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?

Yes. It adheres to the WAI-ARIA accordion design pattern.

Is it styled?

Yes. It ships with default styles that match the other HarmonyUI components.

Is it animated?

The chevron rotates smoothly, and motion is disabled for users who prefer reduced motion.
<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

Orders are shipped within 2 business days.

Returns

Free returns within 30 days of delivery.
<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

This item can be toggled.

Disabled item

You should not be able to open this one.

Another available item

Arrow-key navigation still reaches the disabled item, it just won't open.
<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?

Yes. The content of the indicator replaces the default chevron, and its classes let you adjust the open-state animation.

Does it still track the state?

The custom indicator receives the same 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

Place the indicator anywhere in the header; drop the default ms-auto to keep it next to the text.

It keeps the default chevron

Without content, the indicator renders 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?

Head to the billing settings and pick a new plan; the change is prorated.

Where can I find my invoices?

Invoices are listed in the billing settings and sent by email every month.

Security

All data is encrypted at rest and in transit.
<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?

We offer monthly and annual subscription plans. Billing is charged at the beginning of each cycle, and you can cancel anytime.

Is my data secure?

All data is encrypted in transit and at rest, with daily automatic backups.

What integrations do you support?

We integrate with the most popular tools out of the box, and expose a REST API for everything else.
<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.

هل يدعم الاتجاه من اليمين إلى اليسار؟

نعم. يتم محاذاة النص والمؤشر تلقائيًا حسب اتجاه القراءة.

هل يمكن الوصول إليه؟

نعم. يتبع نمط تصميم الأكورديون الخاص بـ WAI-ARIA.
<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