Button
Starts an action. One primary button per area, everything else quieter.
import { Button } from "@/ui";Variants
Pick the variant by how important the action is on that screen, not by colour.
<Button variant="primary" label="Publish" />
<Button variant="secondary" label="Save as template" />
<Button variant="secondaryGhost" label="Cancel" />
<Button variant="ghost" label="Skip" />
<Button variant="blueGhost" label="View all" />| Variant | Use for |
|---|---|
primary | The one main action in a header, dialog or form. Publish, Save, Send. |
secondary | A second action next to a primary one, or the main action in a quiet area. |
secondaryGhost | Neutral actions with a border: Cancel, Close, filters and menu triggers. |
ghost | Low-emphasis actions in toolbars and rows: Skip, Reject, View all. |
blueGhost | A text-like action that should still read as a link. |
Status variants
Use these only when the action itself is destructive or confirms something positive.
<Button variant="red" label="Delete page" icon={Trash} />
<Button variant="green" label="Approve" />Sizes
The four sizes match Figma's Small, Medium, Large and XL. md is the default and fits almost everywhere. Use sm in dense rows and table cells, lg for the main action in a dialog footer, and xl only for a page's single main call to action.
<Button size="sm" label="Small" />
<Button size="md" label="Medium" />
<Button size="lg" label="Large" />
<Button size="xl" label="Extra large" />| Size | Figma | Height | Padding | Gap | Text | Icon | Icon-only |
|---|---|---|---|---|---|---|---|
sm | Small | 28px | 6px 12px | 6px | 12px regular | 12px | 28 × 28px |
md | Medium | 32px | 6px 12px | 6px | 12px regular | 12px | 32 × 32px |
lg | Large | 36px | 6px 16px | 8px | 14px medium | 16px | 40 × 36px |
xl | XL | 42px | 10px 24px | 12px | 14px medium | 16px | 48 × 42px |
All sizes have an 8px radius.
Figma sets Small's label at 10px. Code uses 12px, because 12px is the smallest text in the product. Figma's Large and XL icon-only buttons are wider than they are tall (40 × 36 and 48 × 42); code follows Figma.
With an icon
Pass a Phosphor icon component, not an element. Icons sit before the label by default; use iconPosition="suffix" for arrows that point to where the action goes. An icon-only button must have an aria-label.
import { ArrowRight, Plus, Trash } from "@phosphor-icons/react";
<Button variant="primary" label="New page" icon={Plus} />
<Button variant="ghost" label="View all 11" icon={ArrowRight} iconPosition="suffix" />
<Button variant="secondaryGhost" icon={Trash} aria-label="Delete" />Button sizes the icon for you: 12px on sm and md, 16px on lg and xl.
States
Each variant has four states in Figma: Default, Hover, Pressed and Disabled. In code, Hover is :hover, Pressed is :active, and Disabled is the disabled prop. Hover over and press the buttons above to see them.
| Variant | Default | Hover | Pressed | Disabled |
|---|---|---|---|---|
primary | Primary 500 fill, white text | Primary 800 fill | Primary 300 fill | Grey 100 fill, Grey 300 text |
secondary | White fill, Primary 500 border and text | Primary 100 fill | Primary 50 fill, Primary 100 border, Primary 300 text | Grey 100 fill, Grey 300 text |
ghost | No fill, Grey 600 text | Grey 50 fill, Grey 800 text | No fill, Grey 300 text | No fill, Grey 300 text |
blueGhost | No fill, Primary 500 text | Primary 50 fill, Primary 800 text | No fill, Primary 300 text | No fill, Primary 300 text |
secondaryGhost, red and green are code-only variants with no Figma component yet.
Disabled
Disable a button when its action cannot run yet, and say why nearby or in a tooltip. Do not hide the button: people should see the action exists.
<Button variant="primary" label="Publish" disabled />Props
| Prop | Type | Default | Notes |
|---|---|---|---|
variant | "primary" | "secondary" | "secondaryGhost" | "ghost" | "blueGhost" | "red" | "green" | "primary" | |
size | "sm" | "md" | "lg" | "xl" | "md" | |
label | string | The text. children also works when you need markup. | |
icon | Phosphor icon component | Rendered at 12px (sm, md) or 16px (lg, xl). | |
iconPosition | "prefix" | "suffix" | "prefix" | |
iconWeight | "regular" | "bold" | "fill" | … | "regular" | |
disabled | boolean | false | |
type | "button" | "submit" | "reset" | "button" | Set "submit" inside forms. |
className | string | For layout only (width, margin). Do not restyle the button. |
Any other prop (aria-*, data-*, onClick) is passed to the <button>. The ref is forwarded, so a Button can be a menu or tooltip trigger.
Accessibility
- It renders a native
<button>, so keyboard and screen reader support come for free. - The focus ring uses
--shadow-focus. Do not remove it. - Icon-only buttons need
aria-label. When you wrap one in a Tooltip, passdescribeChildso the tooltip does not replace that label.
Do and don't
- Do write labels as a verb and an object: "Publish page", "Add to dashboard".
- Do keep one
primaryper area. - Don't make a lookalike with a
<div>or a styled<a>. If you need a link that looks like a button, use a Button withonClickthat navigates. - Don't override colours with
className. If no variant fits, the design needs a decision, not a new colour.