Accordion
Stacked disclosure sections that show one or more panels at a time.
import { Accordion } from "@merid/react";
export function Example() {
return (
<Accordion.Root type="single" defaultValue="billing">
<Accordion.Item value="billing">
<Accordion.Trigger>How does billing work?</Accordion.Trigger>
<Accordion.Content>You are billed monthly per active seat. Unused seats are credited.</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="export">
<Accordion.Trigger>Can I export my data?</Accordion.Trigger>
<Accordion.Content>Yes. Export any workspace as CSV or JSON from Settings.</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="sso" disabled>
<Accordion.Trigger>Do you support SSO? (Enterprise)</Accordion.Trigger>
<Accordion.Content>SAML and OIDC on the Enterprise plan.</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
}Import
import { Accordion } from "@merid/react";Anatomy
<Accordion.Root type="single">
<Accordion.Item value="a">
<Accordion.Trigger />
<Accordion.Content />
</Accordion.Item>
</Accordion.Root>- Root decides whether one (
type="single") or many (type="multiple") items can be open. - Item groups a trigger and its content under a unique
value. - Trigger is a
<button>wrapped in a heading (h3by default) with a plus icon. - Content is a
role="region"labelled by its trigger. Children only render while open.
Examples
Multiple, controlled
With type="multiple", value and onValueChange use a string[]. headingLevel sets the wrapping heading to fit the page outline.
const [open, setOpen] = useState<string[]>(["a"]);
<Accordion.Root type="multiple" value={open} onValueChange={setOpen} headingLevel={4}>
<Accordion.Item value="a">
<Accordion.Trigger>Shipping</Accordion.Trigger>
<Accordion.Content>Orders ship within two business days.</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="b">
<Accordion.Trigger>Returns</Accordion.Trigger>
<Accordion.Content>Return unused items within 30 days.</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="c">
<Accordion.Trigger>Warranty</Accordion.Trigger>
<Accordion.Content>Two years on all hardware.</Accordion.Content>
</Accordion.Item>
</Accordion.Root>Always one open
collapsible={false} on a single accordion prevents closing the open item.
<Accordion.Root type="single" defaultValue="one" collapsible={false}>
<Accordion.Item value="one">
<Accordion.Trigger>Step one: connect</Accordion.Trigger>
<Accordion.Content>Link your repository.</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="two">
<Accordion.Trigger>Step two: deploy</Accordion.Trigger>
<Accordion.Content>Push to main to deploy.</Accordion.Content>
</Accordion.Item>
</Accordion.Root>API reference
Root (single)
Accepts every <div> attribute except defaultValue and onChange.
| Prop | Type | Default | Description |
|---|---|---|---|
typeRequired | "single" | – | One item open at a time. |
value | string | – | Controlled open item ("" for none). |
defaultValue | string | – | Initially open item when uncontrolled. |
onValueChange | (value: string) => void | – | Called with the open item value ("" when all closed). |
collapsible | boolean | true | Allow closing the open item. |
headingLevel | 2 | 3 | 4 | 5 | 6 | 3 | Heading level wrapping each trigger. |
ref | Ref<HTMLDivElement> | – | Forwarded ref to the root element. |
Root (multiple)
| Prop | Type | Default | Description |
|---|---|---|---|
typeRequired | "multiple" | – | Any number of items open. |
value | string[] | – | Controlled open items. |
defaultValue | string[] | – | Initially open items when uncontrolled. |
onValueChange | (value: string[]) => void | – | Called with the open item values. |
headingLevel | 2 | 3 | 4 | 5 | 6 | 3 | Heading level wrapping each trigger. |
ref | Ref<HTMLDivElement> | – | Forwarded ref to the root element. |
Item
Accepts every <div> attribute.
| Prop | Type | Default | Description |
|---|---|---|---|
valueRequired | string | – | Unique value of this item. |
disabled | boolean | false | Prevent toggling. |
Trigger
Accepts every <button> attribute.
| Prop | Type | Default | Description |
|---|---|---|---|
ref | Ref<HTMLButtonElement> | – | Forwarded ref to the button. |
Content
Accepts every <div> attribute.
| Prop | Type | Default | Description |
|---|---|---|---|
ref | Ref<HTMLDivElement> | – | Forwarded ref to the region. |
Styling
| Class | Element |
|---|---|
.mrd-accordion | Root |
.mrd-accordion__item | Item |
.mrd-accordion__heading | Heading wrapping the trigger |
.mrd-accordion__trigger, .mrd-accordion__trigger-text | Trigger and its text |
.mrd-accordion__icon | Plus icon (rotates when open) |
.mrd-accordion__content | Content region |
| Attribute | On | Values |
|---|---|---|
data-state | Item, Trigger, Content | open, closed |
aria-expanded | Trigger | true, false |
aria-disabled="true", data-disabled | Trigger | when the item is disabled |
Accessibility
Follows the WAI-ARIA Accordion pattern. Each trigger sits in a heading, carries aria-expanded and aria-controls, and its region is labelled by the trigger. Disabled triggers stay reachable with Tab (with aria-disabled) so they are still announced, but arrow keys, Home and End skip them.
| Key | Action |
|---|---|
| Enter | Toggle the focused section. |
| Space | Toggle the focused section. |
| Tab | Move to the next focusable element, including inside open content. |
| ArrowDown | Next enabled trigger, wrapping. |
| ArrowUp | Previous enabled trigger, wrapping. |
| Home | First enabled trigger. |
| End | Last enabled trigger. |
Guidelines
Do
Use an accordion for secondary content people scan by heading — FAQs, settings groups, details.
Avoid
Hide content everyone needs to read, or put a single item in an accordion.
Do
Write triggers as the question or topic, so the page outline reads well.
Avoid
Nest accordions inside accordions.