# Switch

> Turns a setting on or off, with an optional label and description.

Source: /docs/components/switch

```tsx
import { Switch } from '@aksornproject/aksara-ui'
```

## Basic switch

Without a `label` the Switch renders the track, the thumb and a hidden input. `checked`, `defaultChecked` and `onCheckedChange` work as in Base UI, and `onCheckedChange` receives the next boolean first. A bare switch needs an `aria-label`.

```tsx
import { Switch } from '@aksornproject/aksara-ui'
import { useState } from 'react'

export default function SwitchBasic() {
  const [on, setOn] = useState(true)

  return (
    <Switch checked={on} onCheckedChange={setOn} aria-label="Show answers" />
  )
}
```

## Label and description

`label` renders a `<label>` linked to the hidden input. Clicking it toggles the switch, and it names the switch. `description` renders under the label and is linked with `aria-describedby`. TypeScript accepts `description`, `labelPosition` and `labelSpacing` only next to a `label`.

```tsx
import { Switch } from '@aksornproject/aksara-ui'

export default function SwitchLabel() {
  return (
    <Switch
      label="Remember me"
      description="Save my login details for next time."
    />
  )
}
```

## Settings rows

`labelPosition="start"` puts the text before the switch, in the DOM as well as on screen. `labelSpacing="spread"` fills the parent and pins the switch to the far end. The default, `hug`, fits the text up to 384px wide.

```tsx
import { Switch } from '@aksornproject/aksara-ui'

export default function SwitchSettings() {
  return (
    <div className="flex w-full max-w-spacing-384 flex-col gap-spacing-16">
      <Switch
        label="Email digests"
        description="A summary of class activity every Friday."
        labelPosition="start"
        labelSpacing="spread"
        defaultChecked
      />
      <Switch
        label="Homework reminders"
        labelPosition="start"
        labelSpacing="spread"
      />
    </div>
  )
}
```

## Sizes

`medium` is 32 by 20px and the default. `large` is 40 by 24px. The thumb grows when the switch turns on.

```tsx
import { Switch } from '@aksornproject/aksara-ui'

export default function SwitchSizes() {
  return (
    <>
      <Switch aria-label="Medium, off" />
      <Switch aria-label="Medium, on" defaultChecked />
      <Switch size="large" aria-label="Large, off" />
      <Switch size="large" aria-label="Large, on" defaultChecked />
    </>
  )
}
```

## Success status

`status="success"` recolours the on track and the focus ring. It draws an X in the thumb while off and a check while on.

```tsx
import { Switch } from '@aksornproject/aksara-ui'

export default function SwitchStatus() {
  return (
    <>
      <Switch status="success" aria-label="Verified, off" />
      <Switch status="success" aria-label="Verified, on" defaultChecked />
    </>
  )
}
```

## Loading

`loading` draws a spinner in the thumb, sets `aria-busy` and blocks every toggle. Focus stays on the switch. Use it while a change waits for the server.

```tsx
import { Switch } from '@aksornproject/aksara-ui'
import { useState } from 'react'

export default function SwitchLoading() {
  const [on, setOn] = useState(false)
  const [saving, setSaving] = useState(false)

  function save(next: boolean) {
    setSaving(true)
    setTimeout(() => {
      setOn(next)
      setSaving(false)
    }, 1500)
  }

  return (
    <Switch
      label="Sync grades"
      checked={on}
      loading={saving}
      onCheckedChange={save}
    />
  )
}
```

## Icons

`checkedIcon` and `uncheckedIcon` fill the thumb while on and while off. Leave one out to keep the status default. Pass `null` to clear it, such as the X of a success switch.

```tsx
import {
  IconInterfaceEssentialBell3,
  IconInterfaceEssentialBellCrossDelete,
} from '@aksornproject/aksara-icons'
import { Switch } from '@aksornproject/aksara-ui'

export default function SwitchIcons() {
  return (
    <>
      <Switch
        size="large"
        aria-label="Notifications"
        defaultChecked
        checkedIcon={<IconInterfaceEssentialBell3 />}
        uncheckedIcon={<IconInterfaceEssentialBellCrossDelete />}
      />
      <Switch
        status="success"
        size="large"
        aria-label="Verified"
        uncheckedIcon={null}
      />
    </>
  )
}
```

