Button

Actions and navigation with shared intents, variants, sizes, and a loading state.

  • .btn is for actionable elements: <button>, <a href>, or input[type="button"|"submit"|"reset"]. Not a decorative box class.
  • Use a real <button> for actions.
  • Use <a class="btn"> for navigation.
  • Always set type="button" when a button is not submitting a form.
  • Supports shared intents, shared variants, button-only variants, and sizes.
  • Adds button-only .ghost and .link variants.
  • Button groups use .join to visually join adjacent buttons.
  • Use aria-pressed for toggle buttons. Actual styles the pressed state; application code owns changing the attribute.
  • Disable a <button> or <input> with the native disabled attribute. Anchors have no native disabled state, so <a class="btn" aria-disabled="true"> is the supported way to disable a link button; application code must also prevent the click since aria-disabled does not stop navigation on its own.
Account Disabled link
<button type="button">Unstyled</button>
<button class="btn" type="button">Default</button>
<button class="btn secondary" type="button">Secondary</button>
<button class="btn danger" type="button">Danger</button>
<button class="btn outline" type="button">Outline</button>
<button class="btn danger outline" type="button">Danger outline</button>
<button class="btn soft" type="button">Soft</button>
<button class="btn danger soft" type="button">Danger soft</button>
<button class="btn ghost" type="button">Ghost</button>
<a class="btn primary" href="/account">Account</a>
<button class="btn link" type="button">Button as link</button>
<button class="btn primary" type="button" aria-busy="true" disabled>
  <span class="spinner" aria-hidden="true"></span>
  Loading
</button>
<button class="btn primary" type="button" aria-busy="true">In progress</button>
<button class="btn" type="button" disabled>Disabled</button>
<a class="btn outline" href="/account" aria-disabled="true">Disabled link</a>

Button group using .join

<div class="join" role="group" aria-label="Text alignment">
  <button class="btn" type="button">Left</button>
  <button class="btn outline" type="button">Center</button>
  <button class="btn soft" type="button">Right</button>
</div>

Toggle buttons

<button class="btn" type="button" aria-pressed="false" data-demo-toggle>
  Bold
</button>
<div class="join" role="group" aria-label="Text alignment" data-demo-toggle-group="single">
  <button class="btn" type="button" aria-pressed="true">Left</button>
  <button class="btn outline" type="button" aria-pressed="false">Center</button>
  <button class="btn outline" type="button" aria-pressed="false">Right</button>
</div>
for (const button of document.querySelectorAll("[data-demo-toggle]")) {
  button.addEventListener("click", () => {
    const pressed = button.getAttribute("aria-pressed") === "true";
    button.setAttribute("aria-pressed", String(!pressed));
  });
}

for (const group of document.querySelectorAll('[data-demo-toggle-group="single"]')) {
  group.addEventListener("click", (event) => {
    const button = event.target.closest("button[aria-pressed]");
    if (!button || !group.contains(button)) return;

    for (const item of group.querySelectorAll("button[aria-pressed]")) {
      item.setAttribute("aria-pressed", String(item === button));
    }
  });
}

Size variants

<button class="btn sm" type="button">Small</button>
<button class="btn" type="button">Regular</button>
<button class="btn lg" type="button">Large</button>

With icons, loading state...

<button class="btn" type="button"><i class="ti ti-star"></i> With an icon</button>
<button class="btn primary" type="button" aria-busy="true" disabled>
  <span class="spinner" aria-hidden="true"></span>
  Saving…
</button>