Button
Action or navigation invoker
Intent
Button is the action atom: submit a form, open a dialog, remove
a row, trigger a side effect. Render as <button>
for actions (anything that mutates state or invokes a widget),
as <a class="button"> for navigation that
visually reads as a button (a primary CTA linking to another
page, a download link). Don't invert the pair: an anchor
that performs an action, or a button that navigates, confuses
assistive tech and the browser's default behavior.
Paired with the command and commandfor
attributes, buttons invoke native overlays without JavaScript.
See
dialog
and drawer
for live invoker examples; the button itself is just a button
in that pattern.
Basic usage
A bare .button renders as a filled primary action
-- the most visually prominent variant, meant for a page's
single primary action. Everything else (outlined, ghost, icon,
block) comes from modifiers.
<button class="button" type="button">Save changes</button>Variants
Color and emphasis
Emphasis levels cover the common action hierarchy. Use
primary (the default, no modifier) for a page's
main action; .is-outlined for a
less-prominent companion; .is-accent
for a brand-accented alternate; .is-danger
for destructive actions; .is-ghost
for toolbar-level actions that shouldn't pull visual weight;
.is-bare when the button wraps
custom content (an avatar, an icon widget) and needs no chrome.
<div class="cluster is-tight">
<button class="button" type="button">Primary</button>
<button class="button is-neutral" type="button">Neutral</button>
<button class="button is-accent" type="button">Accent</button>
<button class="button is-danger" type="button">Danger</button>
<button class="button is-ghost" type="button">Ghost</button>
<button class="button is-bare" type="button">Bare</button>
</div>
<div class="cluster is-tight">
<button class="button is-outlined" type="button">Outlined</button>
<button class="button is-neutral is-outlined" type="button">Neutral (outlined)</button>
<button class="button is-accent is-outlined" type="button">Accent (outlined)</button>
<button class="button is-danger is-outlined" type="button">Danger (outlined)</button>
</div>
Hover darkens the fill, :active inverts the
shadow inward for a pressed look, and
:focus-visible draws the ring.
Size
Sizes key to the ui-pad-* density grammar.
.is-sm tightens padding and drops the font one
step; .is-lg relaxes padding and steps the font
up.
<div class="cluster is-tight">
<button class="button is-sm" type="button">Small</button>
<button class="button" type="button">Default</button>
<button class="button is-lg" type="button">Large</button>
</div>Shape
Shape modes beyond the default text-plus-optional-icon row:
.as-icon for icon-only buttons (equal padding on
both axes via the ui-pad-square-* grammar), and
.as-block for full-width buttons (form submit
actions, modal-footer primaries).
.as-icon requires aria-label -- the
icon alone has no accessible name.
<div class="stack is-snug">
<div class="cluster is-tight">
<button class="button" type="button">Default shape</button>
<button class="button is-outlined as-icon" type="button" aria-label="Settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
</button>
</div>
<button class="button as-block" type="button">Full-width action</button>
</div>States
Interactive state binds to native or ARIA attributes, not
classes: :disabled (or [aria-disabled="true"]
on non-button invokers) for unavailable actions, and
[aria-busy="true"] for pending actions. The busy
state renders a CSS spinner via ::before without
changing the button's width, so the layout doesn't jump as the
action progresses.
<div class="cluster is-tight">
<button class="button" type="button" disabled="">Disabled</button>
<button class="button is-outlined" type="button" disabled="">Disabled outlined</button>
<button class="button" type="button" aria-busy="true">Saving</button>
<button class="button is-outlined" type="button" aria-busy="true">Saving</button>
<button class="button is-danger" type="button" aria-busy="true">Deleting</button>
<button class="button is-danger is-outlined" type="button" aria-busy="true">Deleting</button>
</div>
Pick by meaning: disabled means "this action cannot
be taken right now"; aria-busy means "this action
has been taken and is in progress". Using disabled
during a save is technically available but semantically wrong
-- the user is waiting on a response, not being refused.
prefers-reduced-motion slows the spinner rotation
so it stays visible but isn't disorienting.
Composed patterns
Buttons with icons
Drop an SVG as a sibling of the label text. The button's own
gap spaces them; the SVG sizes itself via
--button-icon-size (falls back to
--icon-size). No .button-icon slot
class -- the SVG sits where it's authored.
<div class="cluster is-tight">
<button class="button is-outlined" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Download
</button>
<button class="button" type="button">
Continue
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</div>Button group
For tightly-coupled action sets -- segmented controls, view
switchers, mode pickers -- .button-group joins
its .button children edge-to-edge into one visual
unit. Adjacent borders collapse into a shared edge, outer
corners keep their radius, and the selected member binds to
[aria-pressed="true"] so the chosen visual
follows the announced state directly.
<div class="stack is-snug">
<div class="cluster is-tight">
<div class="button-group">
<button class="button" type="button">Bold</button>
<button class="button" type="button" aria-pressed="true">Italic</button>
<button class="button" type="button">Underline</button>
</div>
<div class="button-group">
<button class="button is-neutral" type="button">Bold</button>
<button class="button is-neutral" type="button" aria-pressed="true">Italic</button>
<button class="button is-neutral" type="button">Underline</button>
</div>
</div>
<div class="cluster is-tight">
<div class="button-group">
<button class="button is-accent" type="button">Bold</button>
<button class="button is-accent" type="button" aria-pressed="true">Italic</button>
<button class="button is-accent" type="button">Underline</button>
</div>
<div class="button-group">
<button class="button is-danger" type="button">Bold</button>
<button class="button is-danger" type="button" aria-pressed="true">Italic</button>
<button class="button is-danger" type="button">Underline</button>
</div>
</div>
<div class="cluster is-tight">
<div class="button-group">
<button class="button is-outlined" type="button">Bold</button>
<button class="button is-outlined" type="button" aria-pressed="true">Italic</button>
<button class="button is-outlined" type="button">Underline</button>
</div>
<div class="button-group">
<button class="button is-neutral is-outlined" type="button">Bold</button>
<button class="button is-neutral is-outlined" type="button" aria-pressed="true">Italic</button>
<button class="button is-neutral is-outlined" type="button">Underline</button>
</div>
</div>
<div class="cluster is-tight">
<div class="button-group">
<button class="button is-accent is-outlined" type="button">Bold</button>
<button class="button is-accent is-outlined" type="button" aria-pressed="true">Italic</button>
<button class="button is-accent is-outlined" type="button">Underline</button>
</div>
<div class="button-group">
<button class="button is-danger is-outlined" type="button">Bold</button>
<button class="button is-danger is-outlined" type="button" aria-pressed="true">Italic</button>
<button class="button is-danger is-outlined" type="button">Underline</button>
</div>
</div>
<div class="button-group">
<button class="button" type="button">Foo</button>
<button class="button" type="button">Bar</button>
<button class="button" type="button">Baz</button>
<button class="button" type="button">Qux</button>
</div>
<div class="button-group">
<button class="button is-sm" type="button">Foo</button>
<button class="button is-sm" type="button">Bar</button>
<button class="button is-sm" type="button">Baz</button>
<button class="button is-sm" type="button">Qux</button>
</div>
<div class="button-group">
<button class="button" type="button">Foo</button>
<button class="button" type="button" aria-pressed="true">Bar</button>
<button class="button" type="button">Baz</button>
<button class="button" type="button">Qux</button>
</div>
<div class="button-group">
<button class="button is-outlined" type="button">Foo</button>
<button class="button is-outlined" type="button" aria-pressed="true">Bar</button>
<button class="button is-outlined" type="button">Baz</button>
<button class="button is-outlined" type="button">Qux</button>
</div>
</div>The pressed member sits on a tinted-light fill of its own family with text that has guaranteed contrast against it. On solid (non-outlined) members, a family-color border separates the lightened pressed fill from the solid neighbors so the selection reads as a deliberate edge rather than just a lightness drop. Outlined members already carry a tinted border and don't need the extra emphasis.
Button cluster
For independent actions sitting together -- cancel-plus-confirm
pairs, multi-step wizards, modal footer actions -- use
.cluster.is-tight.
Cluster spaces buttons with a fluid gap and wraps them
gracefully on narrow viewports; reach for it when the buttons
aren't a single segmented unit.
<div class="cluster is-tight">
<button class="button is-outlined" type="button">Cancel</button>
<button class="button" type="button">Save</button>
</div>Class reference
.button-- the atom. Render as<button>for actions,<a class="button">for navigation..is-outlined,.is-accent,.is-danger,.is-ghost,.is-bare-- emphasis variants. Default (no modifier) is primary..is-sm,.is-lg-- size variants..as-icon-- icon-only shape (square padding). Requiresaria-label..as-block-- full-width shape..button-group-- joins.buttonchildren edge-to-edge into a segmented set. Selected member binds to[aria-pressed="true"]and picks up--color-state-bg.
Interactive state is attribute-driven: disabled
(or [aria-disabled="true"]) and
[aria-busy="true"]. No .is-disabled
or .is-loading classes.
Customization
Button exposes scoped tokens for color (bg, text, hover text,
border, shadow), sizing (padding, radius, font size), and icon
sizing. Emphasis variants remap the color tokens; size variants
remap padding and font-size; .as-icon swaps to the
ui-pad-square-* grammar; .is-bare
zeroes padding and radius to turn the button into a plain
clickable wrapper.
Full token list and defaults:
src/css/button.css.
Accessibility
-
Pick
<button>vs<a class="button">by behavior. Action (submits, deletes, opens a widget) always uses<button>-- anchors don't carry action semantics and Enter/Space handling differs. Navigation (goes to a URL) uses<a class="button">with a realhref. Neverrole="button"on an anchor unless you're overriding its navigational meaning, which is rare. -
.as-iconrequires a name. An icon-only button has no visible text, soaria-labelis mandatory. A.visually-hiddenspan with the label text works too and survives a CSS-disabled render. -
disabledvsaria-busy.disabledtells the user "this action is unavailable";aria-busy="true"tells the user "this action is in progress". Screen readers announce the difference. Usedisabledfor validation failures and missing inputs; usearia-busyfor "clicked save, now waiting on the network". -
[aria-disabled="true"]for non-buttoninvokers. Anchors can't carry the nativedisabledattribute but can carryaria-disabled; button styles both the same way, andpointer-events: nonekeeps the element inert in either case.
CSS Reference: Button
Clickable action trigger
Source: src/css/button.css
Tokens
| Token | Default | Description |
|---|---|---|
--button-bg | var(--color-primary) | Background color for the button |
--button-color | var(--color-on-primary) | Text and icon color for the button |
--button-color-hover | var(--button-color) | Text color on hover, defaults to --button-color |
--button-border | transparent | Border color for the button |
--button-padding-block | var(--ui-pad-y-tight) | Vertical padding inside the button |
--button-padding-inline | var(--ui-pad-x-snug) | Horizontal padding inside the button |
--button-radius | var(--radius-md) | Border radius for the button |
--button-font-size | var(--ui-text-sm) | Font size for button text |
--button-font-weight | var(--font-weight-bold) | Font weight for button text |
--button-icon-size | var(--icon-size | Size of SVG icons inside the button |
Slots
| Slot | Description |
|---|---|
svg | Icons act as their own slot inside a button |
Classes
| Class | Description |
|---|---|
.button | component root |
.[aria-busy="true"] | Setting aria-busy=true will show the spinner indicator, and alter the cursor type. |
.is-accent | Accent color variant |
.is-danger | Danger color variant |
.is-neutral | Neutral fill for chrome and quiet actions |
.is-outlined | Transparent fill with tinted text and border |
.is-ghost | Transparent background with no border |
.is-bare | No visual chrome, just a clickable wrapper |
.is-sm | Smaller size with tighter padding |
.is-lg | Larger size with more padding |
.as-icon | Square padding for icon-only buttons |
.as-block | Full-width button |
CSS Reference: Button Group
Segmented group of related buttons
Source: src/css/button.css
Classes
| Class | Description |
|---|---|
.button-group | component root |