On this page
ButtonExample.tsx
import { Button } from "@/components/Button";
import { Stack } from "@/components/Stack";
export function ButtonExample() {
return (
<Stack direction="horizontal" gap="sm">
<Button variant="primary">Save changes</Button>
<Button>Cancel</Button>
</Stack>
);
}Examples
Variants
Sizes
Icons
Icon-only
sm · aria-labelmd · aria-labelsm · VisuallyHiddenmd · VisuallyHidden
Toggles
Announced as: “Show grid, toggle button, pressed”
Announced as: “Wrap lines, toggle button, not pressed”
Announced as: “Mark as done, toggle button, not pressed”
Announced as: “Notify me, toggle button, pressed”
Disabled (still focusable)
Your last note is still sending
Long label
Customized with a component token
CSS
.brandCta {
--component-button-primary-background: var(--color-accent-2-fill);
--component-button-primary-background-hover: var(--color-accent-2-fill-hover);
--component-button-primary-background-active: var(--color-accent-2-fill-hover);
--component-button-primary-border: var(--color-accent-2-fill);
--component-button-primary-text: var(--color-accent-2-fill-text);
--component-button-primary-text-active: var(--color-accent-2-fill-text);
}Props
Any native prop not listed here goes to the root element unchanged.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | "ghost" | "secondary" | Emphasis: one primary action per view, secondary for the rest, ghost for toolbars. |
size | "sm" | "md" | "md" | 32px or 40px tall. |
iconStart | IconName | — | Decorative icon before the label. |
iconEnd | IconName | — | Decorative icon after the label. |
type | "button" | "submit" | "reset" | "button" | Defaults to "button", not the native "submit". |
disabled | boolean | false | Sets aria-disabled, not the native attribute: still focusable, announced, and never activated. |
aria-pressed | boolean | "mixed" | — | Makes it a toggle button. "mixed" has no look of its own. |
disabledReason | ReactNode | — | While disabled: why. It's the button's accessible description, and shows as a tooltip on hover, keyboard focus, and a press or tap. |
nameTooltip | boolean | true | Icon-only buttons named by aria-label or VisuallyHidden show that name as a tooltip; false turns it off. |
Component tokens
| Token | Copy | Description | Aliases | Light | Dark | Preview |
|---|---|---|---|---|---|---|
--component-button-radius | Corner radius. | radius.control | 4pxfrom radius.4 | 4pxfrom radius.4 | ||
--component-button-font-family | Label font. | type.family.body | 'Inter Variable', 'Inter Fallback', system-ui, -apple-system, 'Segoe UI', Roboto, sans-seriffrom type.font-stack.sans | 'Inter Variable', 'Inter Fallback', system-ui, -apple-system, 'Segoe UI', Roboto, sans-seriffrom type.font-stack.sans | ||
--component-button-font-weight | Label weight. | type.weight.strong | 600from type.font-weight.600 | 600from type.font-weight.600 | ||
--component-button-gap | Gap between the label and its icons. | space.inline.sm | 0.5remfrom space.2 | 0.5remfrom space.2 | ||
--component-button-line-height | Line height of the label. | type.leading.heading | 1.2from type.line-height.tight | 1.2from type.line-height.tight | ||
--component-button-padding-block | Block padding. It only shows once a label wraps; a single line stays exactly the height token. | space.stack.xs | 0.25remfrom space.1 | 0.25remfrom space.1 | ||
--component-button-height-sm | Block size of small buttons (32px, above the 24px target minimum). | size.control.sm | 2remfrom size.8 | 2remfrom size.8 | ||
--component-button-height-md | Block size of standard buttons (40px). | size.control.md | 2.5remfrom size.10 | 2.5remfrom size.10 | ||
--component-button-padding-inline-sm | Inline padding of small buttons. | space.inline.sm | 0.5remfrom space.2 | 0.5remfrom space.2 | ||
--component-button-padding-inline-md | Inline padding of standard buttons. | space.inline.md | 1remfrom space.4 | 1remfrom space.4 | ||
--component-button-font-size-sm | Label size of small buttons. | type.size.small | 0.875remfrom type.font-size.sm | 0.875remfrom type.font-size.sm | ||
--component-button-font-size-md | Label size of standard buttons. | type.size.body | 1remfrom type.font-size.md | 1remfrom type.font-size.md | ||
--component-button-border-width | Border width. | effect.border.width | 2pxfrom effect.line.2 | 2pxfrom effect.line.2 | ||
--component-button-focus-ring-color | Focus indicator. It's drawn outside the button, so it's checked against the surfaces. | color.focus.ring | #005f7bfrom color.cyan.700 | #54d8f4from color.cyan.300 | ||
--component-button-transition-duration | Duration of hover color changes (0ms under reduced motion). | motion.duration.instant | 100msfrom motion.time.100Reduced motion: 0ms | 100msfrom motion.time.100 | No preview | |
--component-button-transition-easing | Easing of hover color changes. | motion.easing.standard | cubic-bezier(0.4, 0, 0.2, 1)from motion.curve.in-out | cubic-bezier(0.4, 0, 0.2, 1)from motion.curve.in-out | No preview | |
--component-button-primary-background | Primary background. | color.action.background | #b37500from color.gold.600 | #f5e3c2from color.champagne.100 | ||
--component-button-primary-background-hover | Primary background on hover. | color.action.background-hover | #c58800from color.gold.500 | #fff5e2from color.champagne.50 | ||
--component-button-primary-background-active | Primary background while pressed (:active): a darker gold than the resting fill. | color.action.background-pressed | #795616from color.gold.700 | #b2a181from color.champagne.600 | ||
--component-button-primary-text | Primary text and icon color. | color.action.text | #0b0c0efrom color.neutral.950 | #0b0c0efrom color.neutral.950 | ||
--component-button-primary-text-active | Primary text and icon color while pressed (:active), on the darker fill. | color.action.text-pressed | #fbf8f2from color.sand.50 | #0b0c0efrom color.neutral.950 | ||
--component-button-primary-border | Primary border color. | color.action.background | #b37500from color.gold.600 | #f5e3c2from color.champagne.100 | ||
--component-button-primary-pressed-background | Primary toggle background while pressed (aria-pressed="true"): the darker gold of a pressed primary. It differs from the resting fill by lightness only, so a toggle whose state must read clearly also changes its label or shows a check icon. | color.action.background-pressed | #795616from color.gold.700 | #b2a181from color.champagne.600 | ||
--component-button-primary-pressed-background-hover | Primary pressed toggle background on hover and while pressing: it stays the darker gold. | color.action.background-pressed | #795616from color.gold.700 | #b2a181from color.champagne.600 | ||
--component-button-primary-pressed-text | Primary pressed toggle text and icon color. | color.action.text-pressed | #fbf8f2from color.sand.50 | #0b0c0efrom color.neutral.950 | ||
--component-button-primary-pressed-border | Primary pressed toggle border color: the pressed fill, which is the toggle's boundary (3:1 against the surfaces). | color.action.background-pressed | #795616from color.gold.700 | #b2a181from color.champagne.600 | ||
--component-button-secondary-background | Secondary background. | color.surface.raised | #fffffffrom color.gray.0 | #18202efrom color.slate.900 | ||
--component-button-secondary-background-hover | Secondary background on hover. | color.surface.sunken | #f2ede2from color.sand.100 | #060a13from color.slate.1000 | ||
--component-button-secondary-background-active | Secondary background while pressed (:active). | color.surface.sunken | #f2ede2from color.sand.100 | #060a13from color.slate.1000 | ||
--component-button-secondary-text | Secondary text and icon color. | color.text.default | #17202bfrom color.gray.900 | #fffffffrom color.gray.0 | ||
--component-button-secondary-border | Secondary border color. | color.border.strong | #6d7e90from color.gray.500 | #818b9efrom color.slate.400 | ||
--component-button-secondary-pressed-background | Secondary toggle background while pressed (aria-pressed="true"). | color.action.background | #b37500from color.gold.600 | #f5e3c2from color.champagne.100 | ||
--component-button-secondary-pressed-background-hover | Secondary pressed toggle background on hover and while pressing. | color.action.background-hover | #c58800from color.gold.500 | #fff5e2from color.champagne.50 | ||
--component-button-secondary-pressed-text | Secondary pressed toggle text and icon color. | color.action.text | #0b0c0efrom color.neutral.950 | #0b0c0efrom color.neutral.950 | ||
--component-button-secondary-pressed-border | Secondary pressed toggle border color. | color.action.background | #b37500from color.gold.600 | #f5e3c2from color.champagne.100 | ||
--component-button-ghost-background | Ghost background. | color.fill.none | rgba(0, 0, 0, 0)from color.overlay.transparent | rgba(0, 0, 0, 0)from color.overlay.transparent | ||
--component-button-ghost-background-hover | Ghost background on hover. | color.surface.sunken | #f2ede2from color.sand.100 | #060a13from color.slate.1000 | ||
--component-button-ghost-background-active | Ghost background while pressed (:active). | color.surface.sunken | #f2ede2from color.sand.100 | #060a13from color.slate.1000 | ||
--component-button-ghost-text | Ghost text and icon color. | color.text.default | #17202bfrom color.gray.900 | #fffffffrom color.gray.0 | ||
--component-button-ghost-border | Ghost border color. | color.fill.none | rgba(0, 0, 0, 0)from color.overlay.transparent | rgba(0, 0, 0, 0)from color.overlay.transparent | ||
--component-button-ghost-pressed-background | Ghost toggle background while pressed (aria-pressed="true"). | color.action.background | #b37500from color.gold.600 | #f5e3c2from color.champagne.100 | ||
--component-button-ghost-pressed-background-hover | Ghost pressed toggle background on hover and while pressing. | color.action.background-hover | #c58800from color.gold.500 | #fff5e2from color.champagne.50 | ||
--component-button-ghost-pressed-text | Ghost pressed toggle text and icon color. | color.action.text | #0b0c0efrom color.neutral.950 | #0b0c0efrom color.neutral.950 | ||
--component-button-ghost-pressed-border | Ghost pressed toggle border color. | color.action.background | #b37500from color.gold.600 | #f5e3c2from color.champagne.100 | ||
--component-button-disabled-background | Background while disabled. | color.surface.sunken | #f2ede2from color.sand.100 | #060a13from color.slate.1000 | ||
--component-button-disabled-text | Text while disabled. Exempt from contrast minimums (WCAG 1.4.3), but kept at 4.5:1 anyway. | color.text.subtle | #5d6876from color.gray.600 | #d3dce6from color.slate.200 | ||
--component-button-disabled-border | Border while disabled. | color.border.subtle | #e5dccbfrom color.sand.200 | #272f3ffrom color.slate.800 |
Accessibility
- Always a native <button>, type="button" unless set.
- Named by its text. Icon-only buttons need an icon plus aria-label or aria-labelledby (a type error otherwise), or a VisuallyHidden label. With no visible text they're square.
- aria-pressed makes a toggle on any variant: the label stays the same and the pressed look shows the state. A button whose label changes (“Show grid” ↔ “Hide grid”) is a plain button, without aria-pressed.
- A toggled-on primary is a darker gold, so its on and off looks differ by lightness alone. Where the state must read more clearly, show a check icon while it's on, or use a plain button whose label changes.
- disabled sets aria-disabled, not the native attribute: it stays focusable and announced, and blocks clicks, keys and form submission.
- A disabledReason is the disabled button's accessible description, and shows as a tooltip on hover, keyboard focus, and a press or tap (the first Disabled example).
- Icon-only buttons show their name as a tooltip on hover and keyboard focus, for sighted users who don't know the icon. It isn't announced again. Not for buttons named by aria-labelledby, whose name is already on screen.
- In forced colors, disabled shows GrayText and pressed shows Highlight, since the forced palette drops their background and border colors.
- Target size at least 32px tall; focus ring 2px in component.button.focus-ring-color, 3:1 against the surface.
| Key | Action |
|---|---|
| Enter / Space | Activates the button, or flips a toggle. While disabled it does neither, and shows the disabledReason if there is one. |
| Tab | Moves focus to it, including when disabled. |