# Switch (/ui/components/react/switch)



<Playground component="switch" />

## Usage [#usage]

```tsx
import { Switch } from '@appica/ui-react/switch'
```

```tsx
<label className="flex items-center gap-2.5">
  <Switch defaultChecked />
  Airplane mode
</label>
```

`Switch` renders only the toggle — it has no text of its own. Wrap it and its label in a single `<label>` so clicking either flips the switch and assistive tech announces them together; no `id`/`htmlFor` wiring needed. Use it uncontrolled with `defaultChecked`, or controlled with `checked` + `onCheckedChange`.

Reach for a switch when the change takes effect immediately (a setting that's on or off). For a value submitted later with a form — accepting terms, picking items — use a [`Checkbox`](/ui/components/react/checkbox) instead. The thumb slide and check animation are skipped when the user prefers reduced motion.

## Examples [#examples]

### Sizes [#sizes]

`size` scales the track and thumb together — `sm`, `md` (default), or `lg`.

```tsx
import { Switch } from '@appica/ui-react/switch'

export default function SwitchSizes() {
  return (
    <div className="flex flex-wrap items-center gap-6 text-sm select-none">
      <label className="flex items-center gap-2 text-xs">
        <Switch size="sm" defaultChecked />
        Small
      </label>
      <label className="flex items-center gap-2.5">
        <Switch size="md" defaultChecked />
        Medium
      </label>
      <label className="flex items-center gap-2.5 text-base">
        <Switch size="lg" defaultChecked />
        Large
      </label>
    </div>
  )
}
```

### States [#states]

A switch is off or on, and either state can also be `disabled`. Bridging `aria-invalid` paints the error state — useful for a required toggle that hasn't been accepted.

```tsx
import { Switch } from '@appica/ui-react/switch'

export default function SwitchStates() {
  return (
    <div className="flex flex-wrap items-center gap-6 text-sm select-none">
      <label className="flex items-center gap-2.5">
        <Switch />
        Off
      </label>
      <label className="flex items-center gap-2.5">
        <Switch defaultChecked />
        On
      </label>
      <label className="text-foreground-muted flex items-center gap-2.5">
        <Switch disabled />
        Disabled
      </label>
      <label className="text-foreground-muted flex items-center gap-2.5">
        <Switch disabled defaultChecked />
        Disabled on
      </label>
      <label className="flex items-center gap-2.5">
        <Switch aria-invalid />
        Error
      </label>
    </div>
  )
}
```

### With a label and description [#with-a-label-and-description]

A typical settings row: a title and supporting text beside the toggle. Keeping it all inside one `<label>` makes the whole row a single target.

```tsx
import { Switch } from '@appica/ui-react/switch'

export default function SwitchWithLabel() {
  return (
    <label className="border-border flex max-w-sm items-center justify-between gap-4 rounded-xl border p-5 select-none">
      <span className="flex flex-col">
        <span className="text-foreground-intense font-medium">Marketing emails</span>
        <span className="text-foreground-muted text-sm">Receive tips, product updates, and occasional offers.</span>
      </span>
      <Switch defaultChecked />
    </label>
  )
}
```

### Controlled [#controlled]

Drive the state yourself with `checked` + `onCheckedChange` to react to every toggle.

```tsx
'use client'

import { useState } from 'react'
import { Switch } from '@appica/ui-react/switch'

export default function SwitchControlled() {
  const [checked, setChecked] = useState(true)
  return (
    <label className="text-foreground flex items-center gap-2.5 text-sm select-none">
      <Switch checked={checked} onCheckedChange={setChecked} />
      Wi-Fi is {checked ? 'on' : 'off'}
    </label>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The thumb slides toward the start edge instead of the end, following the resolved direction. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="switch" />

## API reference [#api-reference]

`Switch` wraps [Base UI's Switch](https://base-ui.com/react/components/switch) and passes every remaining prop (`ref`, `aria-*`, and native `<button>` attributes) straight through. It exposes `data-*` state attributes (`data-checked`, `data-unchecked`, `data-disabled`, `data-readonly`, `data-invalid`, …) for styling — see the Base UI docs for the full surface.

| Prop              | <ColMinWidth width="200">Type</ColMinWidth>                   | Default | <ColMinWidth width="220">Description</ColMinWidth>                                   |
| ----------------- | ------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------ |
| `size`            | <Code>'sm' \| 'md' \| 'lg'</Code>                             | `'md'`  | Scales the track and thumb together.                                                 |
| `checked`         | `boolean`                                                     | —       | Controlled on/off state. Pair with `onCheckedChange`.                                |
| `defaultChecked`  | `boolean`                                                     | `false` | Uncontrolled initial state.                                                          |
| `onCheckedChange` | <Code>(checked: boolean, details) => void</Code>              | —       | Fires when the switch is flipped on or off.                                          |
| `name`            | `string`                                                      | —       | Field name submitted with a form.                                                    |
| `value`           | `string`                                                      | —       | Value submitted with the form when the switch is on.                                 |
| `uncheckedValue`  | `string`                                                      | —       | Value submitted with the form when the switch is off.                                |
| `form`            | `string`                                                      | —       | `id` of the form that owns the hidden input, when it isn't a descendant.             |
| `disabled`        | `boolean`                                                     | `false` | Removes the switch from the tab order and ignores interaction.                       |
| `readOnly`        | `boolean`                                                     | `false` | Focusable but not toggleable.                                                        |
| `required`        | `boolean`                                                     | `false` | Must be on before its form submits.                                                  |
| `aria-invalid`    | <Code>boolean \| 'true' \| 'false'</Code>                     | —       | Marks the error state; mirrored to `data-invalid` for styling.                       |
| `nativeButton`    | `boolean`                                                     | `false` | Whether the element passed to `render` is a native `<button>`.                       |
| `inputRef`        | <Code>Ref\<HTMLInputElement></Code>                           | —       | Ref to the hidden `<input>` element backing the switch.                              |
| `id`              | `string`                                                      | —       | `id` of the rendered element.                                                        |
| `render`          | <Code>ReactElement \| ((props, state) => ReactElement)</Code> | —       | Replace the rendered element, or compose it with another component.                  |
| `className`       | <Code>string \| ((state) => string)</Code>                    | —       | Extra classes on the track, merged via `tailwind-merge`. May be a function of state. |
| `style`           | <Code>CSSProperties \| ((state) => CSSProperties)</Code>      | —       | Inline styles, optionally derived from state.                                        |

## Accessibility [#accessibility]

* Renders with `role="switch"`. **Space** (or **Enter**) toggles it; **Tab** moves focus to and from it.
* A switch needs an accessible name — wrap it in a `<label>` together with its text (as shown above), or give it an `aria-label`.
* `disabled` removes the switch from the tab order and exposes `data-disabled` for styling; `readOnly` keeps it focusable but inert.
* `aria-invalid` is bridged to `data-invalid` so the error state can be styled without a wrapping `Field`.
* The thumb slide and check animation honour `prefers-reduced-motion`.
