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:

templates/components/Accordion.html.twig
{# @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('accordion.id', id) -%}
{%- do provide('accordion.alwaysOpen', alwaysOpen) -%}
<div {{ attributes.defaults({
    id: id,
    class: html_classes({
        accordion: true,
        'accordion-flush': flush,
    }),
}) }}>
    {%- block content %}{% endblock -%}
</div>
templates/components/Accordion/Item.html.twig
{# @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 'h1'|'h2'|'h3'|'h4'|'h5'|'h6' The heading element to render. #}
{# @block label The accordion button label. #}
{# @block content The content displayed inside the collapsible panel. #}
{%- props id, label = '', expanded = false, headingTag = 'h2' -%}
{%- set _accordion_id = inject('accordion.id', null) -%}
{%- set _accordion_always_open = inject('accordion.alwaysOpen', false) -%}
{%- set heading_id = id ~ '-heading' -%}
{%- set collapse_id = id ~ '-collapse' -%}
{%- set final_heading_tag = headingTag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] ? headingTag : 'h2' -%}
<div {{ attributes.defaults({class: 'accordion-item'}) }}>
    <{{ final_heading_tag }} class="accordion-header" id="{{ heading_id }}">
        <button
            class="{{ html_classes({
                'accordion-button': true,
                collapsed: not expanded,
            }) }}"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#{{ collapse_id }}"
            aria-expanded="{{ expanded ? 'true' : 'false' }}"
            aria-controls="{{ collapse_id }}"
        >
            {%- block label %}{{ label }}{% endblock -%}
        </button>
    </{{ final_heading_tag }}>
    <div
        id="{{ collapse_id }}"
        class="{{ html_classes({
            'accordion-collapse': true,
            collapse: true,
            show: expanded,
        }) }}"
        aria-labelledby="{{ heading_id }}"
        {% if not _accordion_always_open and _accordion_id is not null %}data-bs-parent="#{{ _accordion_id }}"{% endif %}
    >
        <div class="accordion-body">
            {%- block content %}{% endblock -%}
        </div>
    </div>
</div>

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.