Skip to content

Find what you need, instantly

Search components, ARIA roles & attributes, and WCAG criteria.

DisclosureFlagship guide

Accordion

A vertically stacked set of headers that toggle the visibility of associated content panels.

Last verified against WCAG 2.2 and WAI-ARIA APG 1.2 on 2026-07-02.

Implementation

Orders ship within 2 business days via standard carrier.

accordion-pattern.tsx
function AccordionPattern() {
  const [openIds, setOpenIds] = useState(new Set(["shipping"]));
  const headerRefs = useRef({});

  function toggle(id) {
    setOpenIds(prev => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  }

  function onKeyDown(e, index) {
    let nextIndex = null;
    if (e.key === "ArrowDown") nextIndex = (index + 1) % SECTIONS.length;
    if (e.key === "ArrowUp") nextIndex = (index - 1 + SECTIONS.length) % SECTIONS.length;
    if (e.key === "Home") nextIndex = 0;
    if (e.key === "End") nextIndex = SECTIONS.length - 1;
    if (nextIndex !== null) {
      e.preventDefault();
      headerRefs.current[SECTIONS[nextIndex].id]?.focus();
    }
  }

  return SECTIONS.map((section, i) => {
    const expanded = openIds.has(section.id);
    return (
      <h3 key={section.id}>
        <button
          ref={el => (headerRefs.current[section.id] = el)}
          aria-expanded={expanded}
          aria-controls={`panel-${section.id}`}
          id={`header-${section.id}`}
          onClick={() => toggle(section.id)}
          onKeyDown={e => onKeyDown(e, i)}
        >
          {section.title}
        </button>
        <div
          id={`panel-${section.id}`}
          role="region"
          aria-labelledby={`header-${section.id}`}
          hidden={!expanded}
        >
          {section.body}
        </div>
      </h3>
    );
  });
}

Live demo

Orders ship within 2 business days via standard carrier.

Required roles, states & properties

Required roles, states, and properties
ElementAttributeWhy
Header <button>aria-expandedCommunicates whether this section's panel is currently visible — announced as "expanded" or "collapsed."
Header <button>aria-controlsAssociates the header with the panel id it toggles, so AT can relate the two even though they aren't nested.
Panelrole="region" + aria-labelledbyLets a screen reader user jump directly to an open panel via landmark/region navigation, labeled by its header.
Header wrapperNative heading element (h3)Keeps the accordion navigable via a screen reader's heading list, independent of the ARIA toggle semantics.

Keyboard interaction model

Keyboard interaction model
KeyBehavior
Tab / Shift+TabMoves focus into and out of the accordion, stopping only on header buttons (collapsed panels are not in the tab order).
Enter / SpaceToggles the focused header's panel open or closed.
Down ArrowMoves focus to the next header.
Up ArrowMoves focus to the previous header.
Home / EndMoves focus to the first / last header.

Focus management rules

  • Only header buttons are ever in the Tab order — panel content is never reachable while its panel is collapsed.
  • Toggling a panel never moves focus away from its header.
  • Arrow Up/Down/Home/End move focus directly between headers, independent of open/closed state.

WCAG 2.2 success criteria mapping

WCAG 2.2 success criteria that apply to this component
SCNameLevelWhy it applies
1.3.1Info and RelationshipsAStructure and relationships conveyed visually are also programmatically determinable.
2.1.1KeyboardAAll functionality is operable through a keyboard interface with no specific timing.
2.4.6Headings and LabelsAAHeadings and labels describe topic or purpose.
2.4.7Focus VisibleAAAny keyboard-operable UI has a visible focus indicator.
4.1.2Name, Role, ValueAFor all UI components, name, role, and value are programmatically determinable; states and changes are announced.

References