Toggle
Switches a setting or feature on or off.
import { Toggle } from "@/ui";Usage
Toggle is controlled: pass checked and flip it in onChange. onChange receives the native change event; call sites ignore it and flip their own state.
const [pixel, setPixel] = useState(true);
<Toggle
label="Include the Petavue tracking pixel"
checked={pixel}
onChange={() => setPixel(!pixel)}
/>The label always sits to the right of the switch, 8px away, and the whole row is clickable. Name the setting in the label, not the action: "Sitemap indexing", not "Turn on sitemap indexing".
With a hint
When the effect of a setting is not obvious, add a 12px --color-text-secondary line underneath that says what the current position means. Indent it 38px so it lines up with the label. Toggle has no hint prop, so this is your own markup.
<Toggle label="Sitemap indexing" checked={sitemap} onChange={() => setSitemap(!sitemap)} />
<p style={{ margin: "4px 0 0 38px", fontSize: 12, color: "var(--color-text-secondary)" }}>
{sitemap
? "Search engines can list this page."
: "Hidden from search engines. Use for pages that only paid campaigns should reach."}
</p>In a settings row
To put the switch at the right end of a row with the setting's name on the left, leave out label and pass an aria-label with the same words. Without it, the switch has no name.
<div className="settings-row">
<span>Sync HubSpot contacts daily</span>
<Toggle
aria-label="Sync HubSpot contacts daily"
checked={sync.hubspot}
onChange={() => setSync((s) => ({ ...s, hubspot: !s.hubspot }))}
/>
</div>States
<Toggle label="Off" checked={false} />
<Toggle label="On" checked />
<Toggle label="Off, disabled" checked={false} disabled />
<Toggle label="On, disabled" checked disabled />
<Toggle label="Some on" indeterminate />| State | Track | Thumb | Hover |
|---|---|---|---|
| Off | --color-grey-300 | White, left | --color-grey-500 track |
| On | --color-primary-500 | White, right | --color-primary-800 track |
| Focus (keyboard) | --shadow-focus ring | ||
| Off, disabled | --color-grey-200 | --color-grey-50 | none |
| On, disabled | --color-primary-300 | --color-grey-50 | none |
| Intermediate | --color-primary-500 | A 12 by 4px white bar in the middle | --color-primary-800 track |
| Intermediate, disabled | --color-primary-300 | --color-grey-50 bar | none |
Use indeterminate on a toggle that controls a group, such as "Sync all sources", when only some of the group are on. It matches Figma's Intermediate state, and screen readers hear "mixed".
The track is 30 by 16px with fully rounded ends, and the 14px thumb slides 14px in 0.2s. The label is 14px regular --color-text-primary, and --color-text-disabled when disabled.
There is one size. Some call sites pass size="lg", but Toggle has no size prop: the value ends up as an attribute on the hidden input and changes nothing.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
label | ReactNode | "" | Text after the switch. When empty, nothing is rendered and you must pass aria-label. |
checked | boolean | false | Controlled. |
indeterminate | boolean | false | Shows the bar. Takes priority over checked. |
onChange | (event) => void | Receives the native change event. | |
disabled | boolean | false | |
className | string | Added to the wrapping <label>. Use for layout only. |
Any other prop (aria-label, aria-describedby, name, ref) is passed to the hidden <input>.
Accessibility
- It is a native checkbox with
role="switch", visually hidden inside a<label>. Space toggles it, and screen readers announce it as a switch that is on or off. - The focus ring shows on keyboard focus only (
:focus-visible). - Several toggles in the product (Data hub status, column and table switches) have no
labeland noaria-label, so screen readers announce an unnamed switch. Always give one or the other. - If you add a hint line, give it an
idand passaria-describedbyso it is read with the switch.
Do and don't
- Do use a Toggle for a setting that is on or off, such as tracking, indexing or a sync, where the state is the point.
- Do keep the label the same in both positions. Say what changes in a hint line, not by rewriting the label.
- Don't use a Toggle to pick items from a list or to agree to terms. Use a Checkbox.
- Don't use a Toggle for a choice between two named options, such as "Monthly" and "Quarterly". Use Radio.