Accordion

v0.0.10

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

Overview of the Phoenix and LiveView Headless Accordion Component

The Mishka Chelekom Headless Accordion ships zero opinionated styling - only the semantic markup, full WAI-ARIA APG wiring, roving tabindex keyboard navigation, and paired-presence state (data-open/data-closed). Every part - item, header, trigger, and panel - is addressable through a *_class attribute, so you style it entirely with your own Tailwind classes while the accessibility and behavior keep working underneath.

The classes come in two levels. The root takes class for the wrapper and heading_class, item_class, trigger_class and panel_class, which reach every header, item, trigger and panel at once. Each item slot takes class, trigger_class and content_class for that one item. Both levels apply together, the root's classes first.

Base UI Accordion example - single open (Phoenix headless)

Base UI Accordion example - single open (Phoenix headless)

heex
<.accordion
  id="baseui-accordion-hero"
  collapsible
  heading_level={3}
  class="flex w-full max-w-80 flex-col border border-neutral-950 text-neutral-950 dark:border-white dark:text-white"
>
  <:trigger_icon>
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      stroke-linecap="square"
      stroke-linejoin="round"
      class="block shrink-0 transition-transform duration-100 ease-[ease-out] group-data-[panel-open]:rotate-45"
    >
      <path d="M1.5 8h13M8 14.5v-13" />
    </svg>
  </:trigger_icon>
  <:item
    title="What is Base UI?"
    trigger_class="group flex w-full items-center justify-between gap-4 bg-transparent px-3 py-2 text-left text-sm font-normal text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 focus-visible:relative focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800"
    content_class="h-[var(--accordion-panel-height)] overflow-hidden text-sm transition-[height] duration-150 ease-[ease-out] data-[ending-style]:h-0 data-[starting-style]:h-0"
  >
    <div class="px-3 py-2">
      Base UI is a library of high-quality unstyled React components for design systems and web apps.
    </div>
  </:item>
  <:item
    title="How do I get started?"
    class="border-t border-neutral-950 dark:border-white"
    trigger_class="group flex w-full items-center justify-between gap-4 bg-transparent px-3 py-2 text-left text-sm font-normal text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 focus-visible:relative focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800"
    content_class="h-[var(--accordion-panel-height)] overflow-hidden text-sm transition-[height] duration-150 ease-[ease-out] data-[ending-style]:h-0 data-[starting-style]:h-0"
  >
    <div class="px-3 py-2">
      Head to the "Quick start" guide in the docs. If you've used unstyled libraries before, you'll feel at home.
    </div>
  </:item>
  <:item
    title="Can I use it for my project?"
    class="border-t border-neutral-950 dark:border-white"
    trigger_class="group flex w-full items-center justify-between gap-4 bg-transparent px-3 py-2 text-left text-sm font-normal text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 focus-visible:relative focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800"
    content_class="h-[var(--accordion-panel-height)] overflow-hidden text-sm transition-[height] duration-150 ease-[ease-out] data-[ending-style]:h-0 data-[starting-style]:h-0"
  >
    <div class="px-3 py-2">Of course! Base UI is free and open source.</div>
  </:item>
</.accordion>

Base UI Accordion example - multiple open (Phoenix headless)

Base UI Accordion example - multiple open (Phoenix headless)

heex
<.accordion
  id="baseui-accordion-multiple"
  multiple
  collapsible
  heading_level={3}
  class="flex w-full max-w-80 flex-col border border-neutral-950 text-neutral-950 dark:border-white dark:text-white"
