Toggle Group

v0.0.10

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

Overview of the Phoenix and LiveView Headless Toggle Group Component

The Mishka Chelekom Headless Toggle Group is a row of toggle buttons for Phoenix LiveView with no styling of its own. It renders a role="group" container of buttons with the WAI-ARIA wiring and a roving tabindex: arrow keys move focus along the orientation, looping when loop is on, and Home and End jump to the first and last item. Each item shows its state as aria-pressed and data-pressed. Without multiple the group is single-select: pressing an item presses only it, and pressing the pressed item clears it. With multiple, several items can be pressed at once.

The root takes class and every item takes item_class, so you style both with your own Tailwind classes. An :item slot also takes its own class, for that one item. Both apply, item_class first.

Single-select example (text alignment)

Pressing another alignment moves the pressed state to it. Pressing the pressed one clears the group.

Single-select example (text alignment)

heex
<.toggle_group
  id="toggle-group-hero"
  aria-label="Text alignment"
  value="left"
  class="inline-flex gap-px p-px border border-neutral-950 dark:border-white"
  item_class="flex size-8 items-center justify-center border-none rounded-none bg-transparent text-neutral-950 dark:text-white select-none hover:not-data-[disabled]:bg-neutral-100 dark:hover:not-data-[disabled]:bg-neutral-800 active:not-data-[disabled]:not-data-[pressed]:bg-neutral-200 dark:active:not-data-[disabled]:not-data-[pressed]:bg-neutral-700 data-[pressed]:bg-neutral-950 data-[pressed]:text-white dark:data-[pressed]:bg-white dark:data-[pressed]:text-neutral-950 data-[pressed]:hover:not-data-[disabled]:bg-neutral-950 data-[pressed]:hover:not-data-[disabled]:text-white dark:data-[pressed]:hover:not-data-[disabled]:bg-white dark:data-[pressed]:hover:not-data-[disabled]:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
>
  <:item value="left" aria-label="Align left"><.icon name="hero-bars-3-bottom-left" class="size-4" /></:item>
  <:item value="center" aria-label="Align center"><.icon name="hero-bars-3" class="size-4" /></:item>
  <:item value="right" aria-label="Align right"><.icon name="hero-bars-3-bottom-right" class="size-4" /></:item>
</.toggle_group>

Multi-select example (text formatting)

With multiple, each item flips on its own. value takes a list of the values that start pressed.

Multi-select example (text formatting)

heex
<.toggle_group
  id="toggle-group-multiple"
  multiple
  value={["bold", "italic"]}
  aria-label="Text formatting options"
  class="inline-flex gap-px p-px border border-neutral-950 dark:border-white"
  item_class="flex size-8 items-center justify-center border-none rounded-none bg-transparent text-neutral-950 dark:text-white select-none hover:bg-neutral-100 dark:hover:bg-neutral-800 data-[pressed]:bg-neutral-950 data-[pressed]:text-white dark:data-[pressed]:bg-white dark:data-[pressed]:text-neutral-950 data-[pressed]:hover:bg-neutral-950 data-[pressed]:hover:text-white dark:data-[pressed]:hover:bg-white dark:data-[pressed]:hover:text-neutral-950 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white"
>
  <:item value="bold" aria-label="Bold"><.icon name="hero-bold" class="size-4" /></:item>
  <:item value="italic" aria-label="Italic"><.icon name="hero-italic" class="size-4" /></:item>
  <:item value="underline" aria-label="Underline"><.icon name="hero-underline" class="size-4" /></:item>
</.toggle_group>

Attributes and slots

id is required. value is the pressed value, or a list of them, on render. multiple allows several pressed items. disabled disables the whole group and sets data-disabled. orientation is "horizontal" (the default) or "vertical", which also picks the arrow keys. loop is on by default.

Give the group a name to render hidden inputs that hold the pressed values. The hook rewrites them as the selection changes and fires an input event, so an enclosing <.form phx-change> reacts. form names the form they belong to when the group is rendered outside it. on_change is a LiveView event pushed with %{"value" => values}, the list of pressed values, on every press. It goes to the LiveView or LiveComponent that renders the group.

Each :item takes value, disabled, class and aria-label. A disabled item is skipped by the arrow keys.

Parts & data attributes

Each part carries a stable data-part and a structural chelekom-toggle-group__* class you can target, plus the pressed state of the items.

Partdata-partState / ARIAPurpose
Root-role="group", aria-orientation, aria-disabled, data-orientation, data-multiple, data-disabledCarries the ToggleGroup hook; takes class.
Itemitemaria-pressed, aria-disabled, data-pressed, data-disabled, data-valueA single toggle button in the group; takes item_class for every item and the slot's class for one.
Value inputsvalue-inputs-Hidden input(s) carrying the pressed value(s) for form submission.

Accessibility

Follows the WAI-ARIA Toolbar pattern: arrow keys move focus between toggles along the configured orientation (looping when enabled), Home and End jump to the first and last toggle, and Enter or Space toggles the pressed state. Only one item is in the tab sequence (roving tabindex): the first pressed one, or the first enabled one when none is pressed. An item that holds only an icon needs an aria-label, and the group itself needs one too, as the examples show.