Switch
Immediate-effect on/off toggle built on Radix Switch, with `sm` and `default` size presets.
Overview
Switch is built on @radix-ui/react-switch. Use it when toggling the control changes state right away - notifications on / off, dark mode, feature flags. For state that takes effect only on form submission, use Checkbox instead.
Two sizes - sm (compact) and default - are exposed via the size prop. The thumb consumes the size via a group-data-[size] selector so it stays proportional in both presets.
Preview
Installation
bash npx gremorie@latest add rx-switch bash pnpm dlx gremorie@latest add rx-switch bash yarn dlx gremorie@latest add rx-switch bash bunx --bun gremorie@latest add rx-switch Usage
import { Label, Switch } from "@gremorie/rx-forms";
export function Example() {
return (
<div className="flex items-center gap-2">
<Switch id="notifications" defaultChecked />
<Label htmlFor="notifications">Push notifications</Label>
</div>
);
}Angular edition planned for Phase 5h. Star the repo to track progress.
API
<Switch>
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | - | Controlled checked state. |
defaultChecked | boolean | - | Uncontrolled initial state. |
onCheckedChange | (checked: boolean) => void | - | Fires when the user toggles the switch. |
disabled | boolean | false | Disables interaction. |
required | boolean | false | Required for form submission. |
name | string | - | Form field name. |
value | string | "on" | Form field value when checked. |
size | "sm" | "default" | "default" | Footprint preset. sm is h-3.5 w-6; default is h-[1.15rem] w-8. |
Forwards to SwitchPrimitive.Root. Renders an internal SwitchPrimitive.Thumb that translates on data-state=checked.
Composition
- Always pair with a
<Label>viahtmlFormatching the switch'sid. - Use a wrapper with
flex items-center gap-2(orjustify-betweenfor settings rows where the label sits on one side and the switch on the other). - For form-bound boolean values that take effect on submit, prefer
Checkboxinstead.
Variations
Settings row
The canonical pattern for settings pages - label on the left, switch on the right, both vertically centered.
<div className="flex items-center justify-between rounded-md border p-4">
<div className="grid gap-0.5">
<Label htmlFor="dark-mode">Dark mode</Label>
<p className="text-sm text-muted-foreground">
Use a dark theme across the app.
</p>
</div>
<Switch id="dark-mode" defaultChecked />
</div>Compact sm size for toolbars
Use the smaller preset inside dense surfaces (sidebars, mini-toolbars, table cells).
<div className="flex items-center gap-2">
<Switch id="autosave" size="sm" defaultChecked />
<Label htmlFor="autosave">Autosave</Label>
</div>Controlled with optimistic update
For settings that hit the network, flip the switch optimistically and roll back on error.
function NotificationToggle() {
const [enabled, setEnabled] = React.useState(false);
async function handleToggle(next: boolean) {
setEnabled(next);
try {
await api.updateNotifications(next);
} catch {
setEnabled(!next);
toast.error('Could not update notifications.');
}
}
return (
<div className="flex items-center gap-2">
<Switch
id="email-notifications"
checked={enabled}
onCheckedChange={handleToggle}
/>
<Label htmlFor="email-notifications">Email notifications</Label>
</div>
);
}Accessibility
- ARIA: Radix renders
role="switch"witharia-checkedreflecting the state. Screen readers announce the control as "switch" plus the current state. - Keyboard:
Spacetoggles the switch;Tab/Shift+Tabmove focus. - Label association: clicking the
<Label>toggles the switch via standardhtmlForsemantics. - Disabled:
disabledremoves the switch from the tab order, dims the surface, and propagates fade to the linked label viapeer-disabled. - Distinction from Checkbox: Switch is for immediate state changes ("now"); Checkbox is for state captured on form submit ("on submit"). This affects both semantics and user expectations.