# Button

## Overview

The button component is a clickable control for actions such as submitting a form, confirming a choice, or triggering a UI change.

The component styles a native `<button>` element directly. It also provides the `.button` class for native links that navigate somewhere but need button-like visual prominence.

## When to use

Use a button to submit a form, confirm a choice, or trigger an immediate action on the current page.

## When not to use

Do not use a `<button>` to navigate to another destination or present a row of destination choices. Use a native link, optionally with the `.button` class for a prominent call to action, or use nav for a set of destination choices.

## Usage

```html
<button type="button">Primary Button</button>
```

For a standalone navigational call to action outside prose, apply `.button` to an anchor with a valid `href`:

```html
<a class="button" href="/applications/123">View application details</a>
```

The class changes only the visual treatment. The anchor remains a link: browsers and assistive technology identify it as a link, Enter activates it, and link features such as opening in a new tab remain available. Do not add `role="button"`, a Space-key handler, or JavaScript that imitates button behavior. Use destination-oriented link text that communicates the purpose when read out of context. Keep inline prose links unstyled, and let links owned by navigation, breadcrumbs, headings, headers, and footers use those components' link treatments rather than `.button`.

```html
<button type="button" style="--button-border-radius: 999px;">Rounded Button</button>
```

For an action that should stay visually distinct from the primary fill, use the `outline` variant. It uses a transparent surface and a visible border. Combine it with any color variant—`error`, `info`, `success`, or `warning`—using `data-variant` when the action needs that variant color treatment:

```html
<button class="outline" type="button">Outline Button</button>
```

For a lower-emphasis action that should read as lighter-weight while keeping button affordances, use the `flat` variant. It removes the border and background. Combine it with any color variant—`error`, `info`, `success`, or `warning`—using `data-variant` when the action needs that variant color treatment:

```html
<button class="flat" type="button">Flat Button</button>
```

The deprecated `secondary` and `tertiary` classes remain exact aliases for `outline` and `flat` respectively. Use the public variant names for new markup.

Color variants communicate an action's semantic purpose. Use `data-variant="error"`, `data-variant="info"`, `data-variant="success"`, or `data-variant="warning"`. Each color variant applies its color scale to the button's resting, hover, and pressed states while preserving the selected button style variant.

The `.error` color class remains supported for compatibility but is deprecated. Use `data-variant="error"` for new markup. If both are present, `data-variant` takes precedence. Deprecated features will be removed in v1.

Use the `error` color variant for destructive actions such as deleting a record or removing access:

```html
<button data-variant="error" type="button">Delete Item</button>
```

Use the `info` color variant for actions associated with informational content or status:

```html
<button data-variant="info" type="button">View status</button>
```

Use the `success` color variant for actions that approve or complete something successfully:

```html
<button data-variant="success" type="button">Approve request</button>
```

Use the `warning` color variant for actions that need caution or review before proceeding:

```html
<button data-variant="warning" type="button">Review warning</button>
```

For related actions that should read as one compact control, place the buttons directly inside a named element with `role="group"`. Direct-child buttons and button-styled links in any `[role="group"]` keep their dimensions when hovered instead of expanding horizontally. Use a `fieldset` when the group needs a native legend:

```html
<fieldset role="group" aria-label="View range">
  <button class="outline" type="button">Day</button>
  <button type="button">Week</button>
  <button class="outline" type="button">Month</button>
</fieldset>
```

Buttons marked with `aria-active="true"` use the primary filled treatment:

```html
<button aria-active="true" type="button">Active View</button>
```

When a button includes an icon, use the Material Symbols icon font through the shared `.icon` element pattern:

```html
<button type="button">
  <span class="icon" aria-hidden="true">add</span>
  <span>Create Item</span>
</button>
```

For icon-only buttons, apply the `.icon` class directly to the button and provide an accessible name:

```html
<button class="icon" type="button" aria-label="Add item">add</button>
```

For icon-only navigation, combine `.button` and `.icon` on a native link and provide an accessible name:

```html
<a class="button icon" href="/applications/123" aria-label="View application details">arrow_forward</a>
```

For buttons with a loading indicator, use `aria-busy="true"` and keep an action-specific visible label. Add `disabled` when the action must not be triggered again while it is processing:

```html
<button disabled aria-busy="true" type="button">Saving…</button>
```

Button loaders render at `75%` of `--button-icon-size`. Typical SVG icons include internal padding within their view box, while the CSS loading ring fills its entire box; the reduction gives both a comparable optical size. Keeping the loader derived from the icon token also preserves consistent button dimensions between busy buttons and the icon button variant when the shared icon size changes.

When space is constrained, an empty busy button centers the loading indicator. Provide its action-specific accessible name with `aria-label`:

```html
<button disabled aria-busy="true" type="button" aria-label="Saving report"></button>
```

Use this component when the user needs to trigger an immediate action.

Use a link when the interaction navigates to another page or location. Add `.button` to standalone links outside prose; keep inline and component-owned navigation links in their normal link treatment.

