Switch
A binary toggle input rendered as a native checkbox styled to look like a sliding switch.
Overview
Switch wraps a native <input type="checkbox"> with a custom CSS design. Because it is a real checkbox, it participates in forms, supports keyboard navigation natively, and works with standard React state patterns.
Two implementation notes worth knowing:
classNameapplies directly to the<input>element, not a wrapper div — be precise with your overrides.readonlyusespointer-events-none+aria-readonlyrather than the HTMLreadonlyattribute (which doesn't exist on<input type="checkbox">).
Import
import { Switch } from '@unflow/ui/components/Switch';Props
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | — | Controlled checked state |
onChange | ChangeEventHandler<HTMLInputElement> | — | Change handler |
disabled | boolean | false | Disables interaction, sets aria-disabled and HTML disabled |
readonly | boolean | false | Prevents interaction via pointer-events-none |
defaultChecked | boolean | — | Uncontrolled initial value |
All standard HTMLInputElement props are forwarded. Always provide aria-label.
States
<Switch checked={false} onChange={setChecked} aria-label="Toggle feature" />
<Switch checked={true} onChange={setChecked} aria-label="Toggle feature" />
<Switch checked={false} disabled aria-label="Disabled off" />
<Switch checked={true} disabled aria-label="Disabled on" />
<Switch checked={true} readonly aria-label="Read-only on" />Controlled usage
import { useState } from 'react';
import { Switch } from '@unflow/ui/components/Switch';
function FeatureToggle() {
const [enabled, setEnabled] = useState(false);
return (
<Switch
checked={enabled}
onChange={(e) => setEnabled(e.target.checked)}
aria-label="Enable dark mode"
/>
);
}Uncontrolled usage
<Switch defaultChecked aria-label="Enable notifications" />Readonly vs Disabled
Both prevent the user from changing the value, but communicate different semantics:
disabled— the control is not available. Uses HTMLdisabled+aria-disabled="true". The value is excluded from form submission.readonly— the value is fixed but the control is present and accessible. Usespointer-events-none+aria-readonly="true". The value is included in form submission.
Accessibility
- Renders
<input type="checkbox" role="switch">— natively keyboard-operable (Space to toggle). aria-checkedmirrors thecheckedprop.- A console warning fires in development if
aria-labelis missing. - Associate a visible label using
<label htmlFor={id}>alongside the Switch.
<label htmlFor="dark-mode" className="flex items-center gap-2 cursor-pointer">
<Switch id="dark-mode" checked={dark} onChange={toggle} aria-label="Dark mode" />
<span>Dark mode</span>
</label>SplitButton
A compound action button combining a primary action (left) with a dropdown of secondary actions (right chevron). Useful when one action is clearly primary but alternatives must remain accessible.
InputLabel
A label wrapper for form inputs. Associates a visible label, optional hint text, and an optional required asterisk with any wrapped form control.