Button
A native <button> element styled via class + data attributes from the optional styles layer. Six variants, four sizes, color theming, high-contrast and disabled states — all driven by data attributes on a single .x-button class.
Install
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 for the full token system, accent/gray scale lists, and theming knobs.
API
<button class="unset x-button" data-variant="solid" data-size="2" data-accent="mint"> Click me</button>The unset class (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) |
data-back | modifier — a leading back chevron sized to the text (see 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:
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.
<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.
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.
<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.
<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.
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 |
<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.
<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:
<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:
<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.
<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.