Toggle
A button that stays pressed or not.
In Dense data
See it live
npm run storybookWhen to use it
In Dense data: One mode in the table toolbar that turns on and off. Not for settings saved at once, which use a switch.
- Use it when: Turning a mode or format on and off in a toolbar, such as bold.
- Not when: A setting saved at once (switch), or a one-off action (button).
Instead:
- Switch: A setting.
- Button: A one-off action.
- Toggle group: Several toggles together.
Variants
What each variant is for in any design system, then what Dense data decided and why. Each sample is the live story with that variant set.
defaultvariantUsed in Dense dataWhat it is for
No border; the pressed state shows as a fill.
In Dense data
In the table toolbar, with no border; the pressed state shows as a fill, quiet beside the data.
outlinevariantNot used in Dense dataWhat it is for
A border, so the toggle reads as a control at rest.
In Dense data
Borders on every toggle would compete with the data.
defaultsizeNot used in Dense dataWhat it is for
The normal size.
In Dense data
Toggles sit in the table toolbar, at the smaller size like compact buttons.
smsizeUsed in Dense dataWhat it is for
A smaller toggle.
In Dense data
The table toolbar, matching compact buttons. It still fits a mouse.
lgsizeNot used in Dense dataWhat it is for
A larger toggle.
In Dense data
Controls stay small so more records fit on one screen.
Why it works this way
Toggle looks the way it does because of 4 answers about this product. Each card is one answer, in three steps: the answer, what it decided for Toggle, and what would move if it changed.
content.q11The answer
Most of the screen is numbers and records.
2So Toggle
- ✓uses default: In the table toolbar, with no border; the pressed state shows as a fill, quiet beside the data.
- –does not use outline: Borders on every toggle would compete with the data.
Badge: neutral Badge: info Badge: success Badge: warning Badge: danger Sidebar: default Sidebar: outline Empty: default Item: default Item: outline3If it changed
and 44 answers that follow from it: A lot, Scan and compare, People keep their place, and 41 more.
tasks.q21The answer
The same actions repeat many times an hour.
2So Toggle
- ✓uses default: In the table toolbar, with no border; the pressed state shows as a fill, quiet beside the data.
Button: ghost Input group: inline-end Sidebar: default Alert: default Toast: success3If it changed
and 11 answers that follow from it: Time on task, All day, Three, and 8 more.
content.q21The answer
Experts want many records on one screen.
2So Toggle
- –does not use default: Toggles sit in the table toolbar, at the smaller size like compact buttons.
- ✓uses sm: The table toolbar, matching compact buttons. It still fits a mouse.
- –does not use lg: Controls stay small so more records fit on one screen.
Button: default Button: compact Button: large Input group: block-start Input group: block-end Field: horizontal Sidebar: lg3If it changed
and 14 answers that follow from it: Quiet, Only the steps the screens need, Small, and 11 more.
accessibility.values1The answer
Mouse and keyboard; compact targets are enough.
2So Toggle
- ✓uses sm: The table toolbar, matching compact buttons. It still fits a mouse.
Button: default Button: compact Item: sm3If it changed
No other answer follows from it.
Words
- Lead with the object, then the state: "Alert 2041 contained."
- Use numbers, not words for numbers.
- One verb per button. No "please", no exclamation marks.
- Use: Investigate, Filter, Assign, Escalated, Resolved. Avoid: Oops, Awesome, Please, Simply.
The labels components use, from the voice tokens:
content.action.commit: Savecontent.action.cancel: Cancelcontent.action.destroy: Deletecontent.action.retry: Retrycontent.action.empty-action: Change filters
See Voice and tone for the whole voice.
Accessibility and keyboard
| Keyboard | What happens | Not |
|---|---|---|
| undefined | Space or Enter presses it. | undefined |
Test before you ship:
- It says whether it is pressed. undefined
Use it in code
npx open-ds add toggle
The component reads only component keys and semantic tokens, so it follows the brand and mode on <html>.
Tokens it reads
Toggle reads 24 tokens of its own, each an alias of a semantic token. Each row is one part: its own token, the semantic it aliases, and the value it ends at. Change a semantic and every component that reads it follows; override one of Toggle's tokens to change Toggle alone.
Dense data decides what changes when you switch theme. Here a switch changes colors only, so the marked rows differ between light and dark; everything else holds steady.
| Part | Its token and the semantic it aliases | Value |
|---|---|---|
| Toggle | ||
| Gap | component.toggle.gapspace.gap.defaultvar(--toggle-gap) | space.8:0.5rem |
| Corner | component.toggle.radiusradius.controlvar(--toggle-radius) | radius.2:0.125rem |
| Text size | component.toggle.font-sizetype.body.font-sizevar(--toggle-font-size) | font.size.14:0.875rem |
| Weight | component.toggle.font-weighttype.label.font-weightvar(--toggle-font-weight) | font.weight.500:500 |
| Background hover Changes with theme on hover | component.toggle.background-hovercolor.surface.sunkenvar(--toggle-background-hover) | Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38 |
| Label hover Changes with theme on hover | component.toggle.label-hovercolor.text.mutedvar(--toggle-label-hover) | Lightslate.light.600:#535D6DDarkslate.dark.400:#969FAD |
| Border focus Changes with theme on focus | component.toggle.border-focuscolor.border.focusvar(--toggle-border-focus) | Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6 |
| Disabled opacity disabled when disabled | component.toggle.disabled-opacity-disabledopacity.disabledvar(--toggle-disabled-opacity-disabled) | opacity.40:0.4 |
| Border invalid Changes with theme when invalid | component.toggle.border-invalidcolor.status.dangervar(--toggle-border-invalid) | Lightred.light.600:#C42B2BDarkred.dark.600:#F28B8B |
| Focus ring invalid Changes with theme when invalid | component.toggle.focus-ring-invalidcolor.status.dangervar(--toggle-focus-ring-invalid) | Lightred.light.600:#C42B2BDarkred.dark.600:#F28B8B |
| Background on Changes with theme when on | component.toggle.background-oncolor.surface.sunkenvar(--toggle-background-on) | Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38 |
| Label on Changes with theme when on | component.toggle.label-oncolor.text.primaryvar(--toggle-label-on) | Lightslate.light.900:#161B23Darkslate.dark.900:#F7F8FA |
| Icon size | component.toggle.icon-sizesize.iconvar(--toggle-icon-size) | size.16:1rem |
| Focus on focus | component.toggle.focusborder-width.focusvar(--toggle-focus) | border-width.2:2px |
| Focus ring focus Changes with theme on focus | component.toggle.focus-ring-focuscolor.border.focusvar(--toggle-focus-ring-focus) | Lightviolet.light.600:#4B3FD9Darkviolet.dark.600:#A9A3F6 |
| Focus ring offset focus Changes with theme on focus | component.toggle.focus-ring-offset-focuscolor.surface.canvasvar(--toggle-focus-ring-offset-focus) | Lightslate.light.50:#F7F8FADarkslate.dark.0:#0D1117 |
| Outline | ||
| Border Changes with theme | component.toggle.outline.bordercolor.border.strongvar(--toggle-outline-border) | Lightslate.light.500:#6B7585Darkslate.dark.400:#969FAD |
| Shadow | component.toggle.outline.shadowelevation.cardvar(--toggle-outline-shadow) | shadow.none |
| Background hover Changes with theme on hover | component.toggle.outline.background-hovercolor.surface.sunkenvar(--toggle-outline-background-hover) | Lightslate.light.100:#EEF1F5Darkslate.dark.100:#262D38 |
| Label hover Changes with theme on hover | component.toggle.outline.label-hovercolor.text.primaryvar(--toggle-outline-label-hover) | Lightslate.light.900:#161B23Darkslate.dark.900:#F7F8FA |
| Default | ||
| Padding, left and right | component.toggle.default.padding-xcomponent-spacing-map.x2var(--toggle-default-padding-x) | space.8:0.5rem |
| Sm | ||
| Height | component.toggle.sm.heightsize.control.defaultvar(--toggle-sm-height) | size.32:2rem |
| Padding, left and right | component.toggle.sm.padding-xcomponent-spacing-map.x2var(--toggle-sm-padding-x) | space.8:0.5rem |
| Lg | ||
| Padding, left and right | component.toggle.lg.padding-xcomponent-spacing-map.x3var(--toggle-lg-padding-x) | space.12:0.75rem |