Skip to content

Button — HTML & CSS

A themeable button with a ripple effect. Works on a <button> or an <a> (link button). Emits a change event carrying its data-value.

Wiring it up in JS (init, events, API, Vue) is on the JavaScript page.

Live example

Theme:

Color

Size — xsm · sm · default · lg

Shape (no color) — r-0 · default · r-round

Compactness — the label ellipsizes, the icon never scales

Step 4 — narrow the browser window below 480px to see these collapse (it keys off the viewport, so a narrow container will not trigger it)

Last ui-button-change:

Markup

data-value is the payload emitted on click. Text can sit directly in the button, but naming the parts with .UIb__icon / .UIb__label is what lets them survive a narrow screen (see Compactness) and a setValue({ label }) update:

html
<button class="UIb primary" data-value="save" type="button">Save</button>

<button class="UIb success g-1" data-value="ok" type="button">
  <span class="UIb__icon">✓</span><span class="UIb__label">Confirm</span>
</button>

<!-- Link button -->
<a class="UIb success" href="/page/2" data-value="/page/2" role="button" tabindex="0">
  Go
</a>

Minimal markup

The smallest working Button — no color, no data-value, no icon slots:

html
<button class="UIb" type="button">Button</button>

Compactness

A button has a fixed height, so a label that wraps to a second line is clipped. Rather than leave that to chance — and rather than patch every button that happens to be too long — the button defines what gives way, in order, when it runs out of room:

  1. Icons never do. Nothing inside the button shrinks by default, so an icon keeps its box instead of scaling down to make room for text.
  2. Padding and gap go first.
  3. The label ellipsizes. It is the only part allowed to shrink, and it never wraps.
  4. The label disappears — opt-in, below 480px.

The button will shrink, but never past a square (its min-width equals its height), so an icon always fits.

Steps 1–3 are automatic and need no classes. For the ellipsis and for step 4 the text must live in its own element — a bare text node cannot be truncated or hidden by CSS:

html
<button class="UIb primary" data-value="save" type="button">
  <span class="UIb__icon">💾</span><span class="UIb__label">Save all changes</span>
</button>
ClassGoes onEffect
UIb__icona childKeeps its size; centers its glyph or <svg>.
UIb__labela childShrinks and ellipsizes instead of wrapping.
collapsethe buttonBelow 480px: hides the label, squares the button, re-centers the icon.
ui-mobile-hideany childBelow 480px: display: none.

One icon and a label

collapse is the shorthand for the common case:

html
<button class="UIb success collapse" data-value="save" type="button">
  <span class="UIb__icon">💾</span><span class="UIb__label">Save changes</span>
</button>

Two icons, or an icon in an odd place

With two icons the library cannot guess which one carries the meaning — [💾] Save [▾] collapsed down to [▾] says nothing about what it does. That is a decision about content, so the markup makes it: leave collapse off and mark what should go.

html
<button class="UIb info g-1" data-value="export" type="button">
  <span class="UIb__icon">⬆</span>
  <span class="UIb__label ui-mobile-hide">Export report</span>
  <span class="UIb__icon ui-mobile-hide">▾</span>
</button>

ui-mobile-hide is a global utility, like ui-no-ripple — it works on any element, not only inside a button.

Accessibility is handled for you

A hidden label leaves the accessibility tree, so a collapsed button would lose its accessible name at exactly the width where it also has no visible text. Button.scan() mirrors .UIb__label into aria-label to keep the name. A hand-written aria-label always wins and is never overwritten.

One breakpoint, on purpose

There is a single threshold at 480px. Splitting HD from 4K buys nothing — the problems all live around 400px. Note it keys off the viewport, not the button's container.