Accordions group related content into collapsible sections. They support two modes: single (opening one panel closes the others) and multi (each panel toggles independently). The component requires JavaScript (assets/js/accordion.js).
Basic usage
Include the script on any page that uses accordions:
<script src="/assets/js/accordion.js"></script>
The script auto-initialises all containers on DOMContentLoaded. No manual setup needed.
Colour, typography, and spacing primitives that define the visual identity.
Purpose-driven aliases that map brand primitives to interface roles.
Structural building blocks for page composition — sections, containers, blocks.
<div class="accordion" data-accordion="multi">
<div class="accordion-item">
<button type="button" class="accordion-header"
aria-expanded="false" aria-controls="panel-1">
<span class="accordion-title">Heading text</span>
<div class="svg-icn" data-icon="add" aria-hidden="true"><!-- SVG --></div>
</button>
<div class="accordion-content" id="panel-1" role="region">
<div class="accordion-inner">
<div class="accordion-body">
Content goes here.
</div>
</div>
</div>
</div>
<!-- More .accordion-item elements -->
</div>
Structure
The three-element panel structure exists because of how the CSS grid animation works:
.accordion-content → grid container (grid-template-rows: 0fr → 1fr)
.accordion-inner → overflow clipper (overflow: hidden, min-height: 0)
.accordion-body → consumer content and padding live here
Why three elements? The grid row collapses to zero height, but padding on itself would bleed through the 0fr state and prevent full collapse. sits inside the clipping boundary, so its padding gets hidden when the panel is closed.
Rule: Never put padding or margin on . Always use for spacing.
Modes
Multi-open (default)
Each panel toggles independently. Set data-accordion="multi" or omit the attribute entirely.
<div class="accordion" data-accordion="multi">
Single-open
Opening one panel closes all siblings. Set data-accordion="single".
Opening this panel closes the others.
Only one panel can be open at a time.
Mutual exclusion keeps the interface focused.
<div class="accordion" data-accordion="single">
<!-- Items behave as mutual-exclusion group -->
</div>
JavaScript
Auto-initialisation
The script initialises all containers automatically on DOMContentLoaded. No manual setup is required.
Re-initialisation (SPA / Barba)
For single-page apps or Barba transitions, call initAccordion() after new content is injected:
if (typeof window.initAccordion === "function") {
window.initAccordion();
}
The function is safe to call multiple times — headers that are already bound are skipped via a data-accordion-bound guard.
Scoping
The component uses :scope > .accordion-item to select only direct-child items. This means nested accordions work correctly — each level manages its own items without interfering with inner or outer levels.
Nested accordions
Combine single-open and multi-open modes across nesting levels. The outer accordion controls top-level navigation while inner accordions allow independent exploration within each section.
Logo, colour palette, typography selection.
Tokens, components, layout primitives.
Front-end implementation, CMS integration, deployment.
<!-- Outer: single-open -->
<div class="accordion" data-accordion="single">
<div class="accordion-item">
<button type="button" class="accordion-header" aria-expanded="false">
<span class="accordion-title">Design</span>
<div class="svg-icn" data-icon="add" aria-hidden="true"><!-- SVG --></div>
</button>
<div class="accordion-content">
<div class="accordion-inner">
<div class="accordion-body">
<!-- Inner: multi-open -->
<div class="accordion" data-accordion="multi">
<div class="accordion-item">
<button type="button" class="accordion-header" aria-expanded="false"
aria-controls="inner-panel-1">
<span class="accordion-title">Brand identity</span>
<div class="svg-icn" data-icon="add" aria-hidden="true"><!-- SVG --></div>
</button>
<div class="accordion-content" id="inner-panel-1" role="region">
<div class="accordion-inner">
<div class="accordion-body">Content</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Keyboard interactions
| Key | Action |
|---|---|
Tab |
Move focus to the next focusable element (including accordion headers) |
Enter / Space |
Toggle the focused panel open or closed |
ArrowDown |
Move focus to the next header in the group (wraps to first) |
ArrowUp |
Move focus to the previous header in the group (wraps to last) |
Home |
Move focus to the first header in the group |
End |
Move focus to the last header in the group |
Arrow key navigation is scoped to each accordion level — nested accordions navigate independently.
Accessibility notes
- Each is a
<button>element, ensuring native keyboard support (Enter/Space) aria-expandedon headers is updated dynamically by the script ("false"when closed,"true"when open)aria-controlson the header links to theidon the corresponding- uses
role="region"as a landmark for assistive technology - The icon has
aria-hidden="true"— it's decorative, not informational - Focus-visible ring appears on keyboard navigation, suppressed on mouse click
Default styles
The design system provides sensible defaults so accordions work visually out of the box:
| Element | Default style |
|---|---|
border-top using |
|
border-bottom using |
|
padding: var(--space-m) 0, color: var(--text-primary) |
|
.accordion-header .svg-icn |
24x24, rotates 45deg when open (turns + into x) |
padding-bottom: var(--space-l) |
All values use semantic tokens, so they flip automatically in dark mode.
Icon
The accordion uses a wrapper with an inline SVG. The default icon is add (a plus sign) which rotates 45 degrees to form an x when the panel opens. To use a different icon, swap the SVG inside the wrapper — no CSS changes needed.
Overriding defaults
Use a wrapper class to adjust styles for a specific context:
.compact-accordion .accordion-header {
padding: var(--space-s) 0;
}
.compact-accordion .accordion-body {
padding-bottom: var(--space-m);
}
.borderless-accordion,
.borderless-accordion .accordion-item {
border: none;
}
CSS class reference
| Class | Purpose |
|---|---|
Container — border-top, requires data-accordion="single|multi" |
|
| Individual panel wrapper — border-bottom (direct child of ) | |
<button> trigger — flex layout, padding, colour |
|
Optional <span> for heading text inside the header |
|
.accordion-header .svg-icn |
Icon — 24x24, rotates 45deg on open. Swap the SVG for a different icon |
Grid container — animates grid-template-rows: 0fr → 1fr |
|
Overflow clipper — overflow: hidden, min-height: 0. Never apply padding here |
|
| Content wrapper — padding-bottom, consumer content lives here | |
| State class added to when the panel is expanded |
Accordion vs disclosure
Use accordion when content is grouped and benefits from single-open mutual exclusion, or when you need keyboard navigation between panels (ArrowUp/Down).
Use disclosure (<details>/<summary>) for standalone collapsible sections where multiple panels being open simultaneously is the expected behaviour and native HTML semantics are preferred.
Do / Don't
Do:
- Use for FAQ sections, grouped service descriptions, or settings panels
- Use single mode when content is mutually exclusive
- Provide clear, descriptive header text that tells users what's inside
- Use
aria-controlsand matchingidattributes for accessibility - Apply padding to , not
Don't:
- Don't use for navigation — use proper nav patterns
- Don't nest more than two levels deep
- Don't put critical content that all users need inside a collapsed panel
- Don't use when a simple list or visible layout would serve better
- Don't put padding or margin on — it will bleed through when collapsed