Build an accessible Accordion with aria-expanded and aria-controls
Accordion takes items, a list of { id, title, content }, and shows each title as a <button> inside an <h3>, with its content in a panel under it. Every panel starts closed. Clicking a title opens its panel, and clicking it again closes it. Panels open and close on their own: opening one leaves the others as they are.
Make it work with a keyboard and a screen reader. Each button has aria-expanded ("true" or "false") and aria-controls set to its panel's id. Each panel is a <div role="region"> with aria-labelledby set to its button's id. A closed panel stays in the page with the hidden attribute, so aria-controls always points at an element. Two accordions on one page must not share any id.
<Accordion
items={[
{ id: "delivery", title: "How long does delivery take?", content: "Two to three working days." },
{ id: "returns", title: "Can I return an opened tin?", content: "Yes, within 30 days." },
]}
/>
import { useState } from "react";
export default function Accordion({ items }) {
const [openIds, setOpenIds] = useState([]);
function toggle(id) {
setOpenIds((ids) => (ids.includes(id) ? ids.filter((openId) => openId !== id) : [...ids, id]));
}
return (
<div>
{items.map((item) => (
<div key={item.id}>
<h3 onClick={() => toggle(item.id)}>{item.title}</h3>
{openIds.includes(item.id) && <div>{item.content}</div>}
</div>
))}
</div>
);
}
- each title is a button inside a heading
- every panel starts closed, and the buttons say so
- a click opens a panel, and a second click closes it
- panels open and close on their own
- aria-controls points at the panel, which is named by its button
- a closed panel stays in the page, hidden
- works from the keyboard
- two accordions on one page share no ids
The answer
Write it yourself first. Then compare it with a reference solution and the checks it passes.
Checking your sign-in…