NETFRAME_DOCS // ACCORDION
GITHUB

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