Accordion component using exclusively Phoenix.LiveView.JS — no JS hooks required.
An accordion presents a list of items where each item can expand to reveal
its content. This implementation is zero-JavaScript-bundle: all toggle
logic is expressed as Phoenix.LiveView.JS commands compiled into HTML
phx-click attributes, so transitions execute without a server round-trip.
Interaction Modes
| Mode | Behaviour |
|---|---|
:single | Only one item can be open at a time. Opening a new item |
| automatically closes the previously open one. | |
:multiple | Any number of items can be open simultaneously. |
Sub-components
| Function | Element | Purpose |
|---|---|---|
accordion/1 | div | Root container |
accordion_item/1 | div | Per-item wrapper with border and spacing |
accordion_trigger/1 | button | Toggle button with animated chevron + ARIA |
accordion_content/1 | div | Collapsible content panel |
Example — FAQ (single mode)
The most common use case: a list of questions where only one answer is visible at a time, reducing cognitive load.
<.accordion id="faq" type={:single}>
<.accordion_item value="q1" type={:single} accordion_id="faq">
<.accordion_trigger value="q1" type={:single} accordion_id="faq">
What is PhiaUI?
</.accordion_trigger>
<.accordion_content value="q1">
A Phoenix LiveView UI component library inspired by shadcn/ui.
</.accordion_content>
</.accordion_item>
<.accordion_item value="q2" type={:single} accordion_id="faq">
<.accordion_trigger value="q2" type={:single} accordion_id="faq">
Do I need to install any JavaScript packages?
</.accordion_trigger>
<.accordion_content value="q2">
No. The accordion uses only Phoenix.LiveView.JS for all interactions.
</.accordion_content>
</.accordion_item>
</.accordion>Example — Settings Panel (multiple mode)
Use :multiple when independent sections can all be open at once, such as
a settings page with distinct categories.
<.accordion id="settings" type={:multiple}>
<.accordion_item value="profile" type={:multiple} accordion_id="settings">
<.accordion_trigger value="profile" type={:multiple} accordion_id="settings">
Profile Settings
</.accordion_trigger>
<.accordion_content value="profile">
<.input name="name" label="Display Name" value={@user.name} />
</.accordion_content>
</.accordion_item>
<.accordion_item value="notifications" type={:multiple} accordion_id="settings">
<.accordion_trigger value="notifications" type={:multiple} accordion_id="settings">
Notification Preferences
</.accordion_trigger>
<.accordion_content value="notifications">
<.checkbox name="email_notifs" label="Email notifications" />
</.accordion_content>
</.accordion_item>
</.accordion>Opening an Item by Default
Pass open={true} to both the trigger and the content to render a specific
item expanded on first load. This is the recommended way to implement
default_value behaviour:
<%# Open the item whose value matches the initial default %>
<.accordion_trigger value="q1" ... open={"q1" == @default_open}>
<.accordion_content value="q1" ... open={"q1" == @default_open}>Collapsible Single Mode
By default in :single mode, clicking the currently open item does nothing
(the item stays open). Pass collapsible={true} to the trigger to allow
re-clicking an open item to close it:
<.accordion_trigger value="q1" type={:single} accordion_id="faq" collapsible={true}>Disabled Items
Prevent interaction on specific items using the :disabled attribute on
accordion_item/1. The item renders with opacity-50 and pointer-events-none:
<.accordion_item value="premium" type={:single} accordion_id="faq" disabled={!@user.premium}>Accessibility
- Trigger buttons use
aria-expanded(toggled viaJS.set_attribute) andaria-controlspointing at the content panel ID - Content panels use stable IDs (
accordion-content-{value}) referenced byaria-controls - The chevron icon carries
aria-hidden="true"so screen readers ignore it - Disabled items get
aria-disabled="true"on the wrapper div
Summary
Functions
Renders the accordion root container.
Renders the collapsible accordion content panel.
Renders an accordion item wrapper.
Renders the accordion trigger button with Phoenix.LiveView.JS-powered toggle.
Functions
Renders the accordion root container.
The id is the coordination point for :single mode — all child triggers
reference it to close sibling items when a new one is opened.
Attributes
id(:string) - Unique ID for the root container. Required for:singlemode — theaccordion_trigger/1uses this ID to target all sibling items via CSS selectors like#faq [data-accordion-content]when closing others.Defaults to
nil.type(:atom) - Interaction mode::singleallows one open item,:multipleallows many. Defaults to:single. Must be one of:single, or:multiple.class(:string) - Additional CSS classes. Defaults tonil.Global attributes are accepted. Extra HTML attributes forwarded to the root
<div>.
Slots
inner_block(required) -accordion_item/1sub-components.
Renders the collapsible accordion content panel.
The panel ID (accordion-content-{value}) is the coordination point:
accordion_trigger/1references it viaaria-controls- The JS toggle commands target it by ID
By default the panel starts hidden (display: none). The inner pb-4 pt-0
wrapper provides consistent padding without affecting the transition.
Default Open
To render an item expanded on initial page load, pass open={true} to both
the trigger and its corresponding content:
<.accordion_trigger value="welcome" ... open={@default_open == "welcome"}>
Welcome
</.accordion_trigger>
<.accordion_content value="welcome" open={@default_open == "welcome"}>
Content here.
</.accordion_content>Attributes
value(:string) (required) - Must match the:valueof the parentaccordion_item/1.open(:boolean) - Whentrue, the content starts visible (rendered withdisplay: block). Pair withopen={true}on the correspondingaccordion_trigger/1to render an item expanded on first load.Defaults to
false.class(:string) - Additional CSS classes. Defaults tonil.Global attributes are accepted. Extra HTML attributes forwarded to the content
<div>.
Slots
inner_block(required) - Content shown when the item is expanded.
Renders an accordion item wrapper.
Each item has a bottom border by default to visually separate it from its neighbours. The border is part of the item — not the root container — so there is no double-border at the top of the first item.
Attributes
value(:string) (required) - Unique string identifier for this item within the accordion. Used to build stable DOM IDs:accordion-trigger-{value}andaccordion-content-{value}. Must be unique across all items in the same accordion instance.type(:atom) - Inherited from the parentaccordion/1— controls toggle behaviour. Defaults to:single. Must be one of:single, or:multiple.accordion_id(:string) - ID of the parentaccordion/1— required for:singleexclusivity. Defaults tonil.disabled(:boolean) - Whentrue, prevents interaction with this item. Appliespointer-events-noneandopacity-50via CSS, and setsaria-disabled="true"for screen readers. Use this to indicate that a feature is unavailable (e.g. behind a paywall).Defaults to
false.class(:string) - Additional CSS classes. Defaults tonil.Global attributes are accepted. Extra HTML attributes forwarded to the wrapper
<div>.
Slots
inner_block(required) -accordion_trigger/1andaccordion_content/1sub-components.
Renders the accordion trigger button with Phoenix.LiveView.JS-powered toggle.
The toggle JS command is computed at render time by build_toggle_js/4 and
embedded into the phx-click attribute. This means the client executes the
animation directly without a server round-trip.
The animated chevron rotates 180° when the item is open. It is driven by
the rotate-180 Tailwind class toggled by the JS command, and uses
transition-transform for a smooth animation.
ARIA attributes
aria-expanded— reflects current open state; updated by the JS commandaria-controls— points to theaccordion-content-{value}panel ID so screen readers can announce "controls this region"
Attributes
value(:string) (required) - Must match the:valueof the parentaccordion_item/1.type(:atom) - Inherited from the parentaccordion/1— controls the JS toggle strategy. Defaults to:single. Must be one of:single, or:multiple.accordion_id(:string) - ID of the parentaccordion/1— required for:singlemode to close siblings. Defaults tonil.collapsible(:boolean) - Whentruein:singlemode, clicking the currently open item closes it. Whenfalse(default), the open item cannot be closed by clicking its trigger — exactly one item is always open once any item has been opened. Only effective in:singlemode withaccordion_idset.Defaults to
false.open(:boolean) - Whentrue, the item starts in the expanded state. This controls the initialaria-expandedattribute and the chevron rotation. Pair withopen={true}on the correspondingaccordion_content/1.Defaults to
false.class(:string) - Additional CSS classes. Defaults tonil.Global attributes are accepted. Extra HTML attributes forwarded to the
<button>.
Slots
inner_block(required) - Trigger label — question text, section name, etc.