Primitives
Switch
An on/off control for a setting that applies immediately.
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>
)
}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 |
| One value among 2 to 5 options | Radio Group |
| A button that stays pressed in a toolbar | Toggle |
Import the component. A switch has one part.
import { Switch } from 'ferry-ui'
<Switch />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, pass htmlFor to the row. The row then gives the id to the switch.
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>
)
}The size prop sets the size of the track. The default is md.
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 stops all clicks. A Label after a disabled switch shows at half opacity.
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>
)
}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.
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>
)
}A switch in a cell of a Table has no visible label. Pass aria-label, and use the sm size.
| Name | Created | Enabled |
|---|---|---|
| Production | Mar 4, 2026 | |
| Staging | Jan 12, 2026 | |
| Local tests | Nov 30, 2025 |
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>
)
}Label or an aria-label.Switch also accepts the props of the Radix UI Switch primitive and each attribute of the <button> element.
| Prop | Type | Default |
|---|---|---|
size | "default" | "sm" | "md" | md |
Track size: | ||
checked | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
defaultChecked | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
required | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||
onCheckedChange | ((checked: boolean) => void) | - |
| See the element or the Radix UI primitive this part renders. | ||
asChild | boolean | - |
| See the element or the Radix UI primitive this part renders. | ||