Accordion
Build vertically collapsing sections powered by Bootstrap's Collapse plugin.
<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('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>
{# @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.
<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.
<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.
<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 The unique identifier used to coordinate the accordion items.
|
string |
- |
flush Whether to remove outer borders and rounded corners.
|
boolean |
false |
alwaysOpen Whether multiple accordion items may remain open.
|
boolean |
false |
| Block | Description |
|---|---|
content |
The accordion items, typically Accordion:Item components. |
<twig:Accordion:Item>
| Prop | Type | Default |
|---|---|---|
id The unique identifier used for the heading and collapsible panel.
|
string |
- |
label The text displayed in the accordion button.
|
string |
'' |
expanded Whether the accordion item is initially expanded.
|
boolean |
false |
headingTag The heading element to render.
|
'h1'|'h2'|'h3'|'h4'|'h5'|'h6' |
'h2' |
| Block | Description |
|---|---|
label |
The accordion button label. |
content |
The content displayed inside the collapsible panel. |