Button

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleApache-2.0
Installation mix mishka.ui.gen.headless button
Source View Source

Overview of the Phoenix and LiveView Headless Button Component

The Mishka Chelekom Headless Button is the action primitive for Phoenix and LiveView. It has no colors, sizing or spacing. It renders a button, or a link when you pass navigate, patch or href, so a call to action keeps the element that fits what it does: a link navigates, a button acts. A link gets role="button". Anchor attributes such as target and rel, and phx-* bindings, pass through on both.

loading marks the button busy. It sets aria-busy and data-loading, disables the button and shows the loader slot while the label stays in place, so the button keeps its width. disabled sets data-disabled. The four parts, start-icon, loader, label and end-icon, each take a *_class attribute.

Base UI Button example - filled, outlined and disabled (Phoenix headless)

Base UI Button example - filled, outlined and disabled (Phoenix headless)

heex
<div class="flex flex-wrap items-center gap-3">
  <.button
    id="baseui-button-primary"
    class="inline-flex items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm font-medium border-neutral-950 bg-neutral-950 text-white dark:border-white dark:bg-white dark:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-[disabled]:border-neutral-500 data-[disabled]:bg-transparent data-[disabled]:text-neutral-500"
  >Save changes</.button>
  <.button
    id="baseui-button-ghost"
    class="inline-flex items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm font-medium border-neutral-200 text-neutral-950 dark:border-neutral-800 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
  >Cancel</.button>
  <.button
    id="baseui-button-disabled"
    disabled
    class="inline-flex items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm font-medium border-neutral-950 bg-neutral-950 text-white dark:border-white dark:bg-white dark:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-[disabled]:border-neutral-500 data-[disabled]:bg-transparent data-[disabled]:text-neutral-500"
  >Unavailable</.button>
</div>

Base UI Button example - loading with a spinner (Phoenix headless)

Base UI Button example - loading with a spinner (Phoenix headless)

heex
<.button
  id="baseui-button-loading"
  loading
  class="inline-flex items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm font-medium border-neutral-950 bg-neutral-950 text-white dark:border-white dark:bg-white dark:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-[disabled]:border-neutral-500 data-[disabled]:bg-transparent data-[disabled]:text-neutral-500"
>
  <:loader>
    <svg
      xmlns="http://www.w3.org/2000/svg"
      fill="none"
      viewBox="0 0 24 24"
      stroke-width="1.5"
      stroke="currentColor"
      class="size-4 animate-spin"
    >
      <path
        stroke-linecap="round"
        stroke-linejoin="round"
        d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
      />
    </svg>
  </:loader>
  Publishing
</.button>

Button example - start and end icons (Phoenix headless)

Button example - start and end icons (Phoenix headless)

heex
<div class="flex flex-wrap items-center gap-3">
  <.button
    start_icon_class="inline-flex shrink-0"
    class="inline-flex items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm font-medium border-neutral-950 bg-neutral-950 text-white dark:border-white dark:bg-white dark:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white data-[disabled]:border-neutral-500 data-[disabled]:bg-transparent data-[disabled]:text-neutral-500"
  >
    <:start_icon>
      <svg
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
        stroke-width="1.5"
        stroke="currentColor"
        class="size-4"
      >
        <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
      </svg>
    </:start_icon>
    New project
  </.button>
  <.button
    end_icon_class="inline-flex shrink-0"
    class="inline-flex items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm font-medium border-neutral-200 text-neutral-950 dark:border-neutral-800 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
  >
    Continue
    <:end_icon>
      <svg
        xmlns="http://www.w3.org/2000/svg"
        fill="none"
        viewBox="0 0 24 24"
        stroke-width="1.5"
        stroke="currentColor"
        class="size-4"
      >
        <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" />
      </svg>
    </:end_icon>
  </.button>
</div>

Submitting forms

In the button form, type is button by default and accepts submit and reset. form names the id of the form to submit, and name and value are sent with it. While loading is set the button is natively disabled, so it cannot submit twice.

heex
<.form for={@form} id="profile-form" phx-submit="save">
  <.input field={@form[:name]} />
  <.button type="submit" loading={@saving?} class="...">Save</.button>
  <.button type="reset" class="...">Reset</.button>
</.form>

Parts & data attributes

The root carries chelekom-button, data-disabled and data-loading. Each part has a data-part value and a chelekom-button__* class.

  • start-icon holds the :start_icon slot before the label. Class attribute: start_icon_class.
  • loader holds the :loader slot and is rendered only while loading is set and the slot is given. Class attribute: loader_class.
  • label is always rendered. It holds the label attribute and the default slot. Class attribute: label_class.
  • end-icon holds the :end_icon slot after the label. Class attribute: end_icon_class.

The icon and loader parts are marked aria-hidden, so the label is the button's accessible name.

Accessibility

Follows the WAI-ARIA Button pattern: Enter and Space activate the button. A busy button exposes aria-busy. A button with only an icon needs an accessible name, which the headless Action Icon requires for you.