A button triggers an action or takes the user to another page. It lets the user make a choice with a single tap. Use a mix of styles on one page to point at the main action and still offer the alternatives.
Tokens, sizes, measurements, states and requirements. Values come from the live token package and from Figma: Button, Icon buttons, Button dot & badges.
Resolved from the live token package.
| Variant · part | Where | Token | Value |
|---|
Open points: Figma sets ss02 and ss03 on label-lg and label-sm but not on label-md (Small): the CSS follows Figma, confirm that is meant. Disabled Primary inverted and Accent were read from the screenshot (grey surface-disabled fill). Figma uses both text-primary-inverted and text-action-primary-inverted for the white of the badge number: the CSS uses the action token. The badge pill uses a literal 100px radius. The toggle icon button has no focus or disabled state in Figma: disabled uses text-disabled, because icon/disabled (#939595) has no token.
Heights and type per size and breakpoint. The table is generated from the same numbers the Breakpoint toggle previews.
| Size | Height | Padding | Label inset | Icon | Label style |
|---|
Icon-only buttons are squares: width equals height (56 / 48 / 40 / 32px on desktop, 48 / 40 / 32 / 32px on mobile). The 1px frame of Secondary, System and their inverted versions is drawn inside the box, so it never changes the size: Primary and Secondary of the same size are equally wide. The tablet range (640 to 768px) uses the Mobile values.
Number badge: a pill, as high as it is wide for one digit and wider for two or “99+”. Offsets are from the top and right edge of the button. Same numbers for a button with a label and an icon-only button, except XSmall icon only (2px).
| Size · breakpoint | Number: offset | Height | Side padding | Digits | Dot: offset |
|---|
The badge is surface-danger-default with a white number (text-action-primary-inverted), uppercase, 600. The dot is a 6px circle in the same color.
| Version | Box | Icon |
|---|---|---|
| Toggle icon, Large | 24px | 24px |
| Toggle icon, Small | 20px | 20px |
| Close | 24px (2px padding) | 20px |
Close is 24px in Figma, below the usual 44px touch target, and Figma has no larger hit-area version. It meets the WCAG 2.2 minimum (24px).
The padding of each size: the space between the edge of the button and its content. The label has an extra inset on top of it (see the table). The numbers are read from the component.
| Attribute | Large | Medium | Small | XSmall |
|---|---|---|---|---|
| Height, desktop | 56px | 48px | 40px | 32px |
| Height, mobile | 48px | 40px | 32px | 32px |
| Padding left and right, desktop | 16px | 12px | 8px | 6px |
| Padding left and right, mobile | 12px | 8px | 4px | 6px |
| Label inset, left and right, desktop | 8px | 8px | 8px | 4px |
| Label inset, left and right, mobile | 8px | 8px | 6px | 4px |
| Icon | 24px | 24px | 24px | 20px |
| Padding above and below | None: the height is fixed and the content is centred | None | None | None |
| Frame | 1px, inside the box | 1px, inside the box | 1px, inside the box | 1px, inside the box |
| Focus ring | 2px line, 2px gap, outside the box | 2px line, 2px gap | 2px line, 2px gap | 2px line, 2px gap |
An icon-only button has no padding: it is a square the size of its height, with the icon centred. The icon button has no padding either (Close has 2px).
The padding and the label inset as spacing tokens.
| Part | Value |
|---|---|
| Padding, Large desktop | space-16 |
| Padding, Large mobile and Medium desktop | space-12 |
| Padding, Medium mobile and Small desktop | space-8 |
| Padding, Small mobile | space-4 |
| Padding, XSmall | 6px: literal, no spacing token |
| Label inset | space-8 (Large, Medium, Small desktop), space-4 (XSmall); Small mobile 6px: literal, no spacing token |
Every state for every variant. Hover and focus are forced on this page with data-state so they show at the same time; in your code they come from :hover and the keyboard focus rule. The buttons are live (the disabled ones do not respond).
| State | Description |
|---|---|
| Enabled | The standard state of a button: it is available for interaction and is not currently selected. It includes Hover and Focus of the button type. |
| Hover | Shown when the user hovers the cursor over the button, to show that the component is interactive. |
| Focus | The visual style shown when the button is reached with the keyboard (often with Tab). It shows that the button is ready to be activated. It is a blue outline (border-focus), so users can move through the interface without a mouse. |
| Disabled | A disabled button prevents interaction: hover, focus and click. It is not focusable, so Tab skips it. Screen readers may still announce it as unavailable when the user browses the page. Do not associate a tooltip with a disabled button. |
| Selected | Used by the System button in some cases. A selected button has been actively chosen, toggled on, or is the item currently in focus within a group of options. It shows which option is active and confirms that the user’s action was registered. |
| Variant | Enabled | Hover | Focus | Disabled |
|---|
Hover: Primary, Destructive and Accent get a surface-opacity-white-20 layer on top of their color, Primary inverted a surface-opacity-black-12 layer. Secondary and Blank get surface-opacity-black-05, the inverted ones surface-opacity-white-20. System hover and System selected hover are the same: a surface-opacity-black-12 surface and a border-hover frame. The focus ring is a 2px border-focus line with a 2px gap, drawn outside the button. Disabled: Secondary, Secondary inverted and System keep a frame in border-disabled with no fill; Blank has neither; the others get surface-disabled.
Eight icons. Each has Enabled, Hover, Selected and Selected hover, in Large (24px) and Small (20px).
Hover changes the icon color to text-action-primary-hover; there is no surface. Selected hover is the filled icon in that color. Figma has no focus or disabled state for these icons: focus follows the button rule, disabled uses text-disabled. The “cancel” icon is always filled.
| Version | Enabled | Hover | Focus | Disabled |
|---|
Figma only defines Enabled and Hover for Close. Focus follows the shared rule; disabled is not defined and is not used for a close control.
| Size | Type | Mobile (below 769px) | Desktop (769px+) | Font features |
|---|---|---|---|---|
| Large | label-lg | 16/16, 0.32px | 18/18, 0.18px | ss02, ss03 |
| Medium | label-lg | 16/16, 0.32px | 18/18, 0.18px | ss02, ss03 |
| Small | label-md | 14/14, 0.42px | 16/16, 0.48px | None |
| XSmall | label-sm | 12/12, 0.48px | 14/14, 0.56px | ss02, ss03 |
Breuer Condensed, weight 600 (Figma bold 700 − 100), uppercase through CSS, so write the label in sentence case in the HTML. The badge number is 12/12 (Large and Medium mobile) or 14/14 (desktop), and 10/10 in Small and XSmall; see the badge sizes under Sizes.
| Element | Class | Notes |
|---|---|---|
| Button | btn | A button for actions, an a for navigation. |
| Label | btn__label | Always wrap the label in it: it carries the label inset. Without it the text sits too close to the edge. |
| Icon | btn__icon | Material Symbols, aria-hidden="true". |
| Badge number | btn__badge | Top right corner, aria-hidden="true". |
| Badge dot | btn__dot | 6px circle, aria-hidden="true". |
| Icon button | icon-btn, icon-btn--sm, icon-btn--filled | A single icon that is a control. aria-pressed gives the filled icon. |
| Close | icon-btn icon-btn--close, icon-btn--close-inverted | 24px box, 20px icon. |
| Class | Use |
|---|---|
btn--primary, btn--primary-inverted | Main call to action. Inverted: the same role, where the standard variant lacks contrast. |
btn--secondary, btn--secondary-inverted | Second action, with a frame. |
btn--blank, btn--blank-inverted | The quietest action, no fill and no frame. |
btn--destructive | Removes something. |
btn--accent | Brand-colored call to action. |
btn--system | A toggle in a group of buttons, with aria-pressed. |
btn--lg, btn--md, btn--sm, btn--xs | Size. Each sets every --btn-* property, in the mobile block and again from 769px. |
btn--icon | Icon only: a square, needs an aria-label. |
A button hugs its content. An icon-only button is a square. Full width comes from the page layout, not from a modifier.
| Property | Duration | Easing |
|---|---|---|
Hover layer: opacity of ::before | duration-fast-2 (100ms) | ease-standard |
Focus ring: outline-color | duration-fast-2 | ease-standard |
Frame: box-shadow (System hover frame) | duration-fast-2 | ease-standard |
Figma calls this transition “Dissolve”: a custom bezier .35, 0, .35, 1 over 100ms, which is ease-standard. Under prefers-reduced-motion: reduce the transition is turned off.
| Input | Result |
|---|---|
| Click, Enter, Space | Runs the action. Use <button> for actions and <a class="btn …"> for navigation. |
| Tab / Shift+Tab | Moves between buttons. The focus ring shows only when body.keyboard-nav is set (on Tab, removed on mousedown and touchstart), the same as the form controls. |
| Disabled | Use the disabled attribute. It is not focusable and has no hover. Say why in nearby text when the reason is not obvious. |
| System selected | Set aria-pressed="true". It is a toggle, so it must not be used for page navigation. |
| Icon only | Add aria-label, and hide the icon with aria-hidden="true". |
| Label | Uppercase through CSS, so write it in sentence case in the HTML. Keep it short and start with a verb. |
| Icon only tooltip | Always with a tooltip, shown after a 300ms hover delay (design guidance). Open point: the shared tooltip (components/js/tooltip.js) has no hover delay yet, so it appears at once. Never on a disabled button. |
| Topic | Requirement |
|---|---|
| Element | A native <button type="button"> for an action, an <a class="btn …"> for navigation. Never a div with a click handler. |
| Name | A visible label, or an aria-label on an icon-only button. A badge is aria-hidden="true": put the count in the aria-label. |
| Focus | 2px ring border-focus with a 2px gap, on keyboard use (WCAG 2.4.7, 1.4.11). It is set with body.keyboard-nav, never :focus-visible. |
| State | System selected is aria-pressed. A disabled button uses the disabled attribute. |
| Target size | The smallest button is 32px high; Close is 24px, which meets WCAG 2.5.8 (24px) but is below the usual 44px touch target. |
| Contrast | Choose standard or inverted variants by context, visibility and contrast, not by background color alone. Disabled tokens are exempt from contrast requirements. |