# Button

A native `<button>` element styled via class + data attributes from the optional [styles layer](/ui/styles). Six variants, four sizes, color theming, high-contrast and disabled states — all driven by data attributes on a single `.x-button` class.



## Install

```ts
import "elements-kit/ui/styles.css";

// import any color scales you want to use for color theming:
import "elements-kit/ui/styles/palette/mint.css";
import "elements-kit/ui/styles/accent/mint.css";
// import any gray scales you want to use for neutral theming:
import "elements-kit/ui/styles/palette/slate.css";
import "elements-kit/ui/styles/neutral/slate.css";
// and of course the button itself:
import "elements-kit/ui/button/button.css";
```

See [Styles](/ui/styles) for the full token system, accent/gray scale lists, and theming knobs.

## API

```html
<button
  class="unset x-button"
  data-variant="solid"
  data-size="2"
  data-accent="mint"
>
  Click me
</button>
```

The `unset` class ([Styles → Unset native styles](/ui/styles#unset-native-styles)) clears the browser's default `<button>` rendering so `.x-button` styles render predictably. Skip it and you'll see leaked native styling (gray background, system font, beveled border).

| Attribute                    | Values                                                                                                                                                                                                           |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-variant`               | `solid`, `soft`, `surface`, `outline`, `text`, `borderless`                                                                                                                                                      |
| `data-size`                  | `1`, `2`, `3`, `4`                                                                                                                                                                                               |
| `data-accent`                | any imported color scale (`mint`, `blue`, `iris`, …)                                                                                                                                                             |
| `data-icon`                  | modifier — square shape sized by height. Use for icon-only buttons.                                                                                                                                              |
| `data-layout`                | `stacked` — icon above the label; at least square, the label wraps when constrained; `stacked-icon` — the same without padding, the state on a highlight around the icon (see [Icon and label](#icon-and-label)) |
| `data-back`                  | modifier — a leading back chevron sized to the text (see [Back button](#back-button))                                                                                                                            |
| `data-high-contrast`         | modifier — boosts contrast against the page background                                                                                                                                                           |
| `data-loading`               | modifier — sets `position: relative` for a spinner overlay                                                                                                                                                       |
| `disabled` / `data-disabled` | native `:disabled` state, or `data-disabled` on non-button elements                                                                                                                                              |

## Sizing

Each size maps to a height token from the [space scale](/ui/styles#spacing):

| `data-size` | height             | radius                           |
| ----------- | ------------------ | -------------------------------- |
| `1`         | `--space-5` (24px) | `max(--radius-1, --radius-pill)` |
| `2`         | `--space-6` (32px) | `max(--radius-2, --radius-pill)` |
| `3`         | `--space-7` (40px) | `max(--radius-3, --radius-pill)` |
| `4`         | `--space-8` (48px) | `max(--radius-4, --radius-pill)` |

The radius respects `data-radius` on a parent — set `data-radius="pill"` on the page root for pill buttons everywhere.

## Variants

* **`solid`** — flat, color-driven fill. Confident primary action.
* **`soft`** — tinted, low-emphasis background. Pairs well with surfaces.
* **`surface`** — outlined panel with subtle fill. Borderline calls-to-action.
* **`outline`** — bordered, transparent fill. Tertiary actions.
* **`text`** — no background, no border, and uses negative margins so the button sits flush with surrounding body copy. Use for inline calls-to-action inside paragraphs.
* **`borderless`** — no background, no border, no margin trick. Standalone button without chrome. Toolbar-style.

## Icon button

Add `data-icon` to make the button a square sized by height — for icon-only buttons (toolbar actions, close, etc.). Works with every variant and every size.

```html
<button class="unset x-button" data-variant="soft" data-size="2" data-icon aria-label="Close">
  <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true">
    <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" fill="none" />
  </svg>
</button>
```

| Size | Shape |
| ---- | ----- |
| `1`  | 24×24 |
| `2`  | 32×32 |
| `3`  | 40×40 |
| `4`  | 48×48 |

For `data-variant="text"` icon buttons, the kit swaps the asymmetric padding for uniform square padding (4 / 6 / 8 / 12px per size) so the icon sits centered. The negative-margin trick still applies — the button inlines flush with surrounding text.

> **Don't**
>
> Ship an icon-only button without `aria-label` or visually-hidden text. Screen readers have nothing to read.

## Icon and label

Put an icon next to the label as a child; source order decides its side. Size it in `em` so it follows the button's font size.

Add `data-layout="stacked"` to put the icon above the label, as in action items. At size `2` the button is 56px tall, with a 24px icon, a 2px gap, and a medium-weight caption in `--font-size-0` on a 16px line, with at least 4px padding above and below and 10px on the sides — enough for a long caption to clear the rounded ends under a pill radius. The box, the icon and the caption's line all scale with the size, so the caption keeps its place; the caption steps up to `--font-size-1` at size `3`. The icon carries the meaning, so keep captions short.

The height and width are minimums: a short label gives a square, a longer one widens the button. When the button is constrained, the label wraps into balanced lines and the button grows taller, keeping the same padding around it. A single word too long for the width breaks.

| `data-size` | stacked minimum | icon | label                          |
| ----------- | --------------- | ---- | ------------------------------ |
| `1`         | 42px            | 18px | `--font-size-0` on a 12px line |
| `2`         | 56px            | 24px | `--font-size-0` on a 16px line |
| `3`         | 70px            | 30px | `--font-size-1` on a 20px line |
| `4`         | 84px            | 36px | `--font-size-1` on a 24px line |

`text` stacked buttons keep the kit's text padding (8px × 4px at size `2`), so they are at least 51px square and still bleed like other text buttons. For an on/off action with the same shape, use a [stacked toggle](/ui/toggle#stacked).

```html
<button class="unset x-button" data-variant="soft" data-size="2" data-layout="stacked">
  <svg viewBox="0 0 24 24" width="1.25em" height="1.25em" aria-hidden="true">…</svg>
  <span>Share</span>
</button>
```

### Icon highlight

`data-layout="stacked-icon"` drops the button's padding and paints the variant's fill, ring and filter on a highlight around the icon, not the whole button. At size `2` the highlight is a 36px square (the 24px icon with 6px around it) with the button's radius, and the label sits 4px below, so the button stays 56px square. Both scale with the size. A `solid` label, which has no fill behind it, takes the accent text color.

```html
<button class="unset x-button" data-variant="soft" data-size="2" data-layout="stacked-icon">
  <svg viewBox="0 0 24 24" width="1.25em" height="1.25em" aria-hidden="true">…</svg>
  <span>Share</span>
</button>
```

For a selectable item (a navigation rail or bar), use a [`stacked-icon` toggle](/ui/toggle#icon-indicator).

## Back button

Add `data-back` to a button whose first child is a back chevron, as in a navigation bar. The chevron is one text line tall, so it reads at the label's weight rather than as an icon. It sits right against the label.

| `data-size` | chevron |
| ----------- | ------- |
| `1`         | 16px    |
| `2`         | 20px    |
| `3`         | 24px    |
| `4`         | 26px    |

```html
<button class="unset x-button" data-variant="text" data-size="2" data-back>
  <svg viewBox="0 -960 960 960" aria-hidden="true">…</svg>
  Mailboxes
</button>
```

The chevron's box starts on the button's content edge. A `text` button bleeds by its padding, so the chevron lines up with a title or a list below it — as long as the glyph is drawn flush with its box. Material Symbols' `arrow_back_ios` is; `chevron_left` has empty space on its left and lands about a third of the box further in.

With `data-icon`, the button is chevron-only: it keeps its square and centers the chevron. Use a glyph drawn centered in its box, such as `arrow_back_ios_new`, and give the button an `aria-label`.

```html
<button class="unset x-button" data-variant="borderless" data-size="2" data-back data-icon aria-label="Back">
  <svg viewBox="0 -960 960 960" aria-hidden="true">…</svg>
</button>
```

In a right-to-left context (`dir="rtl"` on the button or any ancestor) the chevron is mirrored, so it points to the inline start.



## Use as a link

`.x-button` is class-only — apply it to any element you want shaped like a button. The most common case is a link that should look like a button:

```html
<a
  class="unset x-button"
  data-variant="solid"
  data-size="2"
  href="/getting-started/installation"
>
  Get started
</a>
```

`unset` zeroes the anchor's default underline and color so the `.x-button` styles render exactly like on a `<button>`. Native semantics (focus, navigation, right-click → open in new tab) are preserved — pick `<a>` when the action navigates and `<button>` when it triggers a JS handler.

## Radius

Set `data-radius="<value>"` on a parent (e.g. `<body>`) to control corner radius for every button beneath. Values follow [Styles → Radius](/ui/styles#radius):

```html
<div data-radius="pill">
  <button class="unset x-button" data-variant="solid">Pill</button>
</div>
<div data-radius="none">
  <button class="unset x-button" data-variant="solid">Square</button>
</div>
```

| `data-radius` | Effect                                  |
| ------------- | --------------------------------------- |
| `none`        | square corners — `--radius-factor: 0`   |
| `small`       | tighter — `--radius-factor: 0.75`       |
| `medium`      | default                                 |
| `large`       | softer — `--radius-factor: 1.5`         |
| `pill`        | fully rounded — `--radius-pill: 9999px` |

The size's own radius is `max(--radius-N, --radius-pill)`, so `data-radius="pill"` always wins regardless of size.

## Theming

Switch the color on a single button with `data-accent="<color>"`, or set it on any ancestor (e.g. `<body>`) to theme the whole subtree.

```html
<body data-accent="iris" data-radius="large">
  <button class="unset x-button" data-variant="solid" data-size="3">
    Themed primary
  </button>
</body>
```

Light/dark flips automatically via the `.dark` class on a parent — see [Light & dark](/ui/styles#light--dark).
