Accordion
Sections under headings that open and close: one at a time, or as many as the reader wants.
npm install cennge-ui, then import tailwindcss and cennge-ui/styles.css in your CSS. The stylesheet registers the package with Tailwind itself.- Click a question, or press Enter or Space on it: its answer opens and the open one closes.
- The arrow keys move between the questions; Home and End jump to the first and the last.
- Click the open question again to close it. With isCollapsible={false} one always stays open.
Field marks
- Import
import { Accordion, AccordionItem } from "cennge-ui";- Size
- 1.2 KB gzip for this import, its dependencies aside
- Pattern
- WAI-ARIA accordion: each title is a
<button>in an<h3> - Keyboard
- Enter SpaceArrow up Arrow down HomeEnd
- State
- Controlled with
valueandonValueChange, or uncontrolled withdefaultValue - SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Motion
- The panel slides open and closed; with reduced motion it appears at once
- Tests
- Unit tests
- Source
- src/components/accordion
When to use#
An Accordion shortens a long page of sections that are read one at a time: questions and answers, groups of settings, details under a summary. Each heading stays in view, so the reader sees what is there before opening it. When every section matters to everyone, leave them open as plain headings and text.
Similar#
- Tabs show one panel in place of another, from a row of names; accordion sections open in the flow of the page and can be open together.
- Card sets content apart but never hides it.
Several open at once#
With selectionMode="multiple" opening one section leaves the others as they are, and value is an array. variant="splitted" turns each item into a card of its own; startContent puts an icon before the title.
import { Accordion, AccordionItem } from "cennge-ui";
function WorkflowIcon() {
return (
<svg viewBox="0 0 24 24" className="size-5 text-muted" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<circle cx="6" cy="6" r="2.5" />
<circle cx="18" cy="18" r="2.5" />
<path d="M6 8.5V13a5 5 0 0 0 5 5h4.5" strokeLinecap="round" />
</svg>
);
}
export default function Workflows() {
return (
<Accordion selectionMode="multiple" variant="splitted" defaultValue={["ci"]} className="max-w-lg">
<AccordionItem value="ci" title="CI" subtitle="On every push and pull request" startContent={<WorkflowIcon />}>
Typecheck, unit tests, build, publint and are-the-types-wrong; the unit tests run on React 17, 18 and 19.
</AccordionItem>
<AccordionItem value="e2e" title="End-to-end" subtitle="Playwright" startContent={<WorkflowIcon />}>
Chromium, Firefox and WebKit, on desktop and phone profiles, with axe in both themes.
</AccordionItem>
<AccordionItem value="release" title="Release" subtitle="changesets" startContent={<WorkflowIcon />}>
Versions and the changelog come from changeset files; npm trusted publishing does the rest.
</AccordionItem>
</Accordion>
);
}Controlled, always one open#
In single mode value is a string. isCollapsible={false} stops the open section from closing, so there is always one; its button then carries aria-disabled, so screen readers know it will not close. A disabled item keeps its place and title, but neither a click nor the keyboard opens it.
Open: general
import { useState } from "react";
import { Accordion, AccordionItem } from "cennge-ui";
export default function RepositorySettings() {
const [section, setSection] = useState("general");
return (
<div className="flex w-full max-w-lg flex-col gap-3">
<Accordion variant="bordered" value={section} onValueChange={setSection} isCollapsible={false}>
<AccordionItem value="general" title="General">
Name, description and the default branch of payload-cache.
</AccordionItem>
<AccordionItem value="access" title="Access">
Who can push to main, and who only opens pull requests.
</AccordionItem>
<AccordionItem value="danger" title="Danger zone" subtitle="Only the owner can open it" isDisabled>
Transfer or delete the repository.
</AccordionItem>
</Accordion>
<p className="text-sm text-muted">Open: {section}</p>
</div>
);
}Props#
Accordion#
AccordionItem#
Also takes every attribute of <div>, except title, which mean something else here.