>
  <:trigger_icon>
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      stroke-linecap="square"
      stroke-linejoin="round"
      class="block shrink-0 transition-transform duration-100 ease-[ease-out] group-data-[panel-open]:rotate-45"
    >
      <path d="M1.5 8h13M8 14.5v-13" />
    </svg>
  </:trigger_icon>
  <:item
    title="What is Base UI?"
    trigger_class="group flex w-full items-center justify-between gap-4 bg-transparent px-3 py-2 text-left text-sm font-normal text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 focus-visible:relative focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800"
    content_class="h-[var(--accordion-panel-height)] overflow-hidden text-sm transition-[height] duration-150 ease-[ease-out] data-[ending-style]:h-0 data-[starting-style]:h-0"
  >
    <div class="px-3 py-2">
      Base UI is a library of high-quality unstyled React components for design systems and web apps.
    </div>
  </:item>
  <:item
    title="How do I get started?"
    class="border-t border-neutral-950 dark:border-white"
    trigger_class="group flex w-full items-center justify-between gap-4 bg-transparent px-3 py-2 text-left text-sm font-normal text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 focus-visible:relative focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800"
    content_class="h-[var(--accordion-panel-height)] overflow-hidden text-sm transition-[height] duration-150 ease-[ease-out] data-[ending-style]:h-0 data-[starting-style]:h-0"
  >
    <div class="px-3 py-2">
      Head to the "Quick start" guide in the docs. If you've used unstyled libraries before, you'll feel at home.
    </div>
  </:item>
  <:item
    title="Can I use it for my project?"
    class="border-t border-neutral-950 dark:border-white"
    trigger_class="group flex w-full items-center justify-between gap-4 bg-transparent px-3 py-2 text-left text-sm font-normal text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 focus-visible:relative focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-neutral-950 dark:focus-visible:outline-white dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800"
    content_class="h-[var(--accordion-panel-height)] overflow-hidden text-sm transition-[height] duration-150 ease-[ease-out] data-[ending-style]:h-0 data-[starting-style]:h-0"
  >
    <div class="px-3 py-2">Of course! Base UI is free and open source.</div>
  </:item>
</.accordion>

Accordion example - classes on the root (Phoenix headless)

The shared classes sit on the root, so the three items need no classes of their own. The first item is open from the start (open), the second adds one class of its own, and the third is disabled: its data-disabled attribute picks up the dimmed look from item_class.

Accordion example - classes on the root (Phoenix headless)

Orders leave the warehouse within two working days.

heex
<.accordion
  id="classes-accordion"
  collapsible
  heading_level={3}
  class="flex w-full max-w-80 flex-col gap-2"
  heading_class="m-0"
  item_class="rounded-md border border-neutral-300 bg-white data-[disabled]:opacity-50 dark:border-neutral-700 dark:bg-neutral-900"
  trigger_class="flex w-full items-center justify-between gap-4 rounded-md px-3 py-2 text-left text-sm font-medium text-neutral-950 select-none hover:not-data-[disabled]:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-neutral-950 dark:text-white dark:hover:not-data-[disabled]:bg-neutral-800 dark:focus-visible:outline-white"
  panel_class="px-3 pb-3 text-sm text-neutral-700 dark:text-neutral-300"
>
  <:trigger_icon>
    <.icon name="hero-chevron-down-mini" class="size-4 shrink-0" />
  </:trigger_icon>
  <:item title="Shipping" open>
    Orders leave the warehouse within two working days.
  </:item>
  <:item title="Returns" class="ring-2 ring-neutral-950 dark:ring-white">
    This item adds its own ring on top of the shared item classes.
  </:item>
  <:item title="Gift cards" disabled>
    Gift cards are not available yet.
  </:item>
</.accordion>

Parts & data attributes

Each part carries a stable data-part and a structural chelekom-accordion__* class you can target, plus paired-presence state for open/closed transitions.

Partdata-partState / ARIAPurpose
Itemitemdata-index, data-value, data-open, data-disabled, data-on-open-changeThe wrapper for one header/trigger + panel pair. Takes item_class on the root and class on an item slot.
Headerheaderh1 - h6, level set by heading_levelThe heading element wrapping the trigger button. Takes heading_class on the root.
Triggertriggeraria-expanded, aria-controls, aria-disabled, data-index, data-disabled, data-panel-openThe button that opens/closes its panel; roving tabindex. Takes trigger_class on the root and on an item slot.
Panelpanelrole="region", aria-labelledby, data-open, data-closed, data-index, data-orientation, data-disabled, data-starting-style, data-ending-styleThe disclosure content; closed panels stay mounted with hidden so the client engine can animate them. Takes panel_class on the root and content_class on an item slot.

Accessibility

Follows the WAI-ARIA Accordion pattern: each panel toggles with Enter or Space, arrow keys move focus between headers (vertical or horizontal, RTL-aware), Home/End jump to the first/last enabled header, and disabled headers are skipped. Only one trigger sits in the tab order at a time - the rest are reached with the arrow keys.