Buttons

Buttons allow users to take actions and make choices with a single tap. They communicate actions that users can take and are typically placed throughout your UI.


Usage

The base .Button class provides core button styling. Add variant classes to change appearance.

<button class="Button Button--primary">Primary Action</button>
<button class="Button Button--secondary">Secondary</button>
<button class="Button Button--tertiary">Cancel</button>

Variants

All Variants

<button class="Button Button--primary">Primary</button>
<button class="Button Button--secondary">Secondary</button>
<button class="Button Button--tertiary">Tertiary</button>
<button class="Button Button--ghost">Ghost</button>
<button class="Button Button--outline">Outline</button>
<button class="Button Button--danger">Danger</button>

Primary

The primary button is for the main action on a page. Use sparingly — one primary action per section.

<button class="Button Button--primary">Get Started</button>

Secondary

Secondary buttons are for alternative actions. They pair well with primary buttons.

<button class="Button Button--secondary">Learn More</button>

Tertiary

Tertiary buttons are for less prominent actions. Use for actions like “Cancel” or “Back”.

<button class="Button Button--tertiary">Cancel</button>

Ghost

Ghost buttons are minimal, blending into the background until hovered.

<button class="Button Button--ghost">View All</button>

Outline

Outline buttons have a border but no fill. Good for secondary actions with more visual weight than ghost.

<button class="Button Button--outline">Download</button>

Danger

Danger buttons indicate destructive actions like delete or remove.

<button class="Button Button--danger">Delete Account</button>

Sizes

Buttons come in three sizes: small, medium (default), and large.

<button class="Button Button--primary Button--small">Small</button>
<button class="Button Button--primary">Medium</button>
<button class="Button Button--primary Button--large">Large</button>

With Icons

Buttons can include icons for visual reinforcement. Icons can be leading or trailing.

Leading Icon

<button class="Button Button--primary">
<i class="ph ph-plus Button-icon"></i>
Add Item
</button>
<button class="Button Button--secondary">
<i class="ph ph-download Button-icon"></i>
Download
</button>

Trailing Icon

<button class="Button Button--primary">
Continue
<i class="ph ph-arrow-right Button-icon Button-icon--trailing"></i>
</button>
<button class="Button Button--secondary">
Open Link
<i class="ph ph-arrow-square-out Button-icon Button-icon--trailing"></i>
</button>

Icon-Only Buttons

For actions where the icon is self-explanatory. Always include an aria-label.

<button class="Button Button--icon" aria-label="Search">
<i class="ph ph-magnifying-glass"></i>
</button>
<button class="Button Button--icon" aria-label="Settings">
<i class="ph ph-gear"></i>
</button>
<button class="Button Button--icon" aria-label="Close">
<i class="ph ph-x"></i>
</button>
<button class="Button Button--icon Button--primary" aria-label="Add">
<i class="ph ph-plus"></i>
</button>

States

Loading

Show a loading state when an action is in progress. Disable interaction during loading.

<button class="Button Button--primary Button--loading" disabled>
<span class="Button-spinner"></span>
Loading...
</button>
<button class="Button Button--secondary Button--loading" disabled>
<span class="Button-spinner"></span>
Saving...
</button>

Disabled

Disabled buttons indicate an action is unavailable.

<button class="Button Button--primary" disabled>Primary</button>
<button class="Button Button--secondary" disabled>Secondary</button>
<button class="Button Button--outline" disabled>Outline</button>
<button class="Button Button--danger" disabled>Danger</button>

Button Groups

Group related buttons together.

<div class="ButtonGroup">
<button class="Button Button--secondary">Day</button>
<button class="Button Button--secondary ButtonGroup-item--active">Week</button>
<button class="Button Button--secondary">Month</button>
</div>

Icon Button Group

<div class="ButtonGroup">
<button class="Button Button--icon Button--secondary" aria-label="List view">
<i class="ph ph-list"></i>
</button>
<button class="Button Button--icon Button--secondary ButtonGroup-item--active" aria-label="Grid view">
<i class="ph ph-squares-four"></i>
</button>
<button class="Button Button--icon Button--secondary" aria-label="Column view">
<i class="ph ph-columns"></i>
</button>
</div>

