Button
- Component overview: The Primary button is used for a single primary action on a page — used standalone, never placed side by side with other buttons.
- Size baseline: Small=32px, Medium=40px, Large=48px, Icon Only=28px.
- Implementation note:
ui/buttonretains only the structural skeleton; the design layer owns all visual tokens. - Figma spec
Variants
8 visual variants to cover different semantic levels.
Result
Loading...
Live Editor
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}> <Button variant="primary">Primary</Button> <Button variant="secondary">Secondary</Button> <Button variant="tertiary">Tertiary</Button> <Button variant="quaternary">Quaternary</Button> <Button variant="destructive">Destructive</Button> <Button variant="destructive-outline">Destructive Outline</Button> <Button variant="link-color">Link Color</Button> <Button variant="link-gray">Link Gray</Button> </div>
| Variant | Description |
|---|---|
| Primary | Solid dark background with white text — emphasizes the primary action |
| Secondary | White background with a border — secondary to Primary |
| Tertiary | Transparent, no border — shows a secondary background color on hover |
| Quaternary | Transparent, no border — shows a tinted gray background on hover, lightest |
| Destructive | Solid error-color background — emphasizes dangerous actions |
| Destructive Outline | Error-color border and text — softer emphasis for dangerous actions |
| Link Color | Brand-color text link style |
| Link Gray | Gray text link style — transitions to primary color on hover |
States
5 interaction states: Default / Hover / Clicked / Loading / Disabled.
Result
Loading...
Live Editor
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <span style={{ fontSize: 12, color: '#999' }}>Default</span> <Button>Button</Button> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <span style={{ fontSize: 12, color: '#999' }}>Hover</span> <Button className="bg-[var(--Labels-Secondary)]">Button</Button> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <span style={{ fontSize: 12, color: '#999' }}>Clicked</span> <Button className="bg-[var(--Labels-Secondary)]">Button</Button> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <span style={{ fontSize: 12, color: '#999' }}>Loading</span> <Button loading>Button</Button> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <span style={{ fontSize: 12, color: '#999' }}>Disabled</span> <Button disabled>Button</Button> </div> </div>
Loading State
The loading prop automatically disables the button and displays the built-in Spinner. Supported by all variants.
Result
Loading...
Live Editor
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}> <Button variant="primary" loading> Primary </Button> <Button variant="secondary" loading> Secondary </Button> <Button variant="tertiary" loading> Tertiary </Button> <Button variant="quaternary" loading> Quaternary </Button> <Button variant="destructive" loading> Destructive </Button> <Button variant="destructive-outline" loading> Outline </Button> <Button variant="link-color" loading> Link Color </Button> <Button variant="link-gray" loading> Link Gray </Button> </div>
Icon Button
When size="icon", the button renders as a 28×28 square. aria-label is required to ensure accessibility.
Result
Loading...
Live Editor
<div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}> <Button size="icon" variant="primary" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="secondary" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="tertiary" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="quaternary" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="destructive" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="destructive-outline" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="link-color" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="link-gray" aria-label="Add"> <Plus size={16} /> </Button> </div>
Icon-Only Tertiary / Quaternary buttons give different interaction feedback from their text counterparts:
- Tertiary: Hover / Clicked use the Tinted background scale (
Grays/Tinted/Default,Grays/Tinted/Emphasized) instead of the Gray scale used by text buttons. - Quaternary: drops background feedback entirely and conveys interaction through icon color only — Default
Labels/Tertiary→ HoverLabels/Primary→ ClickedLabels/Secondary.
Size Spec
Result
Loading...
Live Editor
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 24 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <Button variant="secondary" size="lg"> Large </Button> <span style={{ fontSize: 12, color: '#999' }}>48px</span> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <Button variant="secondary" size="default"> Medium </Button> <span style={{ fontSize: 12, color: '#999' }}>40px</span> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <Button variant="secondary" size="sm"> Small </Button> <span style={{ fontSize: 12, color: '#999' }}>32px</span> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <Button variant="secondary" size="icon" aria-label="Add"> <Plus size={16} /> </Button> <span style={{ fontSize: 12, color: '#999' }}>28px</span> </div> </div>
| Dimension | Large | Medium | Small | Icon Only |
|---|---|---|---|---|
| Height | 48px | 40px | 32px | 28px |
| Min width | 100px | 100px | 70px | 28px |
| Horizontal padding | 24px | 24px | 16px | 0 |
| Border radius | 5px | 5px | 5px | 5px |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | - | Button content |
variant | 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'destructive' | 'destructive-outline' | 'link-color' | 'link-gray' | 'primary' | Visual variant |
size | 'default' | 'sm' | 'lg' | 'icon' | 'default' | Size: sm=32px, default=40px, lg=48px, icon=28px |
loading | boolean | false | Loading state — shows Spinner and disables interaction |
disabled | boolean | false | Disabled state |
onClick | MouseEventHandler | - | Click callback |
aria-label | string | - | Accessibility label — required when size="icon" |