Components Accordion

Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Accordion

An accordion expands to expose hidden information - great for condensing a page and letting the reader reveal content on demand, for a cleaner default view.

As of v4 the accordion is interactive out of the box with Phoenix.LiveView.JS - no Alpine.js required.

Basic

Collapsible sections with proper button semantics and keyboard toggling.

heex
<.accordion class="w-full max-w-xl mx-auto">
<:item heading="Is it accessible?">
Yes - proper button semantics, aria-expanded, and keyboard toggling out of the box.
</:item>
<:item heading="Can several be open at once?">
Not by default. Pass the multiple attr to allow more than one open section.
</:item>
<:item heading="Does it follow the theme?">
Borders, radius and text tiers all come from the design tokens.
</:item>
</.accordion>
Bordered

The bordered variant wraps each row in the panel surface.

heex
<.accordion variant="bordered" class="w-full max-w-xl mx-auto">
<:item heading="What's included?">
Auth, billing, orgs, admin and an AI-ready component library.
</:item>
<:item heading="Is it a subscription?">
One purchase, unlimited projects. No recurring fee.
</:item>
</.accordion>
Ghost

The minimal variant - hairline dividers, no surrounding box.

heex
<.accordion variant="ghost" class="w-full max-w-xl mx-auto">
<:item heading="How do I install it?">
Add the Hex dep, run mix deps.get, and point Tailwind at the source.
</:item>
<:item heading="Does it need JavaScript?">
Interactivity is LiveView.JS only - no Alpine as of v4.
</:item>
</.accordion>
Dynamic

Data-driven - pass a list of entries and render each with the item slot. Handy for FAQ or CMS content.

heex
<.accordion
class="w-full max-w-xl mx-auto"
entries={[
%{heading: "Where do the entries come from?", content: "A plain list of maps you pass in."},
%{
heading: "How is each row rendered?",
content: "The item slot receives the entry via :let."
},
%{heading: "Can the content be anything?", content: "Yes - it's a slot, so any markup works."}
]}
>
<:item :let={entry}>{entry.content}</:item>
</.accordion>
Properties
Attribute Type Default Description
class any nil CSS class for parent container
container_id string
entries list [%{}]
multiple boolean false When true, multiple sections can be expanded simultaneously
on_toggle {:struct, Phoenix.LiveView.JS} %Phoenix.LiveView.JS{ops: []} additional JS commands to run when an accordion section is toggled (LiveView.JS only)
open_index integer nil Index of item to be open at render
rest global
size string "md" sm tightens paddings and type for dense UIs (settings panels, sidebars)
one of: "sm", "md"
variant string "default" default is the shadcn row style (hairline dividers, no boxes); bordered is the boxed card-accordion. ghost is a legacy alias of default (identical look) slated for removal in a future major
one of: "default", "bordered", "ghost"
:item slot slot CSS class for parent container