Skip to content
Fleetline

Button

Usage

Use a button to start an action on the current screen: save a work order, assign a mechanic, export a report. Use a link instead when the result is navigation to another page; a link styled as a button (<a class="btn">) is allowed only for that case.

Labels: verb + object, sentence case, three words or fewer ("Assign mechanic", "Export service log"). Avoid "OK", "Submit", "Click here". Keep paired labels parallel ("Save draft" / "Close work order"). Allow +40% length for translations — buttons wrap their row, never truncate the label.

Anatomy

  1. Container — <button type="button|submit" class="btn">, 8px radius, height from --button-height-*
  2. Label — .btn__label (required when an icon or spinner is present)
  3. Leading or trailing icon (optional) — svg.btn__icon, aria-hidden="true"
  4. Spinner (loading only) — .btn__spinner, overlays the label so the width doesn't change

Examples

States marked hover, focus-visible and active are frozen with .is-hover, .is-focus-visible, .is-active. Those classes share their rule with the real pseudo-class, so what you see is exactly what users get. Never use them in product code.

Primary

Log at least one task before closing this work order.

Saving work order WO-4182…
<button type="button" class="btn btn--primary">Assign mechanic</button>
<button type="button" class="btn btn--primary btn--sm">Assign mechanic</button>
<button type="button" class="btn btn--primary btn--lg">Assign mechanic</button>

<!-- Unavailable: disabled (skipped by Tab) or aria-disabled (focusable, explains why) -->
<button type="button" class="btn btn--primary" disabled>Assign mechanic</button>
<button type="button" class="btn btn--primary" aria-disabled="true" aria-describedby="close-why">Close work order</button>

<!-- Loading: keep the label (it stays the accessible name), announce progress separately -->
<button type="submit" class="btn btn--primary" aria-busy="true" aria-disabled="true">
  <span class="btn__label">Save work order</span>
  <span class="btn__spinner" aria-hidden="true"></span>
</button>
<span role="status" class="sr-only">Saving work order WO-4182…</span>

Secondary

<button type="button" class="btn btn--secondary">Save draft</button>

Tertiary (ghost)

<button type="button" class="btn btn--ghost">View history</button>

Destructive

Retire van KX-219? It will be removed from dispatch and its open work orders will be cancelled.

<button type="button" class="btn btn--destructive">Retire vehicle</button>

With icon

<button type="button" class="btn btn--primary">
  <svg class="btn__icon" aria-hidden="true" focusable="false"><use href="#icon-plus"></use></svg>
  <span class="btn__label">New work order</span>
</button>

<!-- Directional icons mirror in RTL -->
<button type="button" class="btn btn--primary">
  <span class="btn__label">Continue to parts</span>
  <svg class="btn__icon icon--mirror-rtl" aria-hidden="true" focusable="false"><use href="#icon-arrow-right"></use></svg>
</button>

<button type="button" class="btn btn--primary btn--lg btn--full-width">…</button>

API

HookValuesPurpose
.btnblock classBase button; without a variant it renders as secondary
.btn--primary | --secondary | --ghost | --destructivemodifier classEmphasis
.btn--sm | --lgmodifier classSize (md is default): 32 / 40 / 48px, same heights as .input--sm|lg
.btn--full-widthmodifier classFills its container (mobile forms, tablet task screens)
.btn__label, .btn__icon, .btn__spinnerpart classesLabel, decorative icon, loading spinner
disabledattributeUnavailable and removed from the tab order
aria-disabled="true"attributeUnavailable but focusable; pair with aria-describedby explaining why. Your click handler must ignore it.
aria-busy="true"attributeLoading: spinner replaces the label visually; also set aria-disabled="true" and ignore repeat activations
.is-hover | .is-focus-visible | .is-activedocs-only classFreezes a state for documentation and visual tests

Do and don't

Do use one primary per region, with parallel verb + object labels.
Don't place two primaries side by side or use labels that don't say what happens.
Do name the object in destructive actions and confirm before acting.
Don't style destructive actions as primary, or let one click delete without confirmation or undo.

Accessibility

Keyboard interaction
KeyBehavior
TabMoves focus to the button (skipped when disabled; kept when aria-disabled)
Enter / SpaceActivates the button

Tokens

Custom propertyPurpose
--button-radiusCorner radius (→ --radius-md, 8px)
--button-height-sm | -md | -lgHeights (→ --size-control-*)
--button-font-weightLabel weight (semibold)
--color-action-{primary|secondary|ghost|danger}-{bg|bg-hover|bg-active|fg}Variant colors per state
--color-action-secondary-borderSecondary outline
--color-bg-muted, --color-text-disabledDisabled fill and label
--focus-ring-width | -offset | -colorFocus ring
--density-{comfortable|compact}-control-padding-xInline padding per density
--icon-size-sm | -md, --icon-strokeIcon and spinner size
--motion-duration-fast, --motion-easing-standardColor transitions