Full Width

Buttons can expand to fill their container.

<div style="max-width: 320px; width: 100%;">
<button class="Button Button--primary Button--block">Create Account</button>
</div>

Common Patterns

<div style="display: flex; gap: var(--space-3); justify-content: flex-end;">
<button class="Button Button--tertiary">Cancel</button>
<button class="Button Button--primary">Save Changes</button>
</div>

Destructive Confirmation

<div style="display: flex; gap: var(--space-3); justify-content: flex-end;">
<button class="Button Button--secondary">Keep</button>
<button class="Button Button--danger">Delete</button>
</div>

Form Submit

<div style="max-width: 320px; width: 100%;">
<div style="margin-bottom: var(--space-4);">
<label style="display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: var(--space-2);">Email</label>
<input type="email" class="Input" placeholder="you@example.com">
</div>
<button class="Button Button--primary Button--block">Subscribe</button>
</div>

Toolbar

<div style="display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); background: var(--bg-s); border-radius: var(--r-m);">
<button class="Button Button--primary Button--small">
<i class="ph ph-plus Button-icon"></i>
New
</button>
<div class="ButtonGroup">
<button class="Button Button--icon Button--secondary Button--small" aria-label="Undo">
<i class="ph ph-arrow-counter-clockwise"></i>
</button>
<button class="Button Button--icon Button--secondary Button--small" aria-label="Redo">
<i class="ph ph-arrow-clockwise"></i>
</button>
</div>
<div style="flex: 1;"></div>
<button class="Button Button--icon Button--ghost Button--small" aria-label="More options">
<i class="ph ph-dots-three"></i>
</button>
</div>

Customization

Override button styles using CSS custom properties:

/* Custom primary color */
.Button--primary {
  --button-bg: oklch(55% 0.2 150);
  --button-hover: oklch(50% 0.2 150);
  background-color: var(--button-bg);
}

.Button--primary:hover {
  background-color: var(--button-hover);
}

/* Custom size */
.Button--xl {
  padding: var(--space-4) var(--space-8);
  font-size: 1.125rem;
}

/* Custom border radius */
.Button--rounded {
  border-radius: var(--space-6);
}

API Reference

Base Classes

Class Description
.Button Base button styles (required)

Variant Classes

Class Description
.Button--primary Primary action button (solid accent color)
.Button--secondary Secondary action button (subtle background)
.Button--tertiary Tertiary action button (text only)
.Button--ghost Minimal button (transparent until hover)
.Button--outline Outlined button (border, no fill)
.Button--danger Destructive action button (red)

Size Classes

Class Description
.Button--small Small button (28px height)
.Button--large Large button (44px height)

Modifier Classes

Class Description
.Button--icon Icon-only button (square aspect ratio)
.Button--block Full-width button
.Button--loading Loading state (use with disabled)

Icon Classes

Class Description
.Button-icon Icon inside button
.Button-icon--trailing Place icon after text
.Button-spinner Loading spinner element

Button Group Classes

Class Description
.ButtonGroup Container for grouped buttons
.ButtonGroup-item--active Active state for segmented control

CSS Reference

/* Base Button */
.Button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--r-s);
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  transition: background-color 0.15s, color 0.15s, border-color 0.15s, box-shadow 0.15s;
  white-space: nowrap;
  text-decoration: none;
  background-color: var(--bg-s);
  color: var(--fg);
}

.Button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Variants */
.Button--primary {
  background-color: var(--accent);
  color: var(--fg-on-accent);
  border-color: transparent;
}

.Button--primary:hover {
  filter: brightness(1.1);
}

.Button--secondary {
  background-color: var(--bg-s);
  color: var(--fg);
  border-color: var(--bd);
}

.Button--secondary:hover {
  background-color: var(--bd);
}

.Button--tertiary {
  background-color: transparent;
  color: var(--fg-3);
  border-color: transparent;
}

