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
Modal Actions
<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