# Switch (/docs/switch)





`Switch` is a single boolean control styled as a sliding track + thumb, with an inline label. Use it for settings that take effect immediately; reach for [`Checkbox`](/docs/checkbox) when the value is part of a form to submit. `description` / `error` / `required` are built in via [`Field`](/docs/field). It implements the kit's form-control contract — `name` submits, and `ref` / `onBlur` land on the hidden input — so it binds to react-hook-form, TanStack Form, or a plain `<form>` with no glue.

```tsx
import { Switch } from '@42-central/ui-react/switch';

<Switch label="Available for evals" />
```

## Playground [#playground]

<story.Playground />

## Sizes [#sizes]

Five sizes — `xs` through `xl` — matching the rest of the family. The track and thumb scale together.

<Preview align="block" code="&#x22;<Switch size=\&#x22;xs\&#x22; label=\&#x22;xs\&#x22; defaultChecked />\n<Switch size=\&#x22;sm\&#x22; label=\&#x22;sm\&#x22; defaultChecked />\n<Switch size=\&#x22;md\&#x22; label=\&#x22;md\&#x22; defaultChecked />\n<Switch size=\&#x22;lg\&#x22; label=\&#x22;lg\&#x22; defaultChecked />\n<Switch size=\&#x22;xl\&#x22; label=\&#x22;xl\&#x22; defaultChecked />&#x22;">
  <div className="flex flex-col gap-3">
    <Switch size="xs" label="xs" />

    <Switch size="sm" label="sm" />

    <Switch size="md" label="md" />

    <Switch size="lg" label="lg" />

    <Switch size="xl" label="xl" />
  </div>
</Preview>

## Colors [#colors]

The resting track is palette-independent; only the checked fill adopts `color`. The focus ring is the kit's fixed `brand-500` regardless of palette — see [Focus](/docs/focus). Set `data-color` once on the root and every kit palette tints for free. The one exception is `neutral`, which fills green: its solid is the same white as the thumb on a dark page.

<Preview align="block" code="&#x22;<Switch color=\&#x22;neutral\&#x22; label=\&#x22;neutral\&#x22; defaultChecked />\n<Switch color=\&#x22;brand\&#x22; label=\&#x22;brand\&#x22; defaultChecked />\n<Switch color=\&#x22;green\&#x22; label=\&#x22;green\&#x22; defaultChecked />\n<Switch color=\&#x22;blue\&#x22; label=\&#x22;blue\&#x22; defaultChecked />\n<Switch color=\&#x22;purple\&#x22; label=\&#x22;purple\&#x22; defaultChecked />\n<Switch color=\&#x22;red\&#x22; label=\&#x22;red\&#x22; defaultChecked />&#x22;">
  <div className="flex flex-wrap gap-4">
    <Switch color="neutral" label="neutral" />

    <Switch color="brand" label="brand" />

    <Switch color="green" label="green" />

    <Switch color="blue" label="blue" />

    <Switch color="purple" label="purple" />

    <Switch color="red" label="red" />
  </div>
</Preview>

## Disabled [#disabled]

`disabled` dims the control and blocks interaction.

<Preview align="block" code="&#x22;<Switch label=\&#x22;Disabled\&#x22; disabled />\n<Switch label=\&#x22;Disabled, on\&#x22; disabled defaultChecked />&#x22;">
  <div className="flex flex-col gap-3">
    <Switch label="Disabled" />

    <Switch label="Disabled, on" />
  </div>
</Preview>

## With description & error [#with-description--error]

`description` (helper text) and `error` sit below the control via the built-in [`Field`](/docs/field). Passing `error` flips the track to the invalid (red) state; `required` adds the semantic flag and a red asterisk on the label.

<Preview align="block" code="&#x22;<Switch\n  label=\&#x22;Evaluation reminders\&#x22;\n  description=\&#x22;We'll only ping you about peer evaluations.\&#x22;\n  defaultChecked\n/>\n\n<Switch label=\&#x22;Accept the terms\&#x22; required />\n\n<Switch label=\&#x22;Accept the terms\&#x22; error=\&#x22;You must accept to continue\&#x22; />&#x22;">
  <div className="flex flex-col gap-5">
    <Switch label="Evaluation reminders" description="We'll only ping you about peer evaluations." />

    <Switch label="Accept the terms" />

    <Switch label="Accept the terms" error="You must accept to continue" />
  </div>
</Preview>

## Controlled [#controlled]

Drive the checked state from state. `onCheckedChange` hands you the next `boolean`.

```tsx
'use client';
import { useState } from 'react';
import { Switch } from '@42-central/ui-react/switch';

export function AvailabilityToggle() {
  const [on, setOn] = useState(false);
  return <Switch label="Available for evals" checked={on} onCheckedChange={setOn} />;
}
```

## Forms [#forms]

`Switch` renders a hidden native `<input type="checkbox">`, so a `name` wires it into native `<form>` submission and `FormData`. The submitted `value` defaults to `"on"`.

```tsx
<form action={save}>
  <Switch name="notifications" value="enabled" label="Enable notifications" />
</form>
```

## API [#api]

<TypeTable
  type="{
  checked: { type: 'boolean', description: 'Controlled checked state.' },
  defaultChecked: { type: 'boolean', description: 'Uncontrolled initial checked state.' },
  onCheckedChange: { type: '(checked: boolean) => void', description: 'Fires with the next checked state.' },
  label: { type: 'ReactNode', description: 'Inline label beside the track.' },
  description: { type: 'ReactNode', description: 'Field helper text below the control.' },
  error: { type: 'ReactNode', description: 'Field error; sets the invalid state.' },
  required: { type: 'boolean', default: 'false', description: 'Required flag + asterisk.' },
  invalid: { type: 'boolean', default: 'false', description: &#x22;Red invalid state, OR'd with error.&#x22; },
  disabled: { type: 'boolean', default: 'false', description: 'Disable the control.' },
  readOnly: { type: 'boolean', default: 'false', description: 'Keep contrast but reject changes.' },
  size: { type: '&#x22;xs&#x22; | &#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;', default: '&#x22;md&#x22;', description: 'Track + thumb sizing.' },
  color: { type: 'Color', default: '&#x22;green&#x22;', description: 'Checked fill accent; `neutral` fills green. The focus ring stays brand.' },
  name: { type: 'string', description: 'Name for the hidden input (form submission).' },
  value: { type: 'string', default: '&#x22;on&#x22;', description: 'Submitted value when checked.' },
  classNames: { type: '{ root?, control?, thumb?, label?, field? }', description: 'Per-part class overrides.' },
  testId: { type: 'string', description: 'data-testid on the root — the clickable control Ark renders, not the hidden native input. See the Testing guide.' },
}"
/>

## Accessibility [#accessibility]

* Full Switch model from Ark: `Space` / `Enter` toggles, focus shows the ring, and the track / label / description / error associations are wired (the latter via the built-in `Field`).
* A hidden native `<input type="checkbox">` backs form submission and carries `aria-checked`; the visual track is presented as the switch.
* `invalid` (or `error`) sets `aria-invalid` on the input.
* `testId` renders as `data-testid` on that same root element, never the hidden native input. See the [Testing](/docs/testing) guide.
* RTL is honored via the document `dir` — the track and label mirror automatically.
