Skip to content
Gloss v2.0.0
Source
Components

Button

No border, no ornament — hierarchy is ink density, not an outline. Five variants, three sizes, one geometry.

Variants

default is quiet — no fill at rest, a flat tint of the stock on hover, the same gesture as a CardRow. primary is the accent fill — one committing action per view, at most. dark is reserved for the single most committing action in a screen, typically a final Save; it should be rare. ghost is the same quiet block one step further back — text-secondary instead of text-primary — for dismissals and tertiary actions. danger is destructive and irreversible only.

Variants
Markup
<button>Cancel</button>
<button data-variant="primary">Save</button>
<button data-variant="dark">Finish setup</button>
<button data-variant="ghost">Dismiss</button>
<button data-variant="danger">Delete</button>

Sizes

Sizes move one rung down the space scale, and at the two ends of the ramp one step of font size with it — weight, radius, and letter-spacing never move. Medium and large pad themselves with the same rung and part on type size instead; that is what holds their order at every width, since the space scale is fluid and the size on the wider rung would otherwise overtake the one on the narrower.

Being fluid, a medium button runs 35–38px tall depending on the width of the window. IconButton builds its square from the same two rungs, so the two are exactly the same height at every width and in every size — a button and an icon button in a toolbar line up without either being told the other’s measurements.

Sizes
Markup
<button data-variant="primary" data-size="sm">Small</button>
<button data-variant="primary">Medium</button>
<button data-variant="primary" data-size="lg">Large</button>

States

Disabled buttons drop to 50% opacity and keep their variant colors, so a disabled primary still reads as the primary.

States
Markup
<button data-variant="primary" disabled="">Disabled</button>
<button aria-disabled="true">Not yet available</button>

disabled vs. aria-disabled

A disabled button is removed from the tab order, so a reader navigating by keyboard never learns it is there. When a button is temporarily unavailable and the reason matters, use aria-disabled="true" instead — it stays focusable and can carry an explanation.

With an icon

Pass an icon as the first child. The gap is --gl-space-2xs, set by the button itself.

Icon
Markup
<button data-variant="primary">
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
  New
</button>

Groups

[role="group"] is already the correct ARIA role for a toolbar of related buttons, so it doubles as the styling hook — no wrapper class.

Button group
Markup
<div role="group" aria-label="View">
  <button data-size="sm">List</button>
  <button data-size="sm">Board</button>
  <button data-size="sm">Calendar</button>
</div>

Full width

Block
Markup
<button data-variant="primary" data-block>Take a photo</button>

An <a> with .btn is fine when the thing it does is navigate. A <button> is required when it changes something. .btn exists only because a bare a selector would catch every ordinary prose link on the site — it is the one class Button needs, and only anchors reach for it.

Anchor as button
Next: IconButton Back to color
Markup
<a class="btn" data-variant="primary" href="/gloss/components/icon-button/">Next: IconButton</a>
<a class="btn" data-variant="ghost" href="/gloss/foundations/color/">Back to color</a>

API

Element / attributeValuesNotes
buttonRequired base. A bare <button> is styled directly — no class. Use <a class="btn"> instead when the control navigates.
data-variantprimary | dark | ghost | dangerOmit for the default, quiet look — no fill at rest.
data-sizesm | lgOmit for the default (medium) size. Padding, plus one step of font size at each end.
data-blockbooleanFull width of its container.
disabledbooleanRemoves the button from the tab order.
aria-disabled"true"Looks disabled, stays focusable.
[role="group"]Wrap adjacent buttons in a group to join their radii; a hairline divider separates them, since the buttons themselves carry no border.