View as Markdown

Accordion

Build vertically collapsing sections powered by Bootstrap's Collapse plugin.

100%
Loading...
<twig:Accordion id="faq-accordion">
    <twig:Accordion:Item id="faq-first" label="What is Symfony UX?" expanded>
        <strong>Symfony UX connects Symfony with modern frontend tools.</strong>
        It provides JavaScript packages, Twig components, and integrations designed to work naturally with Symfony applications.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="faq-second" label="Can I customize these components?">
        Yes. Pass HTML attributes and Bootstrap utility classes directly to the Twig components, or customize Bootstrap through Sass and CSS variables.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="faq-third" label="Does the accordion support multiple open items?">
        Set <code>alwaysOpen</code> on the root accordion to omit Bootstrap's parent constraint.
    </twig:Accordion:Item>
</twig:Accordion>

Installation

php bin/console ux:install accordion --kit bootstrap

Install the following Composer dependencies:

composer require symfony/ux-twig-component:^3.1 twig/extra-bundle twig/html-extra:^3.12.0

Install the following front-end dependencies:

npm install bootstrap@^5.3.0
# ...or with Symfony AssetMapper
php bin/console importmap:require bootstrap bootstrap/dist/css/bootstrap.min.css

Copy the following file(s) into your app:

{# @prop id string The unique identifier used to coordinate the accordion items. #}
{# @prop flush boolean Whether to remove outer borders and rounded corners. #}
{# @prop alwaysOpen boolean Whether multiple accordion items may remain open. #}
{# @block content The accordion items, typically `Accordion:Item` components. #}
{%- props id, flush = false, alwaysOpen = false -%}
{%- do provide(&#039;accordion.id&#039;, id) -%}
{%- do provide(&#039;accordion.alwaysOpen&#039;, alwaysOpen) -%}
&lt;div {{ attributes.defaults({
    id: id,
    class: html_classes({
        accordion: true,
        &#039;accordion-flush&#039;: flush,
    }),
}) }}&gt;
    {%- block content %}{% endblock -%}
&lt;/div&gt;
{# @prop id string The unique identifier used for the heading and collapsible panel. #}
{# @prop label string The text displayed in the accordion button. #}
{# @prop expanded boolean Whether the accordion item is initially expanded. #}
{# @prop headingTag &#039;h1&#039;|&#039;h2&#039;|&#039;h3&#039;|&#039;h4&#039;|&#039;h5&#039;|&#039;h6&#039; The heading element to render. #}
{# @block label The accordion button label. #}
{# @block content The content displayed inside the collapsible panel. #}
{%- props id, label = &#039;&#039;, expanded = false, headingTag = &#039;h2&#039; -%}
{%- set _accordion_id = inject(&#039;accordion.id&#039;, null) -%}
{%- set _accordion_always_open = inject(&#039;accordion.alwaysOpen&#039;, false) -%}
{%- set heading_id = id ~ &#039;-heading&#039; -%}
{%- set collapse_id = id ~ &#039;-collapse&#039; -%}
{%- set final_heading_tag = headingTag in [&#039;h1&#039;, &#039;h2&#039;, &#039;h3&#039;, &#039;h4&#039;, &#039;h5&#039;, &#039;h6&#039;] ? headingTag : &#039;h2&#039; -%}
&lt;div {{ attributes.defaults({class: &#039;accordion-item&#039;}) }}&gt;
    &lt;{{ final_heading_tag }} class=&quot;accordion-header&quot; id=&quot;{{ heading_id }}&quot;&gt;
        &lt;button
            class=&quot;{{ html_classes({
                &#039;accordion-button&#039;: true,
                collapsed: not expanded,
            }) }}&quot;
            type=&quot;button&quot;
            data-bs-toggle=&quot;collapse&quot;
            data-bs-target=&quot;#{{ collapse_id }}&quot;
            aria-expanded=&quot;{{ expanded ? &#039;true&#039; : &#039;false&#039; }}&quot;
            aria-controls=&quot;{{ collapse_id }}&quot;
        &gt;
            {%- block label %}{{ label }}{% endblock -%}
        &lt;/button&gt;
    &lt;/{{ final_heading_tag }}&gt;
    &lt;div
        id=&quot;{{ collapse_id }}&quot;
        class=&quot;{{ html_classes({
            &#039;accordion-collapse&#039;: true,
            collapse: true,
            show: expanded,
        }) }}&quot;
        aria-labelledby=&quot;{{ heading_id }}&quot;
        {% if not _accordion_always_open and _accordion_id is not null %}data-bs-parent=&quot;#{{ _accordion_id }}&quot;{% endif %}
    &gt;
        &lt;div class=&quot;accordion-body&quot;&gt;
            {%- block content %}{% endblock -%}
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

Usage

<twig:Accordion id="account-accordion">
    <twig:Accordion:Item id="account-profile" label="Profile" expanded>
        Update your personal details and contact information.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="account-security" label="Security">
        Review your password and two-factor authentication settings.
    </twig:Accordion:Item>
</twig:Accordion>

Accessibility

Choose a headingTag that fits the surrounding document hierarchy. Each heading contains a button with aria-expanded and aria-controls, and each panel references its heading with aria-labelledby.

Bootstrap's transition respects prefers-reduced-motion. Keep labels concise and do not hide essential information exclusively inside collapsed sections.

Examples

Render a conventional accordion where opening one item closes the currently open item.

100%
Loading...
<twig:Accordion id="accordion-example">
    <twig:Accordion:Item id="accordion-item-one" label="Accordion Item #1" expanded>
        <strong>This is the first item's accordion body.</strong>
        It is shown by default until the Collapse plugin updates the appropriate classes and ARIA state.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="accordion-item-two" label="Accordion Item #2">
        <strong>This is the second item's accordion body.</strong>
        It is hidden by default and can contain nearly any HTML content.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="accordion-item-three" label="Accordion Item #3">
        <strong>This is the third item's accordion body.</strong>
        Opening it closes the currently open item.
    </twig:Accordion:Item>
</twig:Accordion>

Flush

Remove outer borders and rounded corners for an edge-to-edge accordion.

100%
Loading...
<twig:Accordion id="accordion-flush-example" flush>
    <twig:Accordion:Item id="flush-item-one" label="Accordion Item #1">
        Placeholder content for this edge-to-edge accordion item.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="flush-item-two" label="Accordion Item #2">
        The flush style removes some borders and rounded corners.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="flush-item-three" label="Accordion Item #3">
        Items retain the same Collapse behavior and accessibility attributes.
    </twig:Accordion:Item>
</twig:Accordion>

Always open

Allow several items to remain expanded by omitting Bootstrap's parent constraint.

100%
Loading...
<twig:Accordion id="accordion-always-open" alwaysOpen>
    <twig:Accordion:Item id="always-open-one" label="Accordion Item #1" expanded>
        This item starts open and remains open when another item is expanded.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="always-open-two" label="Accordion Item #2">
        Multiple items can stay open because no <code>data-bs-parent</code> constraint is rendered.
    </twig:Accordion:Item>
    <twig:Accordion:Item id="always-open-three" label="Accordion Item #3">
        Each button still controls its own panel and synchronizes <code>aria-expanded</code>.
    </twig:Accordion:Item>
</twig:Accordion>

API Reference

<twig:Accordion>

Prop Type Default
id 
string -
flush 
boolean false
alwaysOpen 
boolean false
Block Description
content The accordion items, typically Accordion:Item components.

<twig:Accordion:Item>

Prop Type Default
id 
string -
label 
string ''
expanded 
boolean false
headingTag 
'h1'|'h2'|'h3'|'h4'|'h5'|'h6' 'h2'
Block Description
label The accordion button label.
content The content displayed inside the collapsible panel.