Switch
A hand-drawn toggle switch for immediate, on/off settings.
Overview
A Switch toggles a setting on or off immediately — unlike a Checkbox, which typically belongs
to a form submitted later, a Switch’s effect is expected to apply right away.
Anatomy
A sketchy track drawn behind a real <input type="checkbox" role="switch"> (React/Web Components)
or a Pressable with accessibilityRole="switch" (React Native). A second, smaller filled thumb
(an ellipse computed directly from @ghds/sketch-core) renders inside the track, positioned left
when off and right when checked.
Variants & States
| State | Status | Notes |
|---|---|---|
| Off | Implemented | — |
| On | Implemented | Track fills solid; the thumb moves to the right. |
| Disabled | Implemented | — |
| Loading | Not implemented | — |
Usage
Do
- Use Switch for settings that take effect immediately (e.g. "Enable notifications").
- Pair a Switch with a clear, static label describing the setting, not the current state ("Notifications", not "On").
Don't
- Don't use Switch inside a form that requires an explicit Save/Submit action — use Checkbox there instead.
- Don't animate the thumb position — v1 ships an immediate, non-animated position swap on toggle; no component in this system has a transition system yet.
Accessibility
- All three platforms use the real, native toggle semantics of their platform’s checkbox-like
control (
<input type="checkbox" role="switch">on React/Web Components,accessibilityRole="switch"on React Native) — Space/click toggles for free, no custom keydown handler. - Browsers compute
aria-checkedfrom the nativecheckedproperty regardless of therole="switch"override, on both React and Web Components. - React Native:
accessibilityState.checkedfor native platforms, plus a directaria-checkedprop for the web target (React Native Web does not derivearia-checkedfromaccessibilityStatethe way it doesaria-disabled). - See the Accessibility Guide for general conventions.
Content
The label names the setting itself (“Enable notifications”), not the current state or an action.
Props API
React@ghds/react
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible label, associated via Radix Label. |
checked | boolean | — | Controlled checked state. |
defaultChecked | boolean | false | Initial checked state when uncontrolled. |
disabled | boolean | false | Disables interaction. |
...rest | InputHTMLAttributes | — | All other native input attributes pass through. |
Web Components@ghds/web-components
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | false | Reflected attribute. |
disabled | boolean | false | Disables interaction and form participation. |
name | string | — | Submitted control name. |
value | string | 'on' | Submitted value when checked. |
label | string | — | Optional visible label, also the accessible name. |
React Native@ghds/react-native
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Visible, accessible label — required. |
checked | boolean | — | Controlled checked state. |
defaultChecked | boolean | — | Initial checked state when uncontrolled. |
onCheckedChange | (checked: boolean) => void | — | Fires with the next checked state on toggle. |
disabled | boolean | false | Disables interaction. |
testID | string | — | Test handle for queries. |
Code examples
// React — @ghds/react
import { Switch } from '@ghds/react/switch';
<Switch
label="Enable notifications"
checked={notifications}
onChange={(event) => setNotifications(event.target.checked)}
/>;
<!-- Web Components — @ghds/web-components -->
<script type="module">
import '@ghds/web-components/switch';
</script>
<gh-switch id="notify" label="Enable notifications"></gh-switch>
<script>
document.getElementById('notify').addEventListener('change', (e) => {
console.log(e.target.checked);
});
</script>
// React Native — @ghds/react-native
import { Switch } from '@ghds/react-native/switch';
<Switch label="Enable notifications" checked={notifications} onCheckedChange={setNotifications} />;
Live Demo
React and Web Components render live below. React Native can’t run in a browser without additional
react-native-web wiring (not set up in this site), so its usage is shown as a code sample instead.