# Switch

An on/off control for a setting that applies immediately.

```tsx
import { FormCard, FormRow, Switch } from 'ferry-ui'

export default function SwitchHero() {
  return (
    <FormCard asDiv title="Notifications">
      <FormRow
        label="Invoice emails"
        description="Send a copy of each invoice to the billing contact."
        htmlFor="invoice-emails"
      >
        <Switch defaultChecked />
      </FormRow>
      <FormRow label="Weekly digest" description="Send a summary of the activity each Monday." htmlFor="weekly-digest">
        <Switch />
      </FormRow>
    </FormCard>
  )
}
```

## Usage guidelines

- **A setting that applies immediately.** The change applies on the click. There is no Save button.
- **Two states only.** For more than two states, use [Radio Group](/docs/components/radio-group) or [Toggle Group](/docs/components/toggle-group).
- **Give each switch a name.** Use a [Label](/docs/components/label) with `htmlFor`, or pass `aria-label`.

| Choice | Control |
| --- | --- |
| A setting that applies immediately | `Switch` |
| A choice that applies on submit, or more than one value | [Checkbox](/docs/components/checkbox) |
| One value among 2 to 5 options | [Radio Group](/docs/components/radio-group) |
| A button that stays pressed in a toolbar | [Toggle](/docs/components/toggle) |

## Anatomy

Import the component. A switch has one part.

```tsx title="Anatomy"

<Switch />
```

## Examples

### Label

Give an `id` to the switch. Pass the same value to the `htmlFor` of the `Label`. A click on the text then changes the switch.

In a `FormRow` of [Form Card](/docs/components/form-card), pass `htmlFor` to the row. The row then gives the `id` to the switch.

```tsx
import { Label, Switch } from 'ferry-ui'

export default function SwitchLabel() {
  return (
    <div className="flex items-center gap-2">
      <Switch id="compact-sidebar" />
      <Label htmlFor="compact-sidebar">Compact sidebar</Label>
    </div>
  )
}
```

### Sizes

The `size` prop sets the size of the track. The default is `md`.

```tsx
import { Label, Switch } from 'ferry-ui'

export default function SwitchSizes() {
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center gap-2">
        <Switch id="switch-md" size="md" defaultChecked />
        <Label htmlFor="switch-md">Medium, 34 × 20px</Label>
      </div>
      <div className="flex items-center gap-2">
        <Switch id="switch-sm" size="sm" defaultChecked />
        <Label htmlFor="switch-sm">Small, 28 × 16px</Label>
      </div>
    </div>
  )
}
```

| Size | Track | Use |
| --- | --- | --- |
| `md` | 34 × 20px | Forms and rows of settings. |
| `sm` | 28 × 16px | Dense lists, table cells and menus. |

### Disabled state

`disabled` stops all clicks. A `Label` after a disabled switch shows at half opacity.

```tsx
import { Label, Switch } from 'ferry-ui'

export default function SwitchDisabled() {
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-center gap-2">
        <Switch id="single-sign-on" disabled />
        <Label htmlFor="single-sign-on">Require single sign-on</Label>
      </div>
      <div className="flex items-center gap-2">
        <Switch id="audit-log" disabled defaultChecked />
        <Label htmlFor="audit-log">Keep an audit log</Label>
      </div>
    </div>
  )
}
```

### Controlled state

A switch holds its state by default. Use `defaultChecked` for the first state.

To control the state, pass `checked` and `onCheckedChange`. Save the setting in `onCheckedChange`.

```tsx
import * as React from 'react'
import { Label, Switch, toast } from 'ferry-ui'

export default function SwitchControlled() {
  const [enabled, setEnabled] = React.useState(false)

  function change(checked: boolean) {
    setEnabled(checked)
    // A switch has no Save button: save the setting here.
    toast.success(checked ? 'Maintenance mode is on' : 'Maintenance mode is off')
  }

  return (
    <div className="flex items-center gap-2">
      <Switch id="maintenance-mode" checked={enabled} onCheckedChange={change} />
      <Label htmlFor="maintenance-mode">Maintenance mode</Label>
    </div>
  )
}
```

### In a table

A switch in a cell of a [Table](/docs/components/table) has no visible label. Pass `aria-label`, and use the `sm` size.

```tsx
import { Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ferry-ui'

const API_KEYS = [
  { id: 'production', name: 'Production', created: 'Mar 4, 2026', enabled: true },
  { id: 'staging', name: 'Staging', created: 'Jan 12, 2026', enabled: true },
  { id: 'local', name: 'Local tests', created: 'Nov 30, 2025', enabled: false },
]

export default function SwitchTable() {
  return (
    <Table aria-label="API keys">
      <TableHeader>
        <TableRow className="hover:bg-transparent">
          <TableHead>Name</TableHead>
          <TableHead>Created</TableHead>
          <TableHead className="w-[1%]">Enabled</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {API_KEYS.map((key) => (
          <TableRow key={key.id}>
            <TableCell>{key.name}</TableCell>
            <TableCell>{key.created}</TableCell>
            <TableCell>
              <Switch size="sm" defaultChecked={key.enabled} aria-label={`Enable the ${key.name} key`} />
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}
```

## Accessibility

- <Kbd>Space</Kbd> changes the state of the switch that has the focus.
- Each switch must have an accessible name: a `Label` or an `aria-label`.

## API reference

`Switch` also accepts the props of the Radix UI Switch primitive and each attribute of the `<button>` element.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"default" \| "sm" \| "md"` | `md` | Track size: `md` 34×20px (default: forms, settings rows), `sm` 28×16px (dense lists, table cells, menus). `default` is a deprecated alias of `md`. |
| `checked` | `boolean` |  |  |
| `defaultChecked` | `boolean` |  |  |
| `required` | `boolean` |  |  |
| `onCheckedChange` | `((checked: boolean) => void)` |  |  |
| `asChild` | `boolean` |  |  |