## States

`disabled` greys the switch. The off track is pale with a border and a grey thumb, and the on track is grey with a white thumb. Its cursor is `not-allowed`. `readOnly` keeps the default colours without hover and blocks toggling.

```tsx
import { Switch } from '@aksornproject/aksara-ui'

export default function SwitchStates() {
  return (
    <div className="flex flex-col gap-spacing-16">
      <Switch label="Disabled" disabled />
      <Switch label="Disabled and on" disabled defaultChecked />
      <Switch label="Read-only" readOnly defaultChecked />
    </div>
  )
}
```

## Accessibility

- The switch has `role="switch"` and `aria-checked`. Space and Enter toggle it.
- Name every switch with `label` or `aria-label`. The types do not require a name, so nothing warns you when it is missing.
- `description` joins `aria-describedby` after any ids you pass.
- The icons and the spinner are hidden from screen readers. The state comes from `aria-checked`.
- The focus ring draws on keyboard focus only. The deprecated `focused` prop draws it without focus.
- The thumb and track stop animating under `prefers-reduced-motion`.

## API

### Switch

`className` styles the track and `thumbClassName` the thumb. `SwitcherProps` is an alias of the same type. Use `SwitchProps` for type annotations.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, SwitchRootState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: SwitchRootState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `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. |
| `id` | `string` |  | The id of the hidden input element. When `nativeButton` is `true`, the id is applied to the root element. |
| `checked` | `boolean` |  | Whether the switch is currently active. To render an uncontrolled switch, use the `defaultChecked` prop instead. |
| `defaultChecked` | `boolean` | `false` | Whether the switch is initially active. To render a controlled switch, use the `checked` prop instead. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `inputRef` | `React.Ref<HTMLInputElement>` |  | A ref to access the hidden `<input>` element. |
| `name` | `string` |  | Identifies the field when a form is submitted. |
| `form` | `string` |  | Identifies the form that owns the hidden input. Useful when the switch is rendered outside the form. |
| `onCheckedChange` | `((checked: boolean, eventDetails: SwitchRoot.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. |
| `required` | `boolean` | `false` | Whether the user must activate the switch before submitting a form. |
| `value` | `string` |  | The value submitted with the form when the switch is on. By default, switch submits the "on" value, matching native checkbox behavior. |
| `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. |
| `size` | `"large" \| "medium"` | `'medium'` | `'medium'` is 32x20. `'large'` is 40x24. |
| `status` | `"default" \| "success"` | `'default'` | `'success'` recolours the on track and draws X and Check in the thumb. |
| `loading` | `boolean` | `false` | Shows a spinner in the thumb, blocks toggling and sets `aria-busy`. |
| `checkedIcon` | `ReactNode` |  | The icon in the thumb while on. `undefined` takes the status default: Check for Success, nothing for Default. `null` clears it. |
| `uncheckedIcon` | `ReactNode` |  | The icon in the thumb while off. Success draws X by default. `null` clears it. |
| `focused` | `boolean` | `false` | Draws the focus ring without focus. |
| `className` | `string` |  | Classes for the track, merged last. |
| `thumbClassName` | `string` |  | Classes for the thumb, merged last. |
| `label` | `string \| number \| bigint \| boolean \| Iterable<ReactNode> \| Promise<AwaitedReactNode> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>> \| React.ReactPortal \| null` |  | A `<label for>` linked to the hidden input. Clicking it toggles, and Base UI names the switch from it. |
| `description` | `SwitchText` |  | Text under the label. Linked with `aria-describedby` after any ids you pass. |
| `labelPosition` | `"end" \| "start"` | `'end'` | `'end'` puts the label after the switch. `'start'` puts it before. |
| `labelSpacing` | `"hug" \| "spread"` | `'hug'` | `'hug'` fits the text, up to 384px wide. `'spread'` fills the parent and pins the switch to the far end. |