Accordion with Opt-Out Panels

Up/Down arrows navigate between accordion headers. Panel content is opted out of arrow navigation with focusgroup="none", but interactive elements inside panels remain reachable via Tab.

focusgroup="toolbar block" role="group"
Try it: Use to move between accordion headers. Click a header or press Enter/Space to expand/collapse. Press Tab to reach interactive content inside expanded panels.

The focusgroup attribute adds declarative arrow-key navigation to composite widgets. Try these documentation links and . They're still reachable via Tab, but arrow keys skip right over this panel.

View source
<div focusgroup="toolbar block" role="group"
     aria-label="Accordion sections" class="accordion">
    <h3>
        <button id="acc-btn1" type="button" class="expanded" aria-expanded="true"
                aria-controls="acc-panel1">Section 1: Getting
                     Started</button>
    </h3>
    <div focusgroup="none" role="region" id="acc-panel1"
         class="panel" aria-labelledby="acc-btn1">
        <p>Panel content with <a href="#">links</a> and
        <input type="text" placeholder="type here"> that remain
        tabbable but are skipped by arrow keys.</p>
    </div>

    <h3>
        <button id="acc-btn2" type="button" aria-expanded="false"
                aria-controls="acc-panel2">Section 2:
                     Configuration</button>
    </h3>
    <div focusgroup="none" role="region" id="acc-panel2"
         class="panel" hidden aria-labelledby="acc-btn2">
        <p>More panel content...</p>
    </div>

    <h3>
        <button id="acc-btn3" type="button" aria-expanded="false"
                aria-controls="acc-panel3">Section 3: Advanced
                     Usage</button>
    </h3>
    <div focusgroup="none" role="region" id="acc-panel3"
         class="panel" hidden aria-labelledby="acc-btn3">
        <p>Even more content...</p>
    </div>
    <!-- more sections ... -->
</div>

Long Content: A Possible Mitigation

Because focusgroup arrow keys jump directly between headers, long panel content can be missed entirely. If a panel has more content than fits on screen, the user may never see it when arrowing through headers.

One possible mitigation: When a section is expanded, move focus into the panel. The panel is a focusable scrollable region (tabindex="0") with focusgroup="none", so arrow keys scroll its content instead of jumping to the next header. The user can press Shift+Tab to return to the accordion headers when done reading. Other approaches (such as requiring an activation step before navigating away, showing a "skip to next section" link inside the panel, or avoiding focusgroup entirely for content-heavy accordions) may be more appropriate depending on the use case.

focusgroup="toolbar block" role="group"
Try it: Click a header or press Enter/Space to expand. Focus moves into the panel. Use to scroll the long content. Press Shift+Tab to return to the accordion headers.

View source
<div focusgroup="toolbar block" role="group"
     aria-label="Articles" class="accordion accordion-focus-into">
    <h3>
        <button id="long-btn1" type="button" aria-expanded="false"
                aria-controls="long-panel1">The Focusgroup
                     Problem</button>
    </h3>
    <div focusgroup="none" role="region" tabindex="0"
         id="long-panel1" class="panel panel-scrollable" hidden
         aria-labelledby="long-btn1">
        <p>Long panel content goes here...</p>
    </div>
    <!-- more sections ... -->
</div>

<!-- accordion.js handles all toggle and focus-into logic.
     The key excerpt for focus-into behavior: -->
<script>
// excerpt from accordion.js: the focus-into enhancement
// "accordion" is the current .accordion element from the outer forEach loop
document.querySelectorAll(".accordion").forEach(function (accordion) {
  var focusInto = accordion.classList.contains("accordion-focus-into");
  accordion.querySelectorAll("h3 button[aria-controls]").forEach(function (btn) {
    btn.addEventListener("click", function () {
        var expanded = btn.getAttribute("aria-expanded") === "true";
        btn.setAttribute("aria-expanded", String(!expanded));
        btn.classList.toggle("expanded", !expanded);
        var panel = document.getElementById(btn.getAttribute("aria-controls"));
        if (panel) {
            panel.hidden = expanded;
            // On expand: move focus into the panel so arrow keys scroll
            if (!expanded && focusInto) panel.focus();
        }
    });
  });
});
</script>

Key Point: focusgroup="none"

The none value opts an element and its subtree out of the ancestor focusgroup. The none value is what makes the accordion pattern work: