# Switch

> A round on/off control for a setting that applies the moment it is flipped.

Source: https://raft-design.botiverse.build/components/switch. Generated from the current release; where it differs from what you remember, this page is right.

A switch turns a setting on or off, and the change applies at once. On, it is a yellow sticker with its knob slid to the right. Switches and radios are the only round controls in the system.

```tsx
import { useCallback, useState } from "react";
import { Bell, Moon } from "lucide-react";
import {
  ListRow,
  ListRowBody,
  ListRowChevron,
  ListRowDescription,
  ListRowGroup,
  ListRowIcon,
  ListRowTitle,
  ListRowValue,
  Switch,
} from "@botiverse/raft-ui";

const APPEARANCES = ["System", "Light", "Dark"] as const;

export function ListRowSettingsComposition() {
  const [appearance, setAppearance] = useState(0);
  const [agentsMerge, setAgentsMerge] = useState(false);
  const handleAppearance = useCallback(() => {
    setAppearance((current) => (current + 1) % APPEARANCES.length);
  }, []);

  return (
    <ListRowGroup className="w-full max-w-sm">
      <ListRow variant="plain" onClick={handleAppearance}>
        <ListRowIcon>
          <Moon />
        </ListRowIcon>
        <ListRowBody>
          <ListRowTitle>Appearance</ListRowTitle>
        </ListRowBody>
        <ListRowValue>{APPEARANCES[appearance]}</ListRowValue>
        <ListRowChevron />
      </ListRow>
      <ListRow variant="plain" interactive={false}>
        <ListRowIcon>
          <Bell />
        </ListRowIcon>
        <ListRowBody>
          <ListRowTitle id="list-row-agents-merge">Agents can merge to main</ListRowTitle>
          <ListRowDescription wrap>Off: a person approves every merge.</ListRowDescription>
        </ListRowBody>
        <Switch
          checked={agentsMerge}
          onCheckedChange={setAgentsMerge}
          aria-labelledby="list-row-agents-merge"
        />
      </ListRow>
    </ListRowGroup>
  );
}
```

## When to use

| Situation                                       | Use                                         |
| ----------------------------------------------- | ------------------------------------------- |
| A setting row that applies when flipped         | Switch                                      |
| A yes or no that applies when the form is saved | [Checkbox](https://raft-design.botiverse.build/components/checkbox.md)         |
| One of several answers                          | [RadioGroup](https://raft-design.botiverse.build/components/radio-group.md) |

Which controls apply at once and which are saved is in [Forms](https://raft-design.botiverse.build/patterns/forms.md).

## Anatomy

- `Switch`: the 50 × 30px track, the same on a phone and a desktop. Required. Beside a label, put it in a `FieldItem`, where it centers on the label's first line, or at the end of a `ListRow variant="plain"`, where it centers on the whole row, with or without a description; at its full 50 × 30 either way. A one-line settings row with a switch is 54px on a phone. Don't add a `margin-top` ([Alignment](https://raft-design.botiverse.build/foundations/alignment.md#a-control-and-its-label)).
- `SwitchThumb`: the round knob. `Switch` renders it by default.

## States

- **Off:** a `fill-muted` track with an ink border, and a white knob on the left.
- **On:** a `brand` sticker with an ink edge, and a white knob on the right. It looks the same in dark mode, where it gains the die-cut edge ([Dark mode](https://raft-design.botiverse.build/foundations/dark-mode.md#the-die-cut-edge)).
- **Disabled:** the shared disabled look, on or off ([States](https://raft-design.botiverse.build/foundations/states.md)).

## Behavior

- Slide the knob over 220ms with `ease-slide` ([Motion](https://raft-design.botiverse.build/foundations/motion.md)).
- Put the switch at the end of a settings row ([ListRow](https://raft-design.botiverse.build/components/list-row.md) with `interactive={false}`). The row itself is not a button and doesn't react to hover or press; focus goes only to the switch.

## Content

- Make the description say what follows from the setting, without repeating the title: for Agents can merge to main, "Off: a person approves every merge."

## Accessibility

Switch wraps Base UI Switch: an element with `role="switch"` whose `aria-checked` says whether it is on, with a hidden native input for forms.

- Name it with `aria-labelledby` pointing at the row's title.
- Set `disabled` when the setting can't be changed.

## Rules

- Keep the switch a pill, though everything else is square: people recognize a switch by its round shape, and a square switch isn't read as one ([Shape](https://raft-design.botiverse.build/foundations/shape.md#principles)).
- Never color a switch green or blue: green means done and online, and an on switch is yellow.

## API

### Switch

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` |  | Whether the switch is currently active. |
| `defaultChecked` | `boolean` | `false` | Whether the switch is initially active. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `form` | `string` |  | Identifies the form that owns the hidden input. Useful when the switch is rendered outside the form. |
| `id` | `string` |  | The id of the hidden input element. |
| `inputRef` | `((instance: HTMLInputElement \| null) => void \| (() => void \| { [UNDEFINED_VOID_ONLY]: never; }) \| DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) \| RefObject<HTMLInputElement \| null>` |  | A ref to access the hidden `<input>` element. |
| `name` | `string` |  | Identifies the field when a form is submitted. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `onCheckedChange` | `(checked: boolean, eventDetails: BaseSwitch.Root.ChangeEventDetails) => void` |  | Event handler called when the switch is activated or deactivated. |
| `readOnly` | `boolean` | `false` | Whether the user should be unable to activate or deactivate the switch. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |
| `required` | `boolean` | `false` | Whether the user must activate the switch before submitting a form. |
| `uncheckedValue` | `string` |  | The value submitted with the form when the switch is off. By default, unchecked switches do not submit any value, matching native checkbox behavior. |
| `value` | `string` |  | The value submitted with the form when the switch is on. By default, switch submits the "on" value, matching native checkbox behavior. |

Also takes the attributes of its element.

### SwitchThumb

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.6.0 (Breaking Changes): **Props that no longer change anything are removed:** `Badge` `uppercase`; `Checkbox` `size`, `variant`, `color`; `Status` `pulse`, `attention` (status never animates); `Switch` / `SwitchThumb` `size`; `SidebarItem` `variant`; `SidebarItemCount` / `SidebarItemIndicator` `variant` and `appearance` (the count is always the pink sticker, the indicator the yellow @); `NotificationCenterActionButton` `variant`; `DrawerContent` `width` (one-go forms use the wide `Dialog`); `AppRailItemIndicator` `attention`; `AppRailItemAttention` `children`; `Steps` `size`, `track`, `renderIndicator`, `renderTraveler`; `FilePreviewBadge` `type`, `color`. Remove them at call sites.
