Accordion
A vertically stacked set of interactive headings that each reveal a section of content
Built on the native <details> and <summary>
HTML elements, with exclusive mode relying on the name attribute, enhanced by a small
Stimulus controller for arrow-key navigation, disabled items and change events.
With Ctrl/Cmd+F, the browser can search for text inside a collapsed <details> panel and expand it automatically.
Usage
Is it accessible?
Is it styled?
Is it animated?
<twig:ui:Accordion :value="['item-1']">
<twig:ui:Accordion:Item value="item-1">
<twig:ui:Accordion:Header>Is it accessible?</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Yes. It adheres to the WAI-ARIA accordion design pattern.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="item-2">
<twig:ui:Accordion:Header>Is it styled?</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Yes. It ships with default styles that match the other HarmonyUI components.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="item-3">
<twig:ui:Accordion:Header>Is it animated?</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
The chevron rotates smoothly, and motion is disabled for users who prefer reduced motion.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
Examples
Multiple
Allow several items to stay open at the same time with multiple.
Shipping
Returns
<twig:ui:Accordion multiple>
<twig:ui:Accordion:Item value="shipping">
<twig:ui:Accordion:Header>Shipping</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Orders are shipped within 2 business days.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="returns">
<twig:ui:Accordion:Header>Returns</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Free returns within 30 days of delivery.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
Disabled item
Available item
Disabled item
Another available item
<twig:ui:Accordion>
<twig:ui:Accordion:Item value="available">
<twig:ui:Accordion:Header>Available item</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
This item can be toggled.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="locked" disabled>
<twig:ui:Accordion:Header>Disabled item</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
You should not be able to open this one.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="another">
<twig:ui:Accordion:Header>Another available item</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Arrow-key navigation still reaches the disabled item, it just won't open.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
Custom indicator
Add the optional <twig:ui:Accordion:Indicator> inside the header to take over
the indicator: its content replaces the default chevron, and the header stops
rendering its own.
Can I use another icon?
Does it still track the state?
data-state attribute
as the default one.
<twig:ui:Accordion :value="['first']">
<twig:ui:Accordion:Item value="first">
<twig:ui:Accordion:Header>
Can I use another icon?
<twig:ui:Accordion:Indicator class="data-[state=open]:rotate-45">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="16" height="16">
<path d="M5 12h14"/>
<path d="M12 5v14"/>
</svg>
</twig:ui:Accordion:Indicator>
</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Yes. The content of the indicator replaces the default chevron, and its
classes let you adjust the open-state animation.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="second">
<twig:ui:Accordion:Header>
Does it still track the state?
<twig:ui:Accordion:Indicator class="data-[state=open]:rotate-45">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="16" height="16">
<path d="M5 12h14"/>
<path d="M12 5v14"/>
</svg>
</twig:ui:Accordion:Indicator>
</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
The custom indicator receives the same <code>data-state</code> attribute
as the default one.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
Indicator position
The indicator is pushed to the end of the header by default (ms-auto);
override it to place the icon before the text.
Icon before the text
ms-auto to keep it next to the text.
It keeps the default chevron
<twig:ui:Accordion>
<twig:ui:Accordion:Item value="first">
<twig:ui:Accordion:Header>
<twig:ui:Accordion:Indicator class="ms-0" />
Icon before the text
</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Place the indicator anywhere in the header; drop the default
<code>ms-auto</code> to keep it next to the text.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="second">
<twig:ui:Accordion:Header>
<twig:ui:Accordion:Indicator class="ms-0" />
It keeps the default chevron
</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Without content, the indicator renders the default chevron.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
Nested
Accordions can be nested inside a panel. Each accordion only manages its own
items: exclusive mode is scoped per instance through its own name group, and
the Stimulus controller ignores items belonging to a nested instance.
Plans and billing
Everything about subscriptions, invoices and payment methods.
How do I upgrade my plan?
Where can I find my invoices?
Security
<twig:ui:Accordion :value="['plans']" multiple>
<twig:ui:Accordion:Item value="plans">
<twig:ui:Accordion:Header>Plans and billing</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
<p>Everything about subscriptions, invoices and payment methods.</p>
<twig:ui:Accordion class="ps-4">
<twig:ui:Accordion:Item value="upgrade">
<twig:ui:Accordion:Header as="h4">How do I upgrade my plan?</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Head to the billing settings and pick a new plan; the change is prorated.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="invoices">
<twig:ui:Accordion:Header as="h4">Where can I find my invoices?</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
Invoices are listed in the billing settings and sent by email every month.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="security">
<twig:ui:Accordion:Header>Security</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
All data is encrypted at rest and in transit.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
Borders
Wrap the accordion in a border by adding rounded-lg border to the root, then
border-b px-4 last:border-b-0 to each item.
How does billing work?
Is my data secure?
What integrations do you support?
<twig:ui:Accordion class="rounded-lg border border-neutral-200 dark:border-neutral-800" :value="['billing']">
<twig:ui:Accordion:Item value="billing" class="px-4">
<twig:ui:Accordion:Header>How does billing work?</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
We offer monthly and annual subscription plans. Billing is charged at
the beginning of each cycle, and you can cancel anytime.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="security" class="px-4">
<twig:ui:Accordion:Header>Is my data secure?</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
All data is encrypted in transit and at rest, with daily automatic backups.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="integrations" class="px-4 border-b-0">
<twig:ui:Accordion:Header>What integrations do you support?</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
We integrate with the most popular tools out of the box, and expose a
REST API for everything else.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
RTL
Set dir="rtl" on the root to render the accordion for right-to-left languages.
The direction is applied to the markup and forwarded to the Zag.js machine, and
the default styles use logical properties so text and the indicator follow the
reading direction.
هل يدعم الاتجاه من اليمين إلى اليسار؟
هل يمكن الوصول إليه؟
<twig:ui:Accordion dir="rtl" :value="['item-1']">
<twig:ui:Accordion:Item value="item-1">
<twig:ui:Accordion:Header>هل يدعم الاتجاه من اليمين إلى اليسار؟</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
نعم. يتم محاذاة النص والمؤشر تلقائيًا حسب اتجاه القراءة.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
<twig:ui:Accordion:Item value="item-2">
<twig:ui:Accordion:Header>هل يمكن الوصول إليه؟</twig:ui:Accordion:Header>
<twig:ui:Accordion:Panel>
نعم. يتبع نمط تصميم الأكورديون الخاص بـ WAI-ARIA.
</twig:ui:Accordion:Panel>
</twig:ui:Accordion:Item>
</twig:ui:Accordion>
API Reference
<twig:ui:Accordion>
| Prop | Type | Description |
|---|---|---|
multiple |
boolean |
Allow several items to stay open at the same time. Defaults to false |
disabled |
boolean |
Disable every item of the accordion. Defaults to false |
value |
string[] |
The values of the items open by default. Only the first one is kept when multiple is false. Defaults to [] |
dir |
'ltr' | 'rtl' |
The reading direction. Defaults to ltr |
name |
string |
The name attribute shared by the items in exclusive mode. Defaults to an auto-generated value |
as |
string |
The HTML tag to render. Defaults to div |
| Block | Description |
|---|---|
content |
The accordion items |
| Event | Detail | Description |
|---|---|---|
hui-accordion:change |
{ value: string[] } |
Fired when an item is toggled, with the values of the open items |
<twig:ui:Accordion:Item>
| Prop | Type | Description |
|---|---|---|
value |
string |
The unique value identifying the item. Required |
disabled |
boolean |
Disable this item. Defaults to false |
| Block | Description |
|---|---|
content |
The item header and panel |
<twig:ui:Accordion:Header>
Renders the <summary> element of the item.
| Prop | Type | Description |
|---|---|---|
as |
string |
The HTML tag of the heading wrapping the content. Defaults to h3 |
| Block | Description |
|---|---|
content |
The header label. The default indicator is appended unless the content brings its own <twig:ui:Accordion:Indicator> |
<twig:ui:Accordion:Panel>
| Prop | Type | Description |
|---|---|---|
as |
string |
The HTML tag to render. Defaults to div |
| Block | Description |
|---|---|
content |
The panel content |
<twig:ui:Accordion:Indicator>
| Prop | Type | Description |
|---|---|---|
as |
string |
The HTML tag to render. Defaults to span |
| Block | Description |
|---|---|
content |
The indicator icon. Defaults to a chevron |