Buttons
Buttons trigger actions, submit forms, and move people through flows. They support several tones, variants, and sizes.
Three semantic tones
Tone communicates the intent of the action. Pick the tone first, then decide how much visual weight the moment deserves by choosing a variant.
The main brand action — new rule, save changes, continue. Exactly one primary tone should dominate a given surface.
Irreversible or negative actions — delete rule, remove member, archive document. Always pair with a confirmation.
Neutral actions — cancel, secondary nav, utility menus. Reads quieter than primary so the primary still wins.
Five variants across every tone
Variant controls visual emphasis. All variants render every tone — pick the tone for meaning and the variant for how loud it should read.
Primary
Solid tonal fill. Highest emphasis — the single most important action on the surface.
Secondary
Soft tinted fill. Works as a supporting action that still feels grounded.
Outline
White surface with a tonal border. Good for paired actions like Cancel / Save.
Ghost
Text only until you hover — the “hover” variant. Use in dense toolbars and inline utilities.
Link
Inline underlined text. Use inside prose or descriptions where a full button would feel heavy.
Four sizes
Horizontal inset is 12px for text buttons; icon-only uses 8px on every side. Use one size per surface — never mix sm and lg in the same row.
6px top/bottom and 12px left/right padding; 13px label. Dense toolbars, table row actions, filter chips.
8px top/bottom and 12px left/right padding; 14px label. The default size for most forms and pages.
10px top/bottom and 12px left/right padding; 16px label. Hero CTAs, onboarding steps, empty-state prompts.
8px padding on all sides; 16px icon. Square icon-only controls — always set aria-label. No label text.
Leading & trailing icons
A button can show an icon before or after its label. Icons inherit the label color and scale with size.
Default, hover, active, disabled
Each variant shifts one step along its tone ramp on hover, one more on active, and drops to 50% opacity when disabled.
Default
Hover
Active
Disabled
Anatomy
Every button is built from the same parts.
Container
Vertical and horizontal padding (sm 6×12, md 8×12, lg 10×12, icon 8×8), 6px radius, border, and surface fill.
Label
Geist Medium at 13/14/16px by size. Secondary-tone outline/ghost/soft fills use role/button/secondary text (#0F172A). Omit for icon-only buttons.
Leading icon
Optional 14px (sm) or 16px (md, lg, icon) glyph before the label. Inherits label color.
Trailing icon
Optional glyph after the label — often a chevron, arrow, or external-link marker.
States
Default → hover → active follow the same tone ramp. Disabled drops opacity to 50%.
Do & Don't
Do
- One primary-tone + primary-variant button per surface — it should always win.
- Pair a destructive button with an outline or ghost Cancel so people can back out.
- Use the ghost (“hover”) variant for dense toolbars and inline utilities.
- Match button size to container density — sm in tables, lg in hero blocks.
Don't
- Don't stack multiple primary-variant buttons next to each other.
- Don't use destructive tone for non-destructive actions just to stand out.
- Don't mix variants within the same button group — stay consistent per action area.
- Don't put a link-variant button inside a dense toolbar; use ghost instead.