Design system

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" />
VariantUse for
primaryThe one main action in a header, dialog or form. Publish, Save, Send.
secondaryA second action next to a primary one, or the main action in a quiet area.
secondaryGhostNeutral actions with a border: Cancel, Close, filters and menu triggers.
ghostLow-emphasis actions in toolbars and rows: Skip, Reject, View all.
blueGhostA 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" />
SizeFigmaHeightPaddingGapTextIconIcon-only
smSmall28px6px 12px6px12px regular12px28 × 28px
mdMedium32px6px 12px6px12px regular12px32 × 32px
lgLarge36px6px 16px8px14px medium16px40 × 36px
xlXL42px10px 24px12px14px medium16px48 × 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.

VariantDefaultHoverPressedDisabled
primaryPrimary 500 fill, white textPrimary 800 fillPrimary 300 fillGrey 100 fill, Grey 300 text
secondaryWhite fill, Primary 500 border and textPrimary 100 fillPrimary 50 fill, Primary 100 border, Primary 300 textGrey 100 fill, Grey 300 text
ghostNo fill, Grey 600 textGrey 50 fill, Grey 800 textNo fill, Grey 300 textNo fill, Grey 300 text
blueGhostNo fill, Primary 500 textPrimary 50 fill, Primary 800 textNo fill, Primary 300 textNo 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

PropTypeDefaultNotes
variant"primary" | "secondary" | "secondaryGhost" | "ghost" | "blueGhost" | "red" | "green""primary"
size"sm" | "md" | "lg" | "xl""md"
labelstringThe text. children also works when you need markup.
iconPhosphor icon componentRendered at 12px (sm, md) or 16px (lg, xl).
iconPosition"prefix" | "suffix""prefix"
iconWeight"regular" | "bold" | "fill" | …"regular"
disabledbooleanfalse
type"button" | "submit" | "reset""button"Set "submit" inside forms.
classNamestringFor 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, pass describeChild so 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 primary per 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 with onClick that navigates.
  • Don't override colours with className. If no variant fits, the design needs a decision, not a new colour.

On this page