.Button--tertiary:hover {
  background-color: var(--bg-s);
  color: var(--fg);
}

.Button--ghost {
  background-color: transparent;
  color: var(--fg);
  border-color: transparent;
}

.Button--ghost:hover {
  background-color: var(--bg-s);
}

.Button--outline {
  background-color: transparent;
  color: var(--accent);
  border-color: var(--accent);
}

.Button--outline:hover {
  background-color: oklch(from var(--accent) l c h / 0.1);
}

.Button--danger {
  background-color: oklch(55% 0.2 25);
  color: white;
  border-color: transparent;
}

.Button--danger:hover {
  background-color: oklch(50% 0.2 25);
}

/* Sizes */
.Button--small {
  padding: var(--space-1) var(--space-3);
  font-size: 0.75rem;
  height: 28px;
}

.Button--large {
  padding: var(--space-3) var(--space-6);
  font-size: 1rem;
  height: 44px;
}

/* Icon-Only */
.Button--icon {
  width: 36px;
  height: 36px;
  padding: 0;
}

.Button--icon.Button--small {
  width: 28px;
  height: 28px;
}

.Button--icon.Button--large {
  width: 44px;
  height: 44px;
}

/* Full Width */
.Button--block {
  display: flex;
  width: 100%;
}

/* Loading */
.Button--loading {
  position: relative;
  color: transparent;
  pointer-events: none;
}

.Button-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: button-spin 0.6s linear infinite;
}

@keyframes button-spin {
  to { transform: rotate(360deg); }
}

/* Disabled */
.Button:disabled,
.Button[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* Icon inside button */
.Button-icon {
  font-size: 1.1em;
  flex-shrink: 0;
}

.Button-icon--trailing {
  order: 1;
}

/* Button Group */
.ButtonGroup {
  display: inline-flex;
  gap: 0;
}

.ButtonGroup .Button {
  border-radius: 0;
}

.ButtonGroup .Button:first-child {
  border-radius: var(--r-s) 0 0 var(--r-s);
}

.ButtonGroup .Button:last-child {
  border-radius: 0 var(--r-s) var(--r-s) 0;
}

.ButtonGroup .Button + .Button {
  margin-left: -1px;
}

.ButtonGroup-item--active {
  background-color: var(--accent);
  color: var(--fg-on-accent);
  border-color: var(--accent);
  z-index: 1;
}

Accessibility

Keyboard Support

Key Action
Enter Activates the button
Space Activates the button
Tab Moves focus to the button

Screen Readers

<!-- Standard button — text is read automatically -->
<button class="Button Button--primary">Submit Form</button>

<!-- Icon-only — requires aria-label -->
<button class="Button Button--icon" aria-label="Close dialog">
    <i class="ph ph-x"></i>
</button>

<!-- Loading state — announce status -->
<button class="Button Button--primary" aria-busy="true" disabled>
    <span class="Button-spinner"></span>
    Saving...
</button>

<!-- Toggle button -->
<button class="Button Button--secondary" aria-pressed="true">
    <i class="ph ph-star-fill"></i>
    Starred
</button>

Disabled vs aria-disabled

<!-- Native disabled — removes from tab order -->
<button class="Button Button--primary" disabled>Cannot Click</button>

<!-- aria-disabled — keeps in tab order (for tooltips) -->
<button class="Button Button--primary" aria-disabled="true">
    Upgrade Required
</button>

Best Practices

Do

  • Use clear, action-oriented labels — “Save Changes” not “Submit”
  • Lead with a verb — “Create Project”, “Delete File”
  • Limit primary buttons — One per section/view
  • Show loading states — Feedback during async actions
  • Size touch targets — Minimum 44px for mobile

Don’t

  • Use vague labels — “Click Here”, “OK”, “Yes”
  • Disable without explanation — Provide tooltip or helper text
  • Use too many styles — Stick to 2-3 variants per view
  • Hide important actions — Primary actions should be visible
  • Forget hover/focus states — Essential for accessibility