Accordion
v0.0.10
mix mishka.ui.gen.headless accordion
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)
<.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)
<.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)
<.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.
| Part | data-part | State / ARIA | Purpose |
|---|---|---|---|
| Item | item | data-index, data-value, data-open, data-disabled, data-on-open-change | The wrapper for one header/trigger + panel pair. Takes item_class on the root and class on an item slot. |
| Header | header | h1 - h6, level set by heading_level | The heading element wrapping the trigger button. Takes heading_class on the root. |
| Trigger | trigger | aria-expanded, aria-controls, aria-disabled, data-index, data-disabled, data-panel-open | The button that opens/closes its panel; roving tabindex. Takes trigger_class on the root and on an item slot. |
| Panel | panel | role="region", aria-labelledby, data-open, data-closed, data-index, data-orientation, data-disabled, data-starting-style, data-ending-style | The 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.
Attributes
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
id
|
string
| — | — | Yes | — |
multiple
|
boolean
|
false
| — | No | Allow multiple panels open at once (one-at-a-time when false) |
collapsible
|
boolean
|
true
| — | No | Allow the open item to be closed |
disabled
|
boolean
|
false
| — | No | Disable the whole accordion |
orientation
|
string
|
vertical
|
verticalhorizontal
| No | Arrow-key navigation axis |
heading_level
|
integer
|
3
| — | No | Heading level (1-6) wrapping each trigger |
loop
|
boolean
|
true
| — | No | Arrow-key focus wraps at the ends |
hidden_until_found
|
boolean
|
false
| — | No | — |
value
|
list
| — | — | No | Controlled: the list of open item values |
initial_open
|
list
| — | — | No | Uncontrolled: initially open item values |
on_value_change
|
string
| — | — | No | LiveView event pushed when the open set changes |
class
|
any
| — | — | No | — |
heading_class
|
any
| — | — | No | — |
item_class
|
any
| — | — | No | — |
trigger_class
|
any
| — | — | No | — |
panel_class
|
any
| — | — | No | — |
rest
|
global
| — | — | No | — |
Slots
| Name | Required | Description |
|---|---|---|
item
| Yes | — |
trigger_icon
| No | Optional icon rendered inside every trigger (e.g. a chevron or plus). Style it off the trigger's `data-panel-open` state, e.g. `group-data-[panel-open]:rotate-45`. |
Attributes of the item slot
| Name | Type | Default | Values | Required | Description |
|---|---|---|---|---|---|
title
|
string
| — | — | Yes | — |
open
|
boolean
| — | — | No | Initially open (ignored when value/default_value is set) |
disabled
|
boolean
| — | — | No | Disable just this item |
value
|
string
| — | — | No | Stable identity, used for controlled mode + change events |
on_open_change
|
string
| — | — | No | LiveView event pushed when THIS item toggles |
class
|
any
| — | — | No | Additional CSS classes for this item's wrapper (data-part=item) |
trigger_class
|
any
| — | — | No | Additional CSS classes for this item's trigger button |
content_class
|
any
| — | — | No | Additional CSS classes for this item's panel (data-part=panel) |
Options
Each option takes one or more of its values, separated by commas.
| Option | Values |
|---|---|
--type
|
accordion
|
--module
| A module name of your own |
What it generates
mix mishka.ui.gen.headless accordion