Accordion
A group of expand/collapse items with coordination. Composes the `disclosure` family (trigger, panel, content). The only things this component declares for itself are Root, Item and Header — the coordination points.
npx shadcn@latest add @fragiola/accordionShow code
"use client";
import { ChevronDownIcon } from "lucide-react";
import type { ReactNode } from "react";
import { Accordion } from "#/ui/accordion";
// The floor is palette-surface — a neutral surface that belongs to no
// palette. The accordion shows its real axes: open/closed items, multiple
// vs single, and a disabled item. No palette showcasing — an accordion is
// not "a blue accordion".
//
// The root carries a width. The preview centres its demo with flex, so a
// widthless root shrinks to its closed triggers and then stretches to the
// panel's paragraph on open — the whole demo jumped. Width comes from the
// container, never from the item.
export default function AccordionDemo() {
return (
<div className="palette-surface flex w-full max-w-xl flex-col gap-6 rounded-lg border border-palette-line bg-palette-base p-6">
{/* Multiple open (default) — multiple={true} is the default */}
<Row label="multiple open (default)">
<Accordion.Root>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>
Is it accessible?
<ChevronDownIcon className="size-4 shrink-0 transition-transform duration-200 group-data-panel-open:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>
<Accordion.Content>
Yes. It adheres to the WAI-ARIA design pattern
and uses Base UI's Accordion primitive for
keyboard navigation, focus management and
aria-expanded.
</Accordion.Content>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>
Is it styled?
<ChevronDownIcon className="size-4 shrink-0 transition-transform duration-200 group-data-panel-open:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>
<Accordion.Content>
Yes. The trigger, panel and content come from
the disclosure family — zero variants, shared
with the collapsible component.
</Accordion.Content>
</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>
</Row>
{/* Single open */}
<Row label="single open">
<Accordion.Root multiple={false}>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>
First
<ChevronDownIcon className="size-4 shrink-0 transition-transform duration-200 group-data-panel-open:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>
<Accordion.Content>
Only one item open at a time.
</Accordion.Content>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item>
<Accordion.Header>
<Accordion.Trigger>
Second
<ChevronDownIcon className="size-4 shrink-0 transition-transform duration-200 group-data-panel-open:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>
<Accordion.Content>
Opening this one closes the first.
</Accordion.Content>
</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>
</Row>
{/* Disabled item */}
<Row label="disabled item">
<Accordion.Root>
<Accordion.Item disabled>
<Accordion.Header>
<Accordion.Trigger>
Disabled item
<ChevronDownIcon className="size-4 shrink-0 transition-transform duration-200 group-data-panel-open:rotate-180" />
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Panel>
<Accordion.Content>
This item is disabled — pointer events are
zeroed and opacity is reduced.
</Accordion.Content>
</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>
</Row>
</div>
);
}
function Row({ label, children }: { label: string; children: ReactNode }) {
return (
<div className="flex flex-col gap-2">
<span className="text-xs font-mono text-palette-accent/85">
{label}
</span>
{children}
</div>
);
}
Style families
accordion and collapsible render from the same disclosure source — trigger, panel, content. The only difference is coordination: an accordion coordinates multiple items (one open at a time, or many); a collapsible is a single section. That is behaviour from the primitive, not a style variant.
The chevron
The trigger is a group. Put a chevron inside with transition-transform duration-200 group-data-[panel-open]:rotate-180 and it rotates when the panel opens. The rotation is driven by data-panel-open, which the primitive emits on the trigger.
Parts
| Part | Source |
|---|---|
Root / Item / Header | Base UI Accordion |
Trigger | disclosure.trigger() |
Panel | disclosure.panel() |
Content | disclosure.content() |
Tooltip
A transient text label that appears on hover or focus. Composes the `popup.tooltip` member — a new member of the `popup` family, not a variant of `popup.content`.
Collapsible
A single expand/collapse section. Composes the `disclosure` family (trigger, panel, content). An accordion is a group of collapsibles with coordination; a collapsible is one section on its own.