Lists
Accordion
A stack of titles that each open their own content, for FAQs and long settings pages. One item opens at a time unless you pass multiple. Panels grow to their measured height with CSS, titles sit inside headings, closed content is in the server HTML and found by the page search of browsers that support hidden="until-found", and rows are 44px tall on touch screens.
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/accordion#v1.2.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/accordionIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/accordion#v1.2.13 ourostack/ouro-ui/accordion-tests#v1.2.13import { Accordion } from "@/components/ouro/accordion/accordion";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
<Accordion
items={[
{ value: "storage", title: "How long does it keep?", content: "Three days in the fridge." },
{ value: "freeze", title: "Can I freeze it?", content: "Yes, for up to a month." },
]}
/>
<Accordion multiple defaultValue={["storage"]} items={items} />Use instead when
- Use
Collapsibleinstead for a single section that opens and closes. - Use
Tabsinstead when exactly one section always shows and the titles fit in a row.
Props
The props the modes share. Type: AccordionProps.
className- Type:
string - Layout only: margins, width, grid placement (
mt-6). Appearance classes warn in development. disabled- Type:
boolean - Keeps every item from opening or closing.
headingLevel- Type:
2 | 3 | 4 | 5 | 6 - Default:
3 - The outline level of each title, so the items sit in the page's heading structure:
headingLevel={2}renders<h2>. items(required)- Type:
readonly AccordionItem[] - The items, in order.
ref- Type:
Ref<HTMLDivElement> - Reaches the rendered element.
Modes
AccordionProps is a union: pass the props of exactly one mode. Mixing them is a type error.
Multiple: AccordionMultipleProps
Several items open at once. The value is the list of open items.
defaultValue- Type:
readonly string[] - Default: []
- The items open first, when the component keeps its own state.
multiple(required)- Type:
true - Lets several items be open at once, and makes the value a list.
onValueChange- Type:
((value: string[]) => void) - Called with the open items' values whenever one opens or closes.
value- Type:
readonly string[] - The open items' values, when you control them. Pair it with
onValueChange.
Single: AccordionSingleProps
One item open at a time (the default). The value is the open item's, or null when all are closed.
defaultValue- Type:
string | null - Default: none
- The item open first, when the component keeps its own state.
multiple- Type:
false - Lets several items be open at once, and makes the value a list.
onValueChange- Type:
((value: string | null) => void) - Called with the open item's value, or
nullwhen the person closes it. value- Type:
string | null - The open item's value, or
nullfor none, when you control it. Pair it withonValueChange.
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
collapsible: Not a prop. An open item always closes when its title is pressed again.onChange: Not a prop. Ouro UI usesonValueChange:<Accordion onValueChange={(value) => …}>.type: Not a prop. One item opens at a time by default; for several, passmultiple:<Accordion multiple>.
className is for layout only
Pass margins, width and grid placement (mt-4, w-full, col-span-2). Your classes always win over Ouro UI's: Accordion merges className last with cn (tailwind-merge), so a class that conflicts with its own replaces it.layout-classes
Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.
Alignment classes (text-center) warn too: set alignment on a parent element.
Types
AccordionItem
One item: a title that opens and closes its content.
interface AccordionItem {
/**
* Identifies the item: `value`, `defaultValue` and `onValueChange` use it. Unique within the
* accordion. @default the item's index, as a string ("0", "1", …)
*/
value?: string | undefined;
/**
* The title, always visible. It is the button that opens the item, inside a heading.
*/
title: ReactNode;
/** What shows while the item is open. */
content: ReactNode;
/** Shows the item but keeps it from opening or closing. */
disabled?: boolean | undefined;
}Claim sources
- layout-classes: test:registry/ouro/components/ouro/accordion/accordion.test.tsx::Accordion > lets app layout classes win
verify:testExists(registry/ouro/components/ouro/accordion/accordion.test.tsx::Accordion > lets app layout classes win)