javascript 63 lines · 8 steps

Building a compound Accordion in React

Two React contexts let an Accordion share open state and per-item IDs across loosely coupled subcomponents.

Explained by highlit
1import { createContext, useContext, useState, useId } from "react";
2 
3const AccordionContext = createContext(null);
4const ItemContext = createContext(null);
5 
6export function Accordion({ children, allowMultiple = false }) {
7 const [openIds, setOpenIds] = useState(() => new Set());
8 
9 const toggle = (id) => {
10 setOpenIds((prev) => {
11 const next = new Set(allowMultiple ? prev : []);
12 if (prev.has(id)) next.delete(id);
13 else next.add(id);
14 return next;
15 });
16 };
17 
18 return (
19 <AccordionContext.Provider value={{ openIds, toggle }}>
20 <div className="accordion">{children}</div>
21 </AccordionContext.Provider>
22 );
23}
24 
25Accordion.Item = function AccordionItem({ children }) {
26 const id = useId();
27 return (
28 <ItemContext.Provider value={id}>
29 <div className="accordion-item">{children}</div>
30 </ItemContext.Provider>
31 );
32};
33 
34Accordion.Header = function AccordionHeader({ children }) {
35 const id = useContext(ItemContext);
36 const { openIds, toggle } = useContext(AccordionContext);
37 const isOpen = openIds.has(id);
38 
39 return (
40 <button
41 type="button"
42 className="accordion-header"
43 aria-expanded={isOpen}
44 aria-controls={`panel-${id}`}
45 onClick={() => toggle(id)}
46 >
47 {children}
48 <span aria-hidden>{isOpen ? "\u2212" : "+"}</span>
49 </button>
50 );
51};
52 
53Accordion.Panel = function AccordionPanel({ children }) {
54 const id = useContext(ItemContext);
55 const { openIds } = useContext(AccordionContext);
56 if (!openIds.has(id)) return null;
57 
58 return (
59 <div id={`panel-${id}`} role="region" className="accordion-panel">
60 {children}
61 </div>
62 );
63};
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Compound components share state through context instead of prop drilling, keeping the public API declarative.
  2. 2A Set in state cleanly models which items are open and toggles between single- and multi-open modes.
  3. 3Deriving aria-expanded and aria-controls from the same state keeps the UI and accessibility tree in sync.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a compound Accordion in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code