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'pendingbooleanpendingLabelReactNodedisabledbooleandefaultfalsefocusableWhenDisabledbooleandefaultfalserenderReactElementclassNamestring
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)