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
on
JSX
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.
PropTypeDefaultWhat it does
namestring—What the switch submits under. Needed for a form; a controlled switch can do without one.
propsOmitTagProps<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.
valuestring | number—The value the form submits when it is on. Defaults to "on", as HTML does.
autoFocusboolean—Focus it on mount. One per page at most — a stolen focus is a lost place in the document.
readOnlyboolean—Rendered and read, but not toggleable. Unlike disabled it stays in the tab order and submits.
defaultCheckedboolean—The state it starts in when the switch is uncontrolled. Pass checked instead to control it.
labelReact.ReactNode—The text beside the switch, rendered inside a label that wraps the input.
labelPropsBoxProps<'label'>—Styles for the wrapping <label>: the row's layout, not the track's own appearance.

Switch keyboard

KeyResult
TabFocuses the switch. It is one tab stop, like any other control.
SpaceToggles it. The platform supplies this one.
EnterToggles it too, and does not submit the surrounding form. APG lists Enter as optional, and a bare checkbox ignores it.

Switch accessibility

  • A real <input type="checkbox"> under role="switch", so focus, the disabled state and form submission are the platform's rather than this component's.
  • aria-checked is the input's own checked state — there is no second element to keep in step with it, and no mixed state to get wrong.
  • label renders 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