Toolbar

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless toolbar
Source View Source

Overview of the Phoenix and LiveView Headless Toolbar Component

The Mishka Chelekom Headless Toolbar is a bar of controls for Phoenix LiveView with no styling of its own. It renders the semantic markup with the WAI-ARIA wiring and roving-focus keyboard navigation over buttons, links, inputs, groups and separators. Arrow keys move focus along the orientation, looping when loop is set, while a focused input keeps its own cursor movement and hands navigation back only at the text boundaries.

Every control is an :item slot. Besides class for the toolbar itself, the root has a class attribute for each kind of part: button_class, link_class, input_class, separator_class and group_class. An item can add its own class on top.

Base UI Toolbar example (Phoenix headless)

Tab into the toolbar, then use the arrow keys, Home and End to move between the buttons and the link. Only one control is in the tab sequence at a time.

Base UI Toolbar example (Phoenix headless)

heex
<.toolbar
  id="baseui-toolbar-hero"
  class="flex w-150 items-center gap-px border border-neutral-950 bg-white p-px dark:border-white dark:bg-neutral-950"
  group_class="flex"
  button_class="flex h-8 items-center gap-2 border-0 bg-transparent font-[inherit] text-sm leading-none whitespace-nowrap font-normal text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 active:not-data-[disabled]:bg-neutral-200 dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800 dark:active:not-data-[disabled]:bg-neutral-700 focus-visible:bg-transparent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
  separator_class="m-1 h-4 w-px bg-neutral-950 dark:bg-white"
  link_class="font-[inherit] text-sm text-neutral-500 no-underline hover:text-blue-700 dark:text-neutral-400 dark:hover:text-blue-500 focus-visible:outline-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
>
  <:item
    group="Alignment"
    label="Align left"
    class="min-w-8 justify-center px-3"
  >
    Align Left
  </:item>
  <:item
    group="Alignment"
    label="Align right"
    class="min-w-8 justify-center px-3"
  >
    Align Right
  </:item>
  <:item type="separator" />
  <:item
    group="Numerical format"
    label="Format as currency"
    class="min-w-8 justify-center"
  >
    $
  </:item>
  <:item
    group="Numerical format"
    label="Format as percent"
    class="min-w-8 justify-center"
  >
    %
  </:item>
  <:item type="separator" />
  <:item
    label="Font family"
    class="min-w-32 justify-between px-2"
  >
    Helvetica <.icon name="hero-chevron-up-down" class="block size-4" />
  </:item>
  <:item type="separator" />
  <:item
    type="link"
    href="/chelekom/docs/headless/toolbar"
    class="mr-[0.875rem] ml-auto flex-none self-center"
  >
    Edited 51m ago
  </:item>
</.toolbar>

Toolbar example - an input and a disabled button (Phoenix headless)

An :item with type="input" draws a text input that takes input_class. While the caret is inside it, ArrowLeft and ArrowRight move the caret, and focus leaves the input only at the start or the end of the text. The Export button is disabled and still in the arrow-key order, because focusable_when_disabled is on by default.

Toolbar example - an input and a disabled button (Phoenix headless)

heex
<.toolbar
  id="toolbar-search"
  class="flex w-full max-w-md flex-wrap items-center gap-1 border border-neutral-950 bg-white p-1 dark:border-white dark:bg-neutral-950"
  button_class="flex h-8 items-center justify-center border-0 bg-transparent px-3 font-[inherit] text-sm leading-none whitespace-nowrap font-normal text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 data-[disabled]:cursor-not-allowed data-[disabled]:opacity-40 dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
  input_class="h-8 min-w-0 flex-1 border border-neutral-300 bg-white px-2 text-sm text-neutral-950 placeholder:text-neutral-500 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white dark:placeholder:text-neutral-400 dark:focus-visible:outline-white"
  separator_class="m-1 h-4 w-px bg-neutral-950 dark:bg-white"
>
  <:item type="input" label="Search files" placeholder="Search files" />
  <:item type="separator" />
  <:item label="Filter">Filter</:item>
  <:item label="Export" disabled>Export</:item>
</.toolbar>

Attributes and slots

id is required. orientation is "horizontal" (the default) or "vertical" and picks the arrow keys. loop is on by default and wraps focus past the ends. disabled disables the whole toolbar. focusable_when_disabled is on by default and keeps disabled items in the roving order, focusable but inert.

An :item takes type: "button" (the default), "link", "input" or "separator". It also takes disabled, label (the aria-label), href for a link, placeholder and value for an input, group, group_class and class. Consecutive items with the same group are wrapped in a role="group" element labelled with that name.

The root's button_class, link_class, input_class and separator_class go on every control of that type, and its group_class on every group. An item's class goes on that control only, after the root's classes. group_class on the first item of a group adds classes to that group only.

Parts & data attributes

Each part carries a stable data-part (where applicable) and a structural chelekom-toolbar__* class you can target, plus orientation and disabled state reflected as data attributes.

PartElement / roleState / ARIAPurpose
Rootdiv, role="toolbar"aria-orientation, aria-disabled, data-orientation, data-disabledCarries the Toolbar hook; reads orientation/loop/disabled/focusable from data attributes; takes class.
Buttonbuttonaria-disabled, data-orientation, data-disabled, data-focusableDefault item type; a roving-focus toolbar control; takes button_class.
Linkadata-orientationItem type "link"; participates in the same roving order; takes link_class.
Inputinputaria-disabled, data-orientation, data-disabled, data-focusableItem type "input"; keeps its own cursor movement, hands navigation back at the text boundaries; takes input_class.
Groupdiv, role="group"aria-label, data-orientation, data-disabledWraps consecutive items that share the same group label; takes group_class.
Separatordiv, role="separator"aria-orientation, data-orientationItem type "separator"; a visual divider, not focusable; takes separator_class.

Accessibility

Follows the WAI-ARIA Toolbar pattern: arrow keys move focus along the orientation and loop past the ends when loop is set, Home / End jump to the first / last item, and Tab moves focus out of the toolbar entirely. With focusable_when_disabled (the default), disabled items stay in the roving order, focusable but inert, so they remain discoverable. A control with no visible text needs a label.