Button
elements man style/components/button Read as markdownA bare <button> is a finished secondary button: bordered, correct height,
hover and active states, a focus ring. Add is- modifiers to change intent,
size, or state. Each modifier owns one axis, so they combine freely and order
never matters. This is button.css in @layer components.
<button>Cancel</button>
<button class="is-primary is-lg is-block">Sign up</button>
Submit Inputs
input[type=submit], input[type=reset], and input[type=button] get the
same treatment with no class at all, and take the same modifiers. A submit
input is a button to whoever meets it, so it looks and behaves like one:
<input type="submit" value="Save"/>
<input type="submit" class="is-primary is-lg" value="Sign up"/>
The two modifiers they cannot take are is-loading and is-icon. Both need
content inside the element, and an input is void, with no children and no
::before. Use a <button> when you need either.
Coercion
To render a non-button element (typically an <a>) as a button, add .button.
It gets the identical treatment and takes all the same modifiers:
<a class="button is-primary" href="/install">Install</a>
Intents
| Class | Look |
|---|---|
(bare <button>) |
Bordered secondary |
is-primary |
Accent-filled call to action (monochrome until branded) |
is-ghost |
Borderless, quiet |
is-danger |
Red destructive fill |
is-primary fills with --accent and uses --accent-ink for its label, so it
takes your brand color automatically. is-danger fills with --danger and uses
white text deliberately (in dark mode --danger-ink is a light text tint, not a
fill foreground) and swaps in the danger focus ring.
<button class="is-primary">Save</button>
<button class="is-ghost">Skip</button>
<button class="is-danger">Delete</button>
Sizes
| Class | Height |
|---|---|
is-sm |
32px |
| (default) | 40px |
is-lg |
48px |
is-hero |
56px |
Width, chrome, and state
| Class | Effect |
|---|---|
is-block |
Full width |
is-bare |
Strips all chrome: height, padding, border, background |
is-loading |
Progress cursor, muted, no pointer events |
is-disabled |
Disabled look (alongside the :disabled attribute) |
<button disabled> and aria-disabled="true" also get the disabled treatment.
Text alignment
A button centers its label, and text-align overrides that. This matters when
the button is wider than its text, which is what is-block, width: 100% or
flex: 1 in a row all do:
.todo-title { flex: 1; text-align: start; }
A button holding elements rather than bare text, an icon beside a label, keeps
its parts centered as a group and ignores text-align.
Icon-only buttons
A button whose only child is a single <svg> goes bare automatically: square
padding, no border, a subtle hover fill. No class needed:
<button>
<svg><!-- icon --></svg>
</button>
Button group
.button-group joins adjacent buttons so they share borders. The group rounds
only its outer corners and collapses the seams between buttons:
<div class="button-group">
<button>Day</button>
<button class="is-primary">Week</button>
<button>Month</button>
</div>
Composing a button into a control
A bare <button> is a finished control, and the things that finish it are the
things that fight a bigger control built around it. Putting one inside a
bordered wrapper, next to an <input>, takes three overrides that are not
obvious from the symptom:
.stepper button {
height: 100%; /* --button-height (2.75rem) otherwise wins even inside a */
min-height: 0; /* wrapper of the same height, and the hover fill paints */
/* over the wrapper's own border */
padding: 0; /* --button-padding-x survives `width`, pushing a single */
width: 2.75rem; /* glyph off centre under border-box */
}
.stepper input {
height: 100%; min-height: 0; padding: 0;
border: none; border-radius: 0; background: none;
}
Each symptom is subtle: asymmetry rather than breakage, a fill that only
misaligns on hover. If a composed control looks slightly wrong, check whether
the bare element is still asserting its own height, padding, border or radius.
This is the one place where start/conventions saying "setting padding means
rebuilding the element" reads backwards, because here setting padding is the
fix.