Use a navigation pattern instead when the interface needs a row of destination choices such as section links or a primary page bar, even if the visual treatment looks similar to grouped controls.

Button text should describe the action clearly and work without relying on surrounding context.

The button should use a native `<button>` element so it keeps keyboard behavior, disabled state support, and expected form interactions.

## Behavior

Covered by [Playwright behavior tests](../../tests/e2e/forms-inputs.spec.js).

- BTN-001: The button should size from its content, padding, and border radius rather than a fixed width.
- BTN-002: Buttons should reserve a `0.125rem` border in every variant so switching visual treatments does not change the outer size.
- BTN-003: Buttons should use the shared control block padding and minimum height so they align with single-line inputs and selects across layout densities.
- BTN-004: Buttons can override the shared control radius with `--button-border-radius` when an action needs a different corner treatment.
- BTN-005: Enabled buttons should show the pointer cursor to reinforce that the control is clickable, while disabled buttons should not.
- BTN-006: Default buttons should use the subtle primary color for both surface and border on hover and focus-visible, with the default primary color for text in light themes and the accessible accent foreground in dark themes, then move to the strong primary tone when pressed.
- BTN-007: Button text should either match the button's visible surface or stroke color, or use the inverse text color when the button is filled.
- BTN-008: Outline buttons should keep a visible default-color stroke with a matching label until hovered, use the subtle primary color for both surface and border with the default primary color for text on hover, and use the same strong pressed fill and inverse label as the primary button; dark themes use the accessible accent foreground for outline labels.
- BTN-009: Flat buttons should stay borderless with the default color by default, show a subtle primary stroke on hover, and keep the default label on the subtle primary pressed fill; dark themes use the accessible accent foreground for flat labels.
- BTN-010: Button color changes should transition smoothly, with reduced-motion preferences disabling the transition.
- BTN-011: Focus-visible should use each variant's hover treatment and add a visible ring.
- BTN-012: Error buttons should use the error default color at rest, the subtle error color scale in each style variant's hover treatment, and each style variant's pressed or activated treatment: default and outline buttons use the strong error fill with inverse text, while flat buttons use the subtle error fill with default error text.
- BTN-013: Buttons with `aria-active="true"` should use the primary filled treatment so the active choice reads with stronger emphasis.
- BTN-014: Disabled buttons should retain gray disabled styling across hover, focus-visible, and active states; disabled outline buttons should keep a visible neutral stroke.
- BTN-015: Buttons with icons should keep the icon inline with the label and adjust spacing without requiring a separate variant class.
- BTN-016: Busy buttons should keep disabled visual styling when disabled, use the wait cursor, and space the generated loading indicator consistently with an inline icon.
- BTN-017: Icon buttons should derive block padding from the shared control height and icon size so their larger font line box aligns with text controls; icon-only buttons remain square, center the glyph, and keep an accessible name through `aria-label` when no visible text is present.
- BTN-018: Buttons should not be used as stand-ins for destination navigation just because the interface groups them horizontally.
- BTN-019: Empty busy buttons should remain square, center the generated loading indicator, and require an accessible name such as `aria-label`.
- BTN-020: An anchor with `.button` should receive the button visual treatment while retaining native link semantics and Enter-only keyboard activation.
- BTN-021: An icon-only anchor with `.button.icon` should remain square, match the shared control height across layout densities, and retain the foreground color of its button variant.
- BTN-022: Buttons and button-styled links should not expand on hover, including when they are direct children of any `[role="group"]`.
- BTN-023: The deprecated `secondary` and `tertiary` classes should remain exact visual aliases for `outline` and `flat`.
- BTN-024: `data-variant="error"` should compose with outline and flat variants without adding variant-specific structure: outline error buttons are transparent with an error default-color outline, and flat error buttons remain borderless.
- BTN-025: Disabled buttons should keep their disabled colors when hovered, focused, or pressed, including when a disabled button also has a variant or error modifier.
- BTN-026: `data-variant` variant colors (`error`, `info`, `success`, and `warning`) should use their variant color scales for filled, outline, and flat buttons across resting, hover, and pressed states.
- BTN-027: In dark themes, filled `data-variant` buttons should use distinct dark-theme-specific variant background mixes that maintain sufficient contrast with inverse text, while outline and flat variants retain their variant color identities.
## Accessibility

- Use a native `<button>` for actions so keyboard and assistive technology behavior stays intact.
- Use `<a class="button" href="…">` for standalone navigation outside prose. Keep a valid `href`, omit `role="button"`, and do not add button keyboard behavior; the class changes appearance, not semantics.
- Give icon-only links an accessible name with `aria-label`.
- Make link text describe its destination or purpose clearly without relying on surrounding context.
- Provide an explicit `type` when the button appears in a form so submit behavior is intentional.
- Keep the visible label descriptive enough to stand on its own.
- Mark decorative icons with `aria-hidden="true"` so they do not add noise to the accessible name.
- Use `aria-busy="true"` only while an action is processing, and remove it when the action finishes.
- Pair `aria-busy="true"` with `disabled` when repeated activation must be prevented; `aria-busy` does not disable the button by itself.
- Do not rely on color alone to communicate disabled or pressed state.

