Switch
An on/off control. A real checkbox input underneath, wearing role=switch — so a screen reader says on and off, and a form still submits it.
Import
JSX
import Switch from '@box-kite/react/components/switch';Usage
JSX
<Switch name="notify" label="Email notifications" defaultChecked />Why it is still an input
A switch drawn from a
<div> has to reimplement focus, Space, the disabled state and the tab order — and it submits nothing. This one is the same <input type="checkbox"> a Checkbox renders, with role="switch" over it, so all of that comes from the platform. The role is the whole difference to a screen reader: "on"/"off" rather than "checked"/"not checked".The track and the thumb are one element and its
::before. Nothing decorative is in the accessibility tree, and there is no second element to keep in sync with the first.Controlled
onJSX
const [notify, setNotify] = useState(true);
<Switch name="notify" label="Email notifications" checked={notify} onChange={(e) => setNotify(e.target.checked)} />Styling
Box props on
<Switch> style the track, on top of the built-in switch component style; labelProps styles the <label> around the pair. checked is both the state and the styles for it — the tuple form takes the value first and what :checked should look like second, so recolouring the on state does not need a second prop or a class of your own.JSX
<Switch
name="notify"
label="Email notifications"
checked={[on, { bgColor: 'emerald-500' }]}
onChange={(e) => setOn(e.target.checked)}
labelProps={{ gap: 3, fontSize: 14 }}
/>Switch props
Everything below is this component’s own. All 235 of Box’s style props work on it too, and those are on /box rather than repeated here.
| Prop | Type | Default | What it does |
|---|---|---|---|
name | string | — | What the switch submits under. Needed for a form; a controlled switch can do without one. |
props | OmitTagProps<BoxTagProps<'input'>, 'type'> | — | Attributes for the <input> itself. type is the component's, and role is already switch. |
onInput | (e: React.ChangeEvent<HTMLInputElement>) => void | — | Fires on every keystroke or click. On a checkbox this is the same moment as onChange. |
onChange | (e: React.ChangeEvent<HTMLInputElement>) => void | — | Fires when the switch is toggled. e.target.checked is the new state. |
value | string | number | — | The value the form submits when it is on. Defaults to "on", as HTML does. |
autoFocus | boolean | — | Focus it on mount. One per page at most — a stolen focus is a lost place in the document. |
readOnly | boolean | — | Rendered and read, but not toggleable. Unlike disabled it stays in the tab order and submits. |
defaultChecked | boolean | — | The state it starts in when the switch is uncontrolled. Pass checked instead to control it. |
label | React.ReactNode | — | The text beside the switch, rendered inside a label that wraps the input. |
labelProps | BoxProps<'label'> | — | Styles for the wrapping <label>: the row's layout, not the track's own appearance. |
Switch keyboard
| Key | Result |
|---|---|
Tab | Focuses the switch. It is one tab stop, like any other control. |
Space | Toggles it. The platform supplies this one. |
Enter | Toggles it too, and does not submit the surrounding form. APG lists Enter as optional, and a bare checkbox ignores it. |
Switch accessibility
Implements the WAI-ARIA APG pattern.
- A real
<input type="checkbox">underrole="switch", so focus, the disabled state and form submission are the platform's rather than this component's. aria-checkedis the input's own checked state — there is no second element to keep in step with it, and no mixed state to get wrong.labelrenders the<label>wrapping the input, which is what names it. The track and the thumb are one element and its::before, so nothing decorative reaches the accessibility tree.
Swept with axe on every release, in this state:
Switch. No violations, with contrast and landmark rules left to a human. Screen-reader results are not published yet.Switch style tree
Every part the component draws is a node with a name, so a default can be restyled with
Box.components() instead of a selector — and a variant is a name too.switch