Toggle
A two-state button that flips between OFF (idle) and ON (pressed) on every click. Built on Radix Toggle. Two variants (default: transparent; outline: bg-background + border-input + shadow-xs) and three sizes (sm=h-8 / default=h-9 / lg=h-10). All text-sm font-medium, rounded-md. When pressed (data-[state=on]), the surface goes bg-accent and the text/icon go text-foreground. The atom is what ToggleGroup composes.
Anatomy
One button primitive with an icon + optional text label. Radix owns the toggled/pressed state and exposes it via data-[state=on]. The kit maps the Yuno spec via a CVA — variant × size, plus a shared base with the hover / pressed / focus / disabled states.
- 1Toggle (Root)
Radix Root button. Uncontrolled by default (defaultPressed); pass pressed + onPressedChange for controlled toggles.
- 2Icon slot
Phosphor weight=light icon size-4 at the left. Some Dashboard toggles swap to fill on the on-state to reinforce the pressed shape (e.g. Star vs Star-fill for favorite).
- 3Label
Optional text-sm font-medium label to the right of the icon. Follows the same tone as the icon in the current state.
- 4Focus ring
3px ring-ring/50 outline shadow — no offset, no ring color change. Sonner-style focus that never clips the icon.
Variants
Two variants and three sizes cover every Yuno case. Default is the light, chrome-less shape you drop into any toolbar or filter row. Outline is the settled, bordered shape for surfaces where the toggle needs to look like a real button (settings pane, empty state, controlled panel header).
<Toggle aria-label="Bold">
<TextB weight="light" className="size-4" />
</Toggle><Toggle variant="outline" aria-label="Bold">
<TextB weight="light" className="size-4" />
</Toggle><Toggle size="sm" />
<Toggle size="default" />
<Toggle size="lg" />States
Idle (OFF): text-foreground with the variant's chrome. Hover: bg-muted; on the Default variant text becomes text-muted-foreground (Figma spec — hover softens instead of intensifying, to signal 'this is a target, not the state'); on the Outline variant text becomes text-accent-foreground. Pressed / on (data-[state=on]): bg-accent + text-foreground on both variants. Focus: 3px ring-ring/50 outline. Disabled: opacity-50 + pointer-events-none.
Recipes
Ready-to-copy compositions covering the most common Yuno usages of this atom.
The canonical text-editor use: an icon-only toggle for Bold. Wrap with a Tooltip for the label. TextB (light) as OFF, TextB (fill) as ON — the tone shifts feed on top of the state change. Sits in a toolbar row next to sibling toggles (italic, underline).
const [bold, setBold] = React.useState(false);
<Tooltip>
<TooltipTrigger asChild>
<Toggle aria-label="Bold" pressed={bold} onPressedChange={setBold}>
<TextB weight={bold ? "fill" : "light"} className="size-4" />
</Toggle>
</TooltipTrigger>
<TooltipContent>Bold</TooltipContent>
</Tooltip>For quick include / exclude filters at the top of a Table: icon + text label. Use the Outline variant so the chip reads like a control, not a decoration. Pair with a controlled state so the parent Table can react to onPressedChange.
const [live, setLive] = React.useState(true);
<Toggle
variant="outline"
pressed={live}
onPressedChange={setLive}
aria-label="Live"
>
<span className="text-sm">Live</span>
</Toggle>A single toggle that flips between GridFour (light) and List (light) icons. Common in the Dashboard on a list surface where the user picks the density they want. If you need 3+ modes, promote to ToggleGroup type='single'.
const [grid, setGrid] = React.useState(true);
<Toggle
aria-label={grid ? "Switch to list view" : "Switch to grid view"}
pressed={grid}
onPressedChange={setGrid}
>
{grid ? <GridFour weight="light" className="size-4" /> : <List weight="light" className="size-4" />}
</Toggle>Outline sm toggle in the top-right of a Sidebar section. CaretLeft (light) as OFF, CaretRight (light) as ON. onPressedChange lets the parent Sidebar shrink to the icon rail. Focus ring stays visible even when the sidebar is docked.
const [collapsed, setCollapsed] = React.useState(false);
<Toggle
variant="outline"
size="sm"
pressed={collapsed}
onPressedChange={setCollapsed}
aria-label={collapsed ? "Expand" : "Collapse"}
>
{collapsed ? <CaretRight weight="light" className="size-4" /> : <CaretLeft weight="light" className="size-4" />}
</Toggle>Icon-only Toggle that swaps the Phosphor weight when pressed. Fill goes text-primary so the star reads as active.
const [favorite, setFavorite] = React.useState(false);
<Toggle
aria-label={favorite ? "Remove from favorites" : "Add to favorites"}
pressed={favorite}
onPressedChange={setFavorite}
>
<Star weight={favorite ? "fill" : "light"} className={favorite ? "size-4 text-primary" : "size-4"} />
</Toggle>Import
One import from @/components/ui/toggle. toggleVariants is exposed so ToggleGroup and other compounds can share the exact same chrome.
import { Toggle, toggleVariants } from "@/components/ui/toggle";Props
Toggle forwards every Radix Toggle prop. The Yuno-specific surface is the CVA variants (variant, size) — everything else (pressed, defaultPressed, onPressedChange, disabled, asChild) comes from Radix.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "outline" | "default" | default = transparent chrome; outline = bg-background + border-input + shadow-xs. Pick outline when the toggle needs to look like a button in its own right. |
| size | "sm" | "default" | "lg" | "default" | sm = h-8 px-1.5; default = h-9 px-2; lg = h-10 px-2.5. All share text-sm font-medium. |
| pressed | boolean | — | Controlled pressed state. Pair with onPressedChange. If you don't pass this, use defaultPressed for an uncontrolled toggle. |
| defaultPressed | boolean | false | Uncontrolled initial pressed state. Radix owns the state after mount. |
| onPressedChange | (pressed: boolean) => void | — | Fires on every click / keyboard activation. The parent hook usually flips a piece of state. |
| disabled | boolean | false | Disables the toggle — opacity-50 + pointer-events-none. Keep the pressed state accurate even when disabled. |
| asChild | boolean | false | Merge props onto the child (Radix Slot). Use when you must wrap a link or a custom element inside a Toggle shape. |
| aria-label | string | — | Required on icon-only toggles. Describes what the toggle changes ('Bold', 'Add to favorites', 'Collapse sidebar'). |
| className | string | — | Merged onto the button via cn(). Keep additions to layout tweaks (self-start, w-fit) — never to override the pressed / hover surfaces. |
When to use
- Text formatting in an editor toolbar (Bold, Italic, Underline).
- View mode flip between two options (grid ↔ list, day ↔ week).
- One-off include / exclude filter chips at the top of a Table.
- Collapsible surfaces where the trigger stays visible after collapse (sidebar / filter panel).
When not to use
- Persistent user preferences (dark mode, notifications) — use Switch, its shape signals 'this changes a setting'.
- One-of-many exclusive picks — use ToggleGroup type='single' or RadioGroup.
- Actions that fire without a state change (Save, Delete) — use Button.
- Long lists of options — Select or Command scales; a row of toggles doesn't.
Usage
- Pair every icon-only Toggle with a Tooltip so keyboard users know what it toggles.
- Swap Phosphor weights across states when it reinforces the meaning (Star light → Star fill).
- Use controlled state (pressed + onPressedChange) whenever the toggle affects the parent.
- Match the size to the surrounding rail — sm inside dense toolbars, default in regular chrome, lg in mobile-adjacent surfaces.
- Don't use a Toggle for a decision that persists across sessions — that's a Switch.
- Don't stack more than 4 Toggles side-by-side without a divider — cognitive load spikes.
- Don't override the pressed styles (bg-accent + text-foreground) — the atom is deliberately monochrome; if you need color, pick Badge or a custom chip.
- Don't mix Toggle and Button in the same row when both look like chips — pressed state gets ambiguous.
Related
Cross-links to atoms and patterns you may reach for next.
- Toggle groupCompound for 2+ toggles with shared exclusivity (single) or shared multi-select (multiple).
- SwitchFor persistent on/off preferences. Toggle is transient; Switch is a setting.
- ButtonFor actions without state. Toggle is 'is this pressed right now?'.
- Radio groupExclusive one-of-many pick when a picker feels more right than a toggle chip.
- TooltipWraps icon-only Toggles so keyboard users know what they toggle.