## Variables

| Variable | Type | Description |
| --- | --- | --- |
| `--button-action-padding-block` | `<length>` | Sets --button-action-padding-block so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-action-padding-inline` | `<length>` | Sets --button-action-padding-inline so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-active-background` | `<color>` | Sets the pressed or active fill color; change it when active buttons need a stronger, weaker, or variant-specific state surface. |
| `--button-active-border-color` | `<color>` | Sets the pressed or active border color; change it with the active background when the button outline should track state changes. |
| `--button-active-color` | `<color>` | Sets the pressed or active label and icon color; change it when the active surface needs a different contrast color. |
| `--button-background` | `<color>` | Sets the resting fill color for filled button variants; change it to create primary, danger, or contextual button treatments. |
| `--button-border-color` | `<color>` | Sets the resting border color; change it for outlined variants or when the button edge needs different emphasis from the fill. |
| `--button-border-radius` | `<length>` | Sets the button corner radius and grouped-control radius; change it to make buttons more rounded, squared, or search-pill shaped. |
| `--button-border-width` | `<length>` | Sets the button stroke width and contributes to computed height; change it when buttons need a heavier or lighter outline. |
| `--button-color` | `<color>` | Sets the default color for the button's state palette; change it for a semantic action color scale. |
| `--button-color-focus-ring` | `<color>` | Sets --button-color-focus-ring so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-color-strong` | `<color>` | Sets the strong color for the button's pressed state; change it with the default palette color. |
| `--button-color-subtle` | `<color>` | Sets the subtle color for the button's hover or low-emphasis state; change it with the default palette color. |
| `--button-color-text` | `<color>` | Sets the resting label and icon color; change it when the button background or border treatment requires a different contrast color. |
| `--button-color-text-inverse` | `<color>` | Sets the inverse text color used on filled and pressed button surfaces. |
| `--button-control-gap` | `<length>` | Sets --button-control-gap so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-control-radius` | `<length>` | Sets --button-control-radius so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-disabled-background` | `<color>` | Sets the disabled fill color; change it when unavailable actions should appear more subtle or keep a transparent surface. |
| `--button-disabled-border-color` | `<color>` | Sets the disabled border color; change it when disabled outlined buttons need a visible but noninteractive edge. |
| `--button-disabled-color` | `<color>` | Sets the disabled label and icon color; change it to keep disabled text legible while still reading inactive. |
| `--button-focus-ring-size` | `<length>` | Sets --button-focus-ring-size so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-font-family-sans` | `Any` | Sets --button-font-family-sans so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-font-size` | `<length>` | Sets --button-font-size so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-height` | `<length>` | Sets the minimum button height after padding, line-height, and border space are combined; change it to tune control density. |
| `--button-hover-background` | `<color>` | Sets the hover fill color; change it when pointer hover should preview a stronger or subtler interaction state. |
| `--button-hover-border-color` | `<color>` | Sets the hover border color; change it when outlined or filled buttons need a distinct pointer affordance. |
| `--button-hover-color` | `<color>` | Sets the hover label and icon color; change it when the hover surface needs a different contrast pairing. |
| `--button-icon-fill` | `<number>` | Sets --button-icon-fill so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-icon-font-family` | `Any` | Sets --button-icon-font-family so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-icon-grad` | `<number>` | Sets --button-icon-grad so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-icon-opsz` | `<number>` | Sets --button-icon-opsz so the component can default to shared theme or layout tokens while exposing an overrideable component API. |
| `--button-icon-padding-block` | `<length>` | Sets the block padding around button icons so their larger font line box fits within the shared control height. |
| `--button-icon-size` | `<length>` | Sets the font size for icons and busy indicators in buttons; change it when icon glyphs should differ from the control text scale. |
| `--button-icon-wght` | `<number>` | Sets --button-icon-wght so the component can default to shared theme or layout tokens while exposing an overrideable component API. |

## Depends On

- `--color-accent-disabled`
- `--color-border`
- `--color-error`
- `--color-error-strong`
- `--color-error-subtle`
- `--color-focus-ring`
- `--color-info`
- `--color-info-strong`
- `--color-info-subtle`
- `--color-primary`
- `--color-primary-strong`
- `--color-primary-subtle`
- `--color-success`
- `--color-success-strong`
- `--color-success-subtle`
- `--color-text-accent`
- `--color-text-inverse`
- `--color-text-subtle`
- `--color-warning`
- `--color-warning-strong`
- `--color-warning-subtle`
- `--font-family-sans`
- `--font-line-height-body`
- `--font-size`
- `--icon-fill`
- `--icon-font-family`
- `--icon-grad`
- `--icon-opsz`
- `--icon-wght`
- `--layout-action-padding-inline`
- `--layout-control-border-width`
- `--layout-control-gap`
- `--layout-control-icon-size`
- `--layout-control-min-height`
- `--layout-control-padding-block`
- `--layout-control-radius`
- `--layout-focus-ring-size`
- [Loading](./loading.md)
