role button
Button draft v0.22.1
Triggers an action or event when activated.
A button lets people perform an action with a single activation: submit a form, open a dialog, confirm a choice. Its emphasis communicates how important the action is on the screen; its intent communicates what kind of action it is. Buttons never navigate; use a link for navigation.
Decisions
From decisions in packages/components/specs/components/button.spec.json, newest first. Every active decision is implemented by the spec, the tokens, the web implementation and the guidelines.
Variant
-
Without a size, a button is medium (36 px), as the decision defaults says. The Figma prototype's Button set defaults to xsmall; the repository keeps medium. Its other defaults (accent, neutral, no icon) match the prototype.
Every component with sizes in the system defaults to medium (text field, select, search field, number field, textarea, dropdown, icon button), so controls placed together without a size get the same size step; the prototype's xsmall default is also the top-left variant of its Button set and may come from the set's layout rather than from a choice.
default-size-medium· 2026-10-15 · v0.22.0button.container.min-height.medium -
The intent property states the kind of action: neutral, brand or error. Each intent reads one color family of the mapped roles: neutral, brand and functional.error.
intent-families· 2026-10-02 · v0.10.0 · replacesintent-axisglobal.bg.accent.*global.bg.subtle.*global.text.on-accent.*global.text.on-subtle.* -
Without explicit options, a button is accent, neutral and medium.
defaults· 2026-10-01 · v0.5.1 -
AI actions get their own look, defined later.
ai-actions-own-look· 2026-10-01 · v0.5.0 -
The emphasis property sets the visual weight: accent, subtle or ghost.
emphasis-axis· 2026-10-01 · v0.5.0button.container.background.*button.container.border-color.*button.label.color.*
Accessibility
-
The button keeps the shared focus ring of the Focus ring foundation, at every size and emphasis: 2.4 px (focus.ring.width), color.focus.ring, 4 px from the button (focus.ring.offset). The Figma prototype draws 1.2 px in #497ede (global.border.base.brand.default; #1148a9 on most accent brand variants), 2 px from the button at extra small and small and 4 px above. A 2 px ring from the space scale is preferred for later, as a change of the whole system, tracked as a known issue.
The ring belongs to the Focus ring foundation and is the same on every component; a 2 px ring and whole-pixel lengths are a global change to make later.
focus-ring-foundation-kept· 2026-10-15 · v0.21.0button.container.outline-width.focus-visiblebutton.container.outline-color.focus-visiblebutton.container.outline-offset.focus-visiblefocus.ring.*color.focus.ringWCAG 1.4.11 Non-text Contrast (AA)WCAG 2.4.13 Focus Appearance (AAA)
-
The focus ring is the strong focus of the stroke scale, 2.4 px, for every component, not the 1.2 px the prototype hints for focus rings.
A 2.4 px ring keeps the 2 CSS px thickness of WCAG 2.4.13 Focus Appearance (AAA); 1.2 px meets WCAG 2.2 AA but not that criterion.
focus-ring-strong· 2026-10-01 · v0.7.1focus.ring.widthbutton.container.outline-width.focus-visibleWCAG 2.4.7 Focus Visible (AA)WCAG 2.4.13 Focus Appearance (AAA)
Color
-
A ghost button has no border at rest; on hover and press it takes the subtle border one step behind, as its fill does (decision ghost-tint-one-step-behind) and as in the Figma prototype: the subtle resting border on hover, the subtle hover border on press (global.border.subtle.<family>.default / hover).
ghost-border-one-step-behind· 2026-10-15 · v0.20.0button.container.border-color.ghost.*global.border.subtle.* -
At rest, a ghost brand button, which has no fill and sits on the surface, takes the brand text of the surface, global.text.on-base.brand.default, as in the Figma prototype; on hover and press, when its tint appears, it takes global.text.on-subtle.brand.default as before.
ghost-brand-label-on-base· 2026-10-08 · v0.19.0button.label.color.ghost.brand.*global.text.on-base.brand.defaultglobal.text.on-subtle.brand.default -
The label of subtle and ghost error buttons keeps the Figma library's error text, global.text.on-subtle.functional.error.default, in every state. The Figma prototype uses #940e11 (raw functional.error.700) on subtle error buttons and on ghost error buttons on hover and press; no text token carries it, and the system adds no semantic token for it for now. The label stays under 4.5:1 on the subtle error hover and press fills and the ghost error press fill in light mode, tracked as a known issue (decision figma-library-colors).
No new semantic token for now.
error-label-figma-library· 2026-10-08 · v0.19.0button.label.color.subtle.error.*button.label.color.ghost.error.*global.text.on-subtle.functional.error.default -
The label of subtle and ghost neutral buttons is the primary text color, global.text.on-base.neutral.primary, in every state and in both modes, as in the Figma prototype; it was global.text.on-subtle.neutral.default, a softer grey.
neutral-label-primary· 2026-10-08 · v0.19.0button.label.color.subtle.neutral.*button.label.color.ghost.neutral.*global.text.on-base.neutral.primary -
The subtle neutral fill stays translucent, global.bg.subtle.neutral.* (and ghost neutral borrows it one step behind on hover and press), with the global.border.subtle.neutral.* border. The Figma prototype uses opaque greys (#f7f7f7 / #eaeaea / #dddddd, closest to global.bg.base.neutral.2 / 3 / 4) and a #d5d5d5 border.
A translucent fill darkens the surface it sits on, so the button keeps a visible fill on pages, cards, sidebars and images in both modes; an opaque grey equals the level 1 surface at rest.
subtle-neutral-translucent· 2026-10-08 · v0.18.0button.container.background.subtle.neutral.*button.container.background.ghost.neutral.*button.container.border-color.subtle.neutral.* -
On hover and press, the border of accent and subtle buttons moves one step along the intent ramp with the fill, so it stays one step darker than the fill in every state. The Figma prototype keeps the resting border on most intents, and not on all of them.
Every state keeps a visible edge, and one rule holds for every intent.
border-follows-state· 2026-10-08 · v0.17.0button.container.border-color.accent.*button.container.border-color.subtle.* -
The button exists in every color mode of the system, light and dark, and takes the colors of the mode of its context. Every color aliases the Figma library's mapped roles as they are, in both modes: fills global.bg.<emphasis>.<family>.<state>, borders global.border.<emphasis>.<family>.<state>, label text global.text.on-<emphasis>.<family>, disabled global.*.state.disabled.default; a ghost button reads the subtle text colors. Where the Figma library's error colors fall under WCAG 2.2 AA, the gap is kept and tracked, not corrected in the button.
The Figma library as it is, errors and limitations included, is the starting point the system evolves from.
figma-library-colors· 2026-10-02 · v0.10.0 · replacescolor-modeserror-darker-than-prototypebutton.container.background.*button.container.border-color.*button.label.color.*global.bg.*global.border.*global.text.* -
A ghost button takes the subtle tint one step behind: no fill at rest, the subtle rest fill on hover, the subtle hover fill on press.
ghost-tint-one-step-behind· 2026-10-01 · v0.5.1button.container.background.ghost.*
Typography
-
The label has the negative letter spacing of the Figma prototype: -2 % of the text size at extra small and small, -3 % at medium and large, carried by font.letter-spacing.tight.xs / sm / md / md (-0.24 / -0.28 / -0.48 / -0.48 px). The Button is the only component that uses it.
label-letter-spacing-tight· 2026-10-08 · v0.16.0button.label.typography.*font.letter-spacing.tight.* -
The label line height is 1 (font.line-height.compact), as in the Figma prototype, at every size. At the default text size the minimum height still sets the height; with larger text, a button without an icon grows less than with 1.2.
label-line-height-1· 2026-10-08 · v0.16.0button.label.typography.*font.line-height.compact -
The large button label is 16 px (body.size.md), as in the Figma prototype, the same size as medium: large differs from medium by its height, padding and corners. Label sizes are body.size.xs / sm / md / md, extra small to large.
large-label-16· 2026-10-08 · v0.16.0button.label.typography.large -
Icons keep optical size 20 (font.icon.optical-size.20) at every size, including the 24 px icons of medium and large; the Figma prototype switches between 20 and 24.
It eases implementation and keeps buttons consistent whatever their size.
icon-optical-size-20· 2026-10-08 · v0.15.0button.icon.optical-sizefont.icon.optical-size.20
Dimension
-
The minimum block padding is space.2 / 3 / 5 / 8, extra small to large, so that the icon, the block padding and the border fit inside the minimum height: a button is 24 / 28 / 36 / 44 px high with or without an icon. As before, larger content grows the button and keeps that space above and below.
With 20 px and 24 px icons, the previous 4 px and 6 px made small and medium buttons with an icon about 1 px taller than those without one, because the border takes room inside the minimum height.
block-padding-fits-icon· 2026-10-08 · v0.15.0 · replacesblock-padding-minimumbutton.container.padding-block.*button.container.min-height.* -
The icon size follows the Figma prototype: size.2 / 3 / 4 / 4 (16 / 20 / 24 / 24 px), extra small to large.
The prototype's rendering is the reference for the button.
icon-size-from-prototype· 2026-10-08 · v0.15.0button.icon.size.* -
The gap between an icon and the label follows the size: space.2 / 2 / 4 / 4, extra small to large. It is smaller than in the Figma prototype (6 / 6 / 10 / 16 px).
The prototype's gap is too large between the icon and the label.
gap-by-size· 2026-10-08 · v0.13.0button.container.gap.* -
With an icon, the inline padding narrows on both sides, as in the Figma prototype: the side that holds an icon takes space.2 / 6 / 6 / 8 between the border and the icon, and the label side of a button with an icon on the other side only takes space.6 / 8 / 8 / 10, extra small to large. A button without an icon keeps space.6 / 8 / 12 / 16 on both sides.
The prototype's rendering is the reference for the button.
padding-next-to-icon· 2026-10-08 · v0.12.0button.container.padding-inline.* -
The minimum height follows the Figma prototype from small up and keeps 24 px at extra small: space.24 / 28 / 36 / 44, extra small to large. Every other size, spacing, radius, border, type, icon and motion value is unchanged and comes straight from the Figma library's scales and the design system's additions: the corner radius follows the size (corner-radius.2xs, 2 px, at extra small and small; corner-radius.xs, 4 px, at medium and large); the border is stroke-width.sm (0.6 px) at every size, emphasis and intent; padding space.6 / 8 / 12 / 16, icon size.2 / 2 / 3 / 4 and label body.size.xs / sm / md / lg at medium weight, extra small to large; icons are Material Symbols Outlined glyphs (font.icon.*: fill off, weight 300, grade 0, optical size 20); state transitions take duration.fast and easing.standard.
The prototype's rendering is closer to the intended button. Extra small stays at 24 px so that every size meets the 24 x 24 CSS pixel minimum target by its own size, whatever the layout around it.
heights-from-prototype· 2026-10-08 · v0.11.0 · replacesfigma-library-scalesbutton.container.*button.label.typography.*button.icon.*corner-radius.*stroke-width.smspace.*size.*body.*font.icon.*
Size
-
The size property offers four sizes: xsmall, small, medium and large.
size-scale· 2026-10-01 · v0.5.0button.container.min-height.*button.container.padding-inline.*button.label.typography.*button.icon.size.*
Superseded (8)
-
The button has a minimum block padding of space.2 / 4 / 6 / 8, extra small to large. At the default text size the minimum height still sets the height and nothing moves; when the content outgrows it (larger text, a larger icon), the button grows and keeps that space above and below.block-padding-minimum· 2026-10-08 · v0.14.0 · replaced byblock-padding-fits-icon -
Sizes, spacing, radii, borders, type, icons and motion come straight from the Figma library's scales and the design system's additions, with the same values as before and no semantic layer in between: the corner radius follows the size (corner-radius.2xs, 2 px, at extra small and small; corner-radius.xs, 4 px, at medium and large); the border is stroke-width.sm (0.6 px) at every size, emphasis and intent; padding space.6 / 8 / 12 / 16, minimum height space.24 / 32 / 40 / 48, icon size.2 / 2 / 3 / 4 and label body.size.xs / sm / md / lg at medium weight, extra small to large; icons are Material Symbols Outlined glyphs (font.icon.*: fill off, weight 300, grade 0, optical size 20); state transitions take duration.fast and easing.standard.figma-library-scales· 2026-10-02 · v0.10.0 · replaced byheights-from-prototype -
The button exists in every color mode of the system, light and dark, and takes the colors of the mode of its context. Its color decisions (emphasis, intent, state) are the same in every mode; only the semantic colors they alias change. Dark colors follow the prototype's dark mapping, with the error fill and border kept one step darker than the prototype, as in light mode.color-modes· 2026-10-01 · v0.9.0 · replaced byfigma-library-colors -
Icons are glyphs of Material Symbols, latest version, for every component: Outlined family with fill off, weight 300, grade 0 and optical size 20 px.icons-material-symbols· 2026-10-01 · v0.8.0 · replaced byfigma-library-scales -
The corner radius follows the size: extra small and small buttons have 2 px corners, medium and large buttons 4 px corners.radius-by-size· 2026-10-01 · v0.7.0 · replaced byfigma-library-scales -
The button border is 0.6 px at every size, emphasis and intent: the default component border of the stroke scale.border-width-hairline· 2026-10-01 · v0.6.0 · replaced byfigma-library-scales -
Error colors sit one step darker than the prototype mapping: the accent error fill uses error 600/700/800 (border 700/800/900) and error text on tinted fills uses error 700.error-darker-than-prototype· 2026-10-01 · v0.5.1 · replaced byfigma-library-colors -
The intent property states the kind of action: neutral, brand or error.intent-axis· 2026-10-01 · v0.5.0 · replaced byintent-families