PhiaUi.Components.Accordion (phia_ui v0.1.5)

Copy Markdown View Source

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

ModeBehaviour
:singleOnly one item can be open at a time. Opening a new item
automatically closes the previously open one.
:multipleAny number of items can be open simultaneously.

Sub-components

FunctionElementPurpose
accordion/1divRoot container
accordion_item/1divPer-item wrapper with border and spacing
accordion_trigger/1buttonToggle button with animated chevron + ARIA
accordion_content/1divCollapsible 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 via JS.set_attribute) and aria-controls pointing at the content panel ID
  • Content panels use stable IDs (accordion-content-{value}) referenced by aria-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

accordion(assigns)

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 :single mode — the accordion_trigger/1 uses 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: :single allows one open item, :multiple allows many. Defaults to :single. Must be one of :single, or :multiple.

  • class (:string) - Additional CSS classes. Defaults to nil.

  • Global attributes are accepted. Extra HTML attributes forwarded to the root <div>.

Slots

accordion_content(assigns)

Renders the collapsible accordion content panel.

The panel ID (accordion-content-{value}) is the coordination point:

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 :value of the parent accordion_item/1.

  • open (:boolean) - When true, the content starts visible (rendered with display: block). Pair with open={true} on the corresponding accordion_trigger/1 to render an item expanded on first load.

    Defaults to false.

  • class (:string) - Additional CSS classes. Defaults to nil.

  • Global attributes are accepted. Extra HTML attributes forwarded to the content <div>.

Slots

  • inner_block (required) - Content shown when the item is expanded.

accordion_item(assigns)

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} and accordion-content-{value}. Must be unique across all items in the same accordion instance.

  • type (:atom) - Inherited from the parent accordion/1 — controls toggle behaviour. Defaults to :single. Must be one of :single, or :multiple.

  • accordion_id (:string) - ID of the parent accordion/1 — required for :single exclusivity. Defaults to nil.

  • disabled (:boolean) - When true, prevents interaction with this item. Applies pointer-events-none and opacity-50 via CSS, and sets aria-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 to nil.

  • Global attributes are accepted. Extra HTML attributes forwarded to the wrapper <div>.

Slots

accordion_trigger(assigns)

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 command
  • aria-controls — points to the accordion-content-{value} panel ID so screen readers can announce "controls this region"

Attributes

  • value (:string) (required) - Must match the :value of the parent accordion_item/1.

  • type (:atom) - Inherited from the parent accordion/1 — controls the JS toggle strategy. Defaults to :single. Must be one of :single, or :multiple.

  • accordion_id (:string) - ID of the parent accordion/1 — required for :single mode to close siblings. Defaults to nil.

  • collapsible (:boolean) - When true in :single mode, clicking the currently open item closes it. When false (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 :single mode with accordion_id set.

    Defaults to false.

  • open (:boolean) - When true, the item starts in the expanded state. This controls the initial aria-expanded attribute and the chevron rotation. Pair with open={true} on the corresponding accordion_content/1.

    Defaults to false.

  • class (:string) - Additional CSS classes. Defaults to nil.

  • Global attributes are accepted. Extra HTML attributes forwarded to the <button>.

Slots

  • inner_block (required) - Trigger label — question text, section name, etc.