Skip to content

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).

AttributeValues
data-variantsolid, soft, surface, outline, text, borderless
data-size1, 2, 3, 4
data-accentany imported color scale (mint, blue, iris, …)
data-iconmodifier — square shape sized by height. Use for icon-only buttons.
data-layoutstacked — 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-backmodifier — a leading back chevron sized to the text (see Back button)
data-high-contrastmodifier — boosts contrast against the page background
data-loadingmodifier — sets position: relative for a spinner overlay
disabled / data-disablednative :disabled state, or data-disabled on non-button elements

Sizing

Each size maps to a height token from the space scale:

data-sizeheightradius
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>
SizeShape
124×24
232×32
340×40
448×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-sizestacked minimumiconlabel
142px18px--font-size-0 on a 12px line
256px24px--font-size-0 on a 16px line
370px30px--font-size-1 on a 20px line
484px36px--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-sizechevron
116px
220px
324px
426px
<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.

.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-radiusEffect
nonesquare corners — --radius-factor: 0
smalltighter — --radius-factor: 0.75
mediumdefault
largesofter — --radius-factor: 1.5
pillfully 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.