Components
Toggle
BetaA switch control for binary on/off settings that take immediate effect — no form submission required.
Purpose
The Toggle is a switch control that turns a single setting on or off immediately — the change takes effect the moment the user activates it, without a form submission or confirmation step. It renders with role="switch" and communicates its current state through aria-checked.
Usage
Use a Toggle when the action takes effect instantly (preference settings, notification toggles, feature flags), there is a single binary setting the user flips back and forth, and the UI needs to communicate the current state clearly at a glance.
Do not use a Toggle when the change requires a Save or Submit action to persist — use a Checkbox instead. When multiple options need to be selected from a set, use Checkboxes; when a single option must be chosen from several, use a Radio Button group. Avoid Toggle for destructive or irreversible actions — its instant nature is inappropriate for high-consequence actions.
Toggle typically appears in account and notification preference screens, settings lists, and inline feature-flag controls.
Anatomy
- 1Track — Pill-shaped container (rounded-full). OFF state: grey-300 background. ON state: teal (#005B55) background. 200 ms colour transition.
- 2Thumb — White circular button that slides left (OFF) or right (ON). The 200 ms translateX transition communicates the state change smoothly.
- 3Label — Optional visible text. Positioned to the left or right of the track via the labelPosition prop. Clicking the label activates the toggle.
- 4Description — Optional secondary text below the label. Provides context without extending the label.
- 5Focus ring — 2 px teal ring with 2 px offset on the track element. Visible on keyboard focus only (focus-visible).
Variants
Default (medium, label right)
The medium size with the label on the right is the most common configuration. Use it for preference screens, account settings, and notification controls.
Small size
The size="sm" toggle (20×36 px thumb) is appropriate for compact settings panels, inline table rows, or toolbars where the medium size would feel too heavy.
| Size | Track dimensions | Thumb diameter | Use when |
|---|---|---|---|
sm | 36×20 px | 16 px | Compact panels, inline table cells, toolbars |
md | 44×24 px | 20 px | General settings, preference screens |
Label left
labelPosition="left" places the label before the track. Use when right-aligning the toggle in a settings list where labels sit on the left and controls on the right.
Without a label
When the toggle sits adjacent to a self-describing element (for example a settings row with a heading and paragraph), you can omit the label and supply an aria-label directly for screen readers.
Price drop alerts
Notify me when holiday prices fall.
With description
The description prop adds secondary text beneath the label, giving context for less-obvious settings without adding it to the label itself.
Settings list pattern
The most common pattern is a vertical list of toggles with label-left alignment, used for account or notification preferences. Use labelPosition="left" and a consistent container width so the tracks align on the right.
States
Off and on
The OFF state uses a bg-grey-300 track with the thumb translated to the left. The ON state uses a bg-teal track with the thumb translated to the right. Both the colour and the thumb position change together so the state is never communicated by colour alone.
Focus
The focus ring (2 px teal, 2 px offset) surrounds the track. It is visible only on :focus-visible — keyboard and sequential focus — not on mouse click. The ring colour (#005B55) contrasts at above 3:1 against white backgrounds.
Disabled
The entire control renders at reduced opacity with cursor-not-allowed. The underlying button element has disabled applied, removing it from the tab order and preventing all interaction.
Use disabled when:
- A setting is managed externally (for example by a corporate account)
- A prerequisite step must be completed first
When a disabled toggle has context the user should know, pair it with help text or a tooltip explaining why.
Best practices
Use Toggle for settings that take effect immediately, such as notification preferences and display options.
Instant effect means no Save needed — the on/off visual communicates the persisted state.
Use Toggle inside a form that requires a Submit action to save the setting.
If the user must save the form for the change to persist, a Checkbox is the correct control.
Write toggle labels as a noun phrase that names the feature: "Price drop alerts", "Email notifications".
Verb-noun labels like these name the thing being toggled without describing the state.
Label the toggle with the current state: "Enabled", "Active", "On". The track itself communicates on/off.
State labels like these become confusing when flipped — 'Enabled' on an off toggle reads as contradictory.
Use labelPosition="left" in settings lists where labels are on the left and controls on the right.
Label-left alignment creates a clean two-column layout with settings on the left and controls on the right.
Mix label positions within the same settings list or preference panel.
Mixing left and right label positions breaks the visual alignment of the control column.
Provide an aria-label when using Toggle without a label prop next to a self-describing heading.
An aria-label communicates the toggle purpose to screen readers when the visible heading is outside the control.
Render a Toggle with no label prop and no aria-label.
Without a label or aria-label, screen readers announce only 'switch' with no name or purpose.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | — | Visible label text. When omitted, supply aria-label for screen readers. |
| labelPosition | 'left' | 'right' | 'right' | Which side of the track the label appears on. Use left for settings lists with right-aligned controls. |
| description | string | — | Secondary text rendered below the label. Provides context without extending the label. |
| checked | boolean | — | Controlled on/off state. When provided, the component is fully controlled and requires an onChange handler. |
| defaultChecked | boolean | false | Uncontrolled initial state. The toggle starts in the ON position when true. |
| onChange | (newValue: boolean) => void | — | Called when the toggle is activated. Receives the new boolean state (true for ON, false for OFF). |
| disabled | boolean | false | Disables interaction. Applies reduced opacity and not-allowed cursor. Removes from tab order. |
| size | 'sm' | 'md' | 'md' | Controls the track and thumb dimensions. sm: 36×20 px track, 16 px thumb. md: 44×24 px track, 20 px thumb. |
| id | string | — | HTML id for the button element. Auto-generated when omitted. Used to associate the label via htmlFor. |
| className | string | — | Additional Tailwind classes merged onto the root wrapper element. |
Accessibility
Role and state
The Toggle renders as a <button> with role="switch". The aria-checked attribute is set to "true" when ON and "false" when OFF. Screen readers announce this as "switch, on" or "switch, off" alongside the accessible name.
<!-- OFF state -->
<button role="switch" aria-checked="false" aria-label="Price drop alerts">...</button>
<!-- ON state -->
<button role="switch" aria-checked="true" aria-label="Price drop alerts">...</button>
Keyboard interaction
| Key | Action |
|---|---|
Tab | Move focus to the toggle |
Shift + Tab | Move focus to the previous interactive element |
Space or Enter | Toggle the switch on or off |
Label requirement
Every Toggle must have an accessible name — either via the label prop or aria-label. Without a name, screen readers announce only "switch" with no indication of what is being toggled.
{/* Correct — label prop */}
<Toggle label="Price drop alerts" />
{/* Correct — aria-label when label is provided by surrounding context */}
<Toggle aria-label="Enable price drop alerts" />
{/* Incorrect — no accessible name */}
<Toggle />
State communication
The toggle communicates its state through three simultaneous signals — aria-checked, track colour, and thumb position. This means the state is never communicated by colour alone, satisfying WCAG 1.4.1 (Use of Colour).
Immediate-effect pattern
Because toggles take effect instantly, provide clear feedback that the action occurred. For settings that trigger an API call, consider a brief loading state and an aria-live status region that announces success or failure.
{/* Pattern for async toggles — use state in your component */}
{/*
const [enabled, setEnabled] = useState(false);
const [status, setStatus] = useState('');
async function handleChange(newValue) {
setEnabled(newValue);
setStatus('Saving...');
await saveSetting(newValue);
setStatus(newValue ? 'Alerts enabled' : 'Alerts disabled');
}
*/}
Colour and contrast
| Element | Foreground | Background | Ratio | WCAG |
|---|---|---|---|---|
| Track ON | — | Teal #005B55 | — | — |
| Thumb | White #FFFFFF | Teal #005B55 | 7.0:1 | AAA |
| Track OFF | — | #CCCCCC | — | — |
| Thumb OFF | White #FFFFFF | #CCCCCC | 1.6:1 | Non-text (track is decorative; state is conveyed by aria-checked and position) |
| Label text | #1A1A1A | White #FFFFFF | 17.4:1 | AAA |
| Description text | #666666 | White #FFFFFF | 5.9:1 | AA |
| Focus ring | Teal #005B55 | White #FFFFFF | 7.0:1 | AAA |
Related components
- Checkbox — use instead of Toggle when the change requires a Save/Submit action, or for selecting multiple options from a set.
- Radio Button — use when a single option must be chosen from a small set of mutually exclusive options, rather than a simple on/off state.