@loke/ui
Version:
143 lines (101 loc) • 3.71 kB
Markdown
---
name: switch
type: core
domain: forms
requires: [loke-ui]
description: >
Switch + SwitchThumb for binary on/off toggles. role=switch semantics, no indeterminate
state. Hidden native checkbox input for form participation. Label required for accessible
name. Use Checkbox for tri-state or multi-select scenarios.
---
# Switch
`/ui/switch` — toggle switch built on `Primitive.button` with `role="switch"`. A hidden `<input type="checkbox">` handles form participation. No indeterminate state.
**Exports:** `Switch`, `SwitchThumb`, `createSwitchScope`
## Setup
```tsx
import { Switch, SwitchThumb } from "@loke/ui/switch";
import { Label } from "@loke/ui/label";
function NotificationToggle() {
return (
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Label htmlFor="notifications">Email notifications</Label>
<Switch id="notifications" name="notifications" defaultChecked>
<SwitchThumb />
</Switch>
</div>
);
}
```
`data-state` on `Switch` and `SwitchThumb`: `"checked"` | `"unchecked"`
## Core Patterns
### Controlled state
```tsx
const [enabled, setEnabled] = useState(false);
<Switch
checked={enabled}
onCheckedChange={setEnabled}
name="darkMode"
>
<SwitchThumb />
</Switch>
```
### Form participation
Inside a `<form>`, a hidden `<input type="checkbox">` is rendered automatically. Use `name` and `value` props to control the submitted field.
```tsx
<form onSubmit={handleSubmit}>
<Switch name="marketing" value="yes" defaultChecked={false}>
<SwitchThumb />
</Switch>
<button type="submit">Save</button>
</form>
```
For a Switch outside its form element, pass the `form` prop with the form's `id`:
```tsx
<Switch name="analytics" form="settings-form" defaultChecked>
<SwitchThumb />
</Switch>
```
## Common Mistakes
### 1. Using Switch when Checkbox semantics are needed
Switch uses `role="switch"` — a binary on/off control. It has no indeterminate state and no multi-select semantics. Screen readers announce it as "on" or "off".
**Wrong:** Using Switch for selecting items in a list, or for a tri-state header row in a table.
**Correct:** Use `Checkbox` with `checked="indeterminate"` for tri-state. Use `Checkbox` for selecting items from a set. Use `Switch` only for settings-style on/off toggles.
Source: `src/components/switch/switch.tsx` — `role="switch"`, no indeterminate branch
### 2. Forgetting SwitchThumb
`Switch` renders the track button only. `SwitchThumb` is the sliding indicator element that moves between checked and unchecked positions. Without it the switch has no visible affordance.
**Wrong:**
```tsx
<Switch name="active" />
```
**Correct:**
```tsx
<Switch name="active">
<SwitchThumb />
</Switch>
```
`SwitchThumb` reads checked state from context — it must be a child of `Switch`.
Source: `src/components/switch/switch.tsx` — `SwitchThumb` uses `useSwitchContext`
### 3. Missing label — no accessible name
Switch renders as `<button role="switch">`. Without an associated Label, screen readers announce the control without context.
**Wrong:**
```tsx
<Switch name="wifi"><SwitchThumb /></Switch>
<span>Wi-Fi</span>
```
**Correct:**
```tsx
<Label htmlFor="wifi">Wi-Fi</Label>
<Switch id="wifi" name="wifi"><SwitchThumb /></Switch>
```
Or wrap:
```tsx
<Label>
Wi-Fi
<Switch name="wifi"><SwitchThumb /></Switch>
</Label>
```
Source: `src/components/switch/switch.tsx` — renders `Primitive.button`
## Cross-references
- **Label** (`/ui/label`) — accessible labelling for Switch
- **Checkbox** (`/ui/checkbox`) — tri-state and multi-select scenarios
- **Choosing the Right Component** — Switch vs Checkbox decision guide