Components · Actions
Toggle
BetaA two-state button, such as marking a set as done.
- Source
components/ui/toggle.tsx - shadcn/ui Toggle
- Figma: not linked yet (Q13)
Barbell back squat
4 sets × 6 reps
When to use
- To turn one option on or off where it sits, with the result visible right away: favorite an exercise, link an exercise into a superset, show the rest timer.
- In toolbars, where a button-shaped control fits better than a switch.
When not to use
- For a setting with a label that saves immediately ("Check-in reminders"): use a Switch. A switch reads as a setting, a toggle as a tool.
- As part of a form that is saved later: use a Checkbox.
- To pick one of several options: use a Toggle group.
- To start an action ("Duplicate"): use a Button. A toggle stays pressed; a button doesn't.
Anatomy
- 1. Container: transparent or outlined; filled with accent when on
- 2. Icon: 16px, aria-hidden
- 3. Label (optional): stays the same in both states
Variants
Default
Outline
| Variant | Use for |
|---|---|
default | Inside a row or toolbar that already has a border: a favorite star on an exercise row. |
outline | On its own, where the control needs a visible edge to look clickable. |
Icon-only or icon with label. Icon-only toggles need an aria-label, and the icon should change shape when pressed (an outlined star becomes a filled one), as in the first example.
States
| State | What changes | Token |
|---|---|---|
| Off | No fill (default) or an outline (outline) | bg-transparent, border-input |
| Hover | Light fill | hover:bg-muted (default), hover:bg-accent (outline) |
On (data-state="on") | Accent fill and text | bg-accent, text-accent-foreground |
| Focus-visible | Ring around the toggle | ring-ring/50, 3px |
| Disabled | Half opacity, no pointer events | opacity-(--opacity-disabled) |
Invalid (aria-invalid) | Red border and ring | border-destructive, ring-destructive/20 |
The on-state fill (accent) is very close to the page background, so "on" is shown mostly by the text color. Pair it with a visible change in the icon (filled star, link icon) so the state doesn't depend on a faint fill. This is reported as a gap: see the Accessibility section.
Sizes
sm (32px) in dense tables and set loggers, default (36px) for most UI, lg (40px) on touch-first screens. All three meet the 24×24px minimum target. Heights come from the 4px spacing unit, not a sizing token (Q5).
Usage guidelines
Content guidelines
- One to three words that name the option, not the action: "Superset", "Rest timer", "Show rest days".
- Sentence case, no punctuation.
- Arabic labels are nouns too: "مؤقت الراحة"، "مجموعة مركّبة".
Accessibility
| Key | Does |
|---|---|
| Tab | Moves focus to the toggle |
| Enter or Space | Turns it on or off |
- Renders a
<button>witharia-pressed. NVDA announces "Superset, toggle button, pressed" or "not pressed"; VoiceOver says "selected" for pressed. - The accessible name never changes with the state; only
aria-presseddoes. - Icon-only toggles need
aria-label, written through the product's i18n so Arabic screens get an Arabic label. - The on state is
bg-accenton the page background, about 1.1:1, so the fill alone doesn't meet the 3:1 non-text contrast a state indicator needs (WCAG 1.4.11). The text color change and an icon change carry the state today.
RTL
- The icon sits before the label, so it moves to the right in RTL. Nothing else changes.
- Icons in toggles are rarely directional; if one is (an arrow), mirror it with
rtl:-scale-x-100.
Tokens used
Read from components/ui/toggle.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.
| Token | Utilities | SuperCoach | Coach |
|---|---|---|---|
| --accent | bg-accent | brand-50 | orange-50 |
| --accent-foreground | text-accent-foreground | brand-700 | orange-700 |
| --destructive | border-destructive ring-destructive/20 | red-600 | red-600 |
| --input | border-input | neutral-200 | neutral-200 |
| --muted | bg-muted | neutral-100 | neutral-100 |
| --muted-foreground | text-muted-foreground | neutral-600 | neutral-600 |
| --ring | border-ring ring-ring/50 | brand-600 | orange-500 |
- Opacity
opacity-(--opacity-disabled) - Radius
rounded-md - Shadow
shadow-xs - Type
font-medium - Type
text-sm
Props
<Toggle>
Also takes every prop of Radix Toggle.Root
| Prop | Type | Default |
|---|---|---|
| variant | "default" | "outline" | "default" |
| size | "default" | "sm" | "lg" | "default" |
Replaces in coach-admin
From the read-only audit of coach-admin: 21 style values, 25 uses.
The only toggle buttons the audit found are the .tgl, .goal-toggle-btn and .sub-toggle-btn classes in the standalone public/supercoach-program-planner.html, which DECISIONS #10 proposes leaving out of the system. Inside the app, on/off controls are switches (.react-toggle, .toggle-switch), which map to Switch, not to this component.
Classes and selectors
.goal-toggle-btn7.tgl5.sub-toggle-btn4.tgl.tgl-no3.goal-toggle-btn:hover2.tgl.tgl-yes2.sub-toggle-btn:hover1.tgl:hover1