DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk4 min

How to Build an Accessible Accordion Component in React

Choose whether panels open independently or one at a time, then connect React state to semantic button triggers and their panels.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build a React accordion by choosing whether panels open independently or one at a time, storing that state in React, and connecting each native button trigger to its panel. Give every trigger an accurate aria-expanded value and an aria-controls reference to the panel it operates. The W3C Accordion Pattern defines the expected semantics and keyboard behavior; React’s state-sharing guidance shows how to coordinate items through a common parent.

Choose how the accordion should open

An accordion is a vertical set of interactive headings whose controls reveal or hide associated panels. Decide its open-state policy before writing the component: it determines what state to store and what users can do.

As an Amazon Associate I earn from qualifying purchases.

Behavior State to store Can every panel be closed? When it fits
Independent panels A collection of open item IDs Yes, unless you deliberately enforce another rule. Readers may need to compare or keep several answers visible.
One panel at a time One active item ID, or null when none is open Yes if toggling the active item sets the ID to null; otherwise one item can remain required open. The interface should show a single section at once.

For a controlled accordion, keep the state in a parent that needs to coordinate or control it, then pass each item its expanded value and a toggle callback. For a self-contained accordion, keep that state inside the accordion. React describes this choice as a trade-off between flexibility and configuration; see Sharing State Between Components.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build a single-open accordion

This example lets users close the open panel, so the initial state is null and clicking the active item returns it to null. Each item receives its expanded state from the parent; the same value drives both the button’s ARIA state and the panel’s visibility.

import { useState } from 'react';

function AccordionItem({ id, title, expanded, onToggle, children }) {
  const buttonId = `${id}-trigger`;
  const panelId = `${id}-panel`;

  return (
    <section>
      <h3>
        <button
          id={buttonId}
          type="button"
          aria-expanded={expanded}
          aria-controls={panelId}
          onClick={onToggle}
        >
          {title}
        </button>
      </h3>
      <div id={panelId} hidden={!expanded}>
        {children}
      </div>
    </section>
  );
}

function Accordion({ items }) {
  const [activeId, setActiveId] = useState(null);

  return (
    <div>
      {items.map((item) => {
        const expanded = activeId === item.id;

        return (
          <AccordionItem
            key={item.id}
            {...item}
            expanded={expanded}
            onToggle={() =>
              setActiveId(expanded ? null : item.id)
            }
          />
        );
      })}
    </div>
  );
}

Pass an items array with a unique, stable id and a title for each entry; the item’s remaining content becomes its children. The heading level in this example is h3: choose the level that fits the page outline, rather than copying it regardless of context. Keep item IDs unique so each generated trigger and panel ID—and each aria-controls relationship—is unique.

Allow multiple panels to stay open

For independent panels, store a collection of open IDs in the parent instead of a single active ID. Toggling an item adds or removes only that item’s ID; the item component and its button-panel markup can stay the same.

function MultiOpenAccordion({ items }) {
  const [openIds, setOpenIds] = useState(() => new Set());

  function toggle(id) {
    setOpenIds((current) => {
      const next = new Set(current);
      if (next.has(id)) {
        next.delete(id);
      } else {
        next.add(id);
      }
      return next;
    });
  }

  return (
    <div>
      {items.map((item) => {
        const expanded = openIds.has(item.id);

        return (
          <AccordionItem
            key={item.id}
            {...item}
            expanded={expanded}
            onToggle={() => toggle(item.id)}
          />
        );
      })}
    </div>
  );
}

This uses the functional form of the state setter so each update works from the latest state. If your design must always leave one panel open, initialize with that panel’s ID and prevent toggling the active item closed. The W3C pattern says that when an expanded panel cannot be collapsed, its trigger should use aria-disabled="true" to indicate that restriction.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use accessible trigger and panel semantics

The W3C pattern specifies a native button inside a heading for each accordion trigger. In the examples, aria-expanded receives the same boolean that controls the panel’s hidden attribute, so the announced state and visible state stay aligned. aria-controls points to the associated panel’s ID.

  • Keep the button’s type="button" so it does not accidentally submit a surrounding form.
  • Use Enter or Space to activate the focused button. Native buttons provide these behaviors without recreating keyboard interaction on a clickable div.
  • Tab and Shift+Tab move through focusable elements in normal page order. Keep links and other focusable content inside panels in that order.
  • Provide a visible focus style in the finished design, and verify that the trigger remains identifiable while keyboard focus is on it.

The panel’s region role is optional. Add role="region" and aria-labelledby only when making the panel a landmark is useful; applying region landmarks to every item in a large accordion can create landmark clutter. The W3C pattern specifically cautions against region roles when more than approximately six panels can be expanded simultaneously. The basic example therefore leaves the role off.

The pattern’s listed keyboard interaction is Enter, Space, Tab, and Shift+Tab. Arrow-key, Home, and End navigation are optional enhancements, not required behavior in that pattern; add them only if the component implements and documents them consistently.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Test the behavior before shipping

  • Activate every trigger with both a pointer and the keyboard; confirm it opens and, where allowed, closes its panel.
  • Check that only the intended panel changes in independent mode, or that opening a second item closes the first in single-open mode.
  • Inspect each rendered button: its aria-expanded value should match whether its associated panel is visible, and its aria-controls value should match that panel’s ID exactly.
  • Confirm focus is visible and that Tab and Shift+Tab follow the expected page order through buttons and any focusable panel content.

These checks describe expected implementation behavior, not a claim of browser or assistive-technology testing. Test the finished component in the application and environments you support.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.