Button

Starts an action.

Styles

  • primarydefault

  • destructive

  • confirm

  • secondary

  • ghost

Sizes

  • sm

  • defaultdefault

  • lg

  • icon-sm

  • icon

  • icon-lg

States

  • rest

  • hover

  • held

  • focus

  • disabled

  • pending

Usage

When to use it
Use
The person does something now: run, publish, save, join, approve, revoke.
Use
A card, footer, dialog or approval row ends in one action, with a way out beside it.
Use
A row or toolbar repeats one quiet action on every item (ghost, icon sizes).
Not for
The control moves to another page. Use a link; a Button acts on this page.
Not for
The control turns a setting on or off at once. Use a switch.
Not for
The person picks one of two to five views. Use a segmented control.

Rules

10 rules
Must
Give a card, footer, dialog or any group of actions at most one primary Button: variant="primary" (ink), "destructive" (coral) or "confirm" (green). Make every other action beside it variant="secondary". Because the three are one glass family, so two of them side by side split the eye and neither reads as the next step; primary is the default variant, so two bare Buttons are two ink ones.
Must
Lead with variant="primary" (ink) unless the action means loss or approval: destructive for what cannot be undone, confirm for saying yes to something proposed and waiting, such as a tool call, a diff or a reviewed deploy. Because coral and green are Glaze's failure and done colours, so on Save or Join they claim something went wrong or already passed, and the approval rows where green means sign-off stop standing out.
Never
Add glass, a gradient, a sheen or a fill to variant="secondary"; it is the plain card surface, the panel's paper with its hairline and a text-1 label. Because only the primary family is glass, so the one grey, coral or green pill leads, and two special buttons side by side give the group two leads.
Never
Copy the glass classes onto something nobody presses, such as a card, a badge or a field. Because glass is the one sign in Glaze that a thing can be pressed, and a glass card reads as a giant button.
Must
Pass pending as a boolean from the first render, with pendingLabel for the verb; never swap the children while an action runs. Because Button reserves the arc's room only when pending is defined, and swapped children change the width and the accessible name mid-press.
Must
Give every icon size an aria-label. Because the button holds only a glyph, so a screen reader announces "button" and the development check logs an error.
Must
Set type="submit" on the Button that submits a form. Because Base UI defaults to type="button", so the press and Enter in a field never submit the form.
Must
Pass only placement classes in className, never fills, shadows, heights, radii or type. Because a hand-set fill drops the hover, held and disabled states, Glaze draws no shadows, and a fixed height clips the label at 200% zoom.
Never
Wrap a Button in an <a>; render it as the anchor with render and nativeButton={false}. Because a button inside a link is invalid HTML, takes two tab stops and is announced as both.
Should
Use size="sm" in a card footer and a row, the default in forms and toolbars, and lg only for a page's one main action. Because a 26px button sits 3px inside the card's footer row, and a 36px one there would push the shell open.

State order

6 states, highest first
1
pending
2
disabled
3
held
4
focus-visible
5
hover
6
rest

Accessibility

5 checks
Focus shows a 2px ink outline 2px out on :focus-visible, on every variant, never coral.
Labels where they sit: ink glass 8.9:1 or more, coral 5.0:1, green 5.4:1, the flat secondary's ink on white 18:1 or more, in both themes.
Name icon sizes with aria-label; the glyph is aria-hidden. Development builds log a button with no name.
Pending sets aria-busy, keeps focus and keeps the idle name; the pending verb is aria-hidden.
Report the result in a role="status" region, never in the label.

Keyboard

2 keys
Enter
Presses the button.
Space
Presses the button on release.

Edge cases

11 edge cases
Long label
Never wraps; a label wider than its container ends in an ellipsis and stays the accessible name.
Row narrower than its buttons
Buttons never shrink. Let the row wrap with flex-wrap.
Zoom 200%
The height grows with the text and the layers scale with the pill.
Slow request
The box keeps its width, focus stays, the arc turns and repeat presses are blocked.
Failed request
Clear pending and the button returns to rest with its name; say what failed in a status region.
Double click or held Enter
Pending blocks repeats; the handler needs no guard of its own.
Disabled
Glass and flat both drop to the same flat grey pill with a faint ring, because a control that cannot be pressed should not look pressable.
Dark theme
Ink, coral and green glass turn light with an ink label; the secondary is the dark panel with its hairline and a light label.
Over a photo or video
Put the actions on the shell or a panel under the media, never over it: Glaze has no blur, so there is no glass for an image.
Forced colors
The layers drop and the border takes ButtonText; the focus outline stays.
Reduced motion
No press scale; the layers still change, and the pending arc stays as a still mark.

API

8 props
variant
'primary' | 'destructive' | 'confirm' | 'secondary' | 'ghost' default 'primary'
size
'sm' | 'default' | 'lg' | 'icon-sm' | 'icon' | 'icon-lg' default 'default'
pending
boolean
pendingLabel
ReactNode
disabled
boolean default false
focusableWhenDisabled
boolean default false
render
ReactElement
className
string

Tokens

39 tokens
--glass-ink-base
rgb(0 0 0 / 0.66)
--glass-rim
0.24
--glass-ink-text
rgb(255 255 255 / 0.90)
--glass-sheen
0.12
--glass-sheen-hover
0.16
--glass-coral-base
rgb(150 34 18 / 0.88)
--glass-coral-text
rgb(255 255 255 / 0.90)
--glass-green-base
rgb(6 86 40 / 0.88)
--glass-green-text
rgb(255 255 255 / 0.90)
--flat-bg
var(--paper)
--flat-ring
var(--line-faint)
--flat-text
var(--text-1)
--flat-hover
#f8f8f8
--glass-core-held
0.44
--scale-press-glass
0.99
--scale-press-flat
0.99
--scale-press
0.97
--hover
rgb(5 5 5 / 0.06)
--pressed
rgb(5 5 5 / 0.10)
--field
#efefef
--line-faint
rgb(5 5 5 / 0.06)
--text-1
rgb(5 5 5 / 0.80)
--text-disabled
rgb(5 5 5 / 0.32)
--control-sm
26px
--control
32px
--control-lg
36px
--icon-sm
12px
--icon
14px
--icon-lg
16px
--radius-pill
999px
--type-label
500 13px/1.35 var(--font-sans)
--type-label-sm
500 12px/1.35 var(--font-sans)
--type-label-lg
500 14px/1.35 var(--font-sans)
--focus
rgb(5 5 5 / 0.80)
--focus-width
2px
--focus-offset
2px
--dur-press
120ms
--dur-spin
800ms
--ease-out
cubic-bezier(0.2, 0.8, 0.2, 1)

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.