Accordion
Expandable sections driven by aria-expanded on the trigger. Height animation requires JS to set explicit pixel heights on each panel.
Basic
ACCORDION
All subsystems nominal. Uplink active.
No anomalies detected in the last 24h.
0 active threats. ICE integrity at 100%.
<div class="nf-accordion">
<div class="nf-accordion-item">
<button class="nf-accordion-trigger" aria-expanded="true" aria-controls="panel-1">SYSTEM_STATUS<span class="nf-accordion-icon"></span></button>
<div class="nf-accordion-content" id="panel-1">
<div class="nf-accordion-body">All subsystems nominal. Uplink active.</div>
</div>
</div>
<div class="nf-accordion-item">
<button class="nf-accordion-trigger" aria-expanded="false" aria-controls="panel-2">NETWORK_LOGS<span class="nf-accordion-icon"></span></button>
<div class="nf-accordion-content" id="panel-2" style="height: 0;">
<div class="nf-accordion-body">No anomalies detected in the last 24h.</div>
</div>
</div>
<div class="nf-accordion-item">
<button class="nf-accordion-trigger" aria-expanded="false" aria-controls="panel-3">THREAT_ANALYSIS<span class="nf-accordion-icon"></span></button>
<div class="nf-accordion-content" id="panel-3" style="height: 0;">
<div class="nf-accordion-body">0 active threats. ICE integrity at 100%.</div>
</div>
</div>
</div>JavaScript
Toggle aria-expanded and animate the content height:
document.querySelectorAll('.nf-accordion-trigger').forEach((trigger) => {
const panel = document.getElementById(trigger.getAttribute('aria-controls'));
if (!panel) return;
function setExpanded(expanded) {
trigger.setAttribute('aria-expanded', String(expanded));
// `inert` keeps links/buttons inside a collapsed panel out of the tab order
// and the accessibility tree, which `height: 0` alone does not.
panel.inert = !expanded;
panel.style.height = expanded ? panel.scrollHeight + 'px' : '0';
if (expanded && matchMedia('(prefers-reduced-motion: reduce)').matches) panel.style.height = 'auto';
}
// Release the fixed height after expanding so content can reflow/resize.
panel.addEventListener('transitionend', () => {
if (trigger.getAttribute('aria-expanded') === 'true') panel.style.height = 'auto';
});
const initiallyOpen = trigger.getAttribute('aria-expanded') === 'true';
setExpanded(initiallyOpen);
if (initiallyOpen) panel.style.height = 'auto'; // no transition runs on load
trigger.addEventListener('click', () => {
const expanded = trigger.getAttribute('aria-expanded') === 'true';
if (expanded) {
// Pin the current height first so the collapse can animate from it.
panel.style.height = panel.scrollHeight + 'px';
panel.offsetHeight; // force reflow
}
setExpanded(!expanded);
});
});
Class reference
| Class | Description |
|---|---|
nf-accordion |
Outer wrapper |
nf-accordion-item |
Individual collapsible section |
nf-accordion-trigger |
Clickable header button — set aria-expanded |
nf-accordion-icon |
CSS caret — flips when trigger has aria-expanded="true" |
nf-accordion-content |
Animated height wrapper — set height via JS |
nf-accordion-body |
Inner content with padding |