Stack related sections and reveal their content as needed. Keep one or several panels open, control the state yourself or leave it internal, and animate height with CSS.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger>What ships in the box?</AccordionTrigger> <AccordionContent> Source files you own. Install a component, then edit it like any other file in your app. </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger>Will this lock me to one stack?</AccordionTrigger> <AccordionContent> It targets React. The rest of your tooling stays yours — bundler, router, and styling approach included. </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger>How do I pull a new piece in?</AccordionTrigger> <AccordionContent> Run the CLI against the registry. The component lands in your repo ready to customize. </AccordionContent> </AccordionItem> </Accordion> )}Installation
npx @arctis-sh/@arctis-sh/ui@latest add accordionUsage
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"<Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger>What ships in the box?</AccordionTrigger> <AccordionContent> You get the source files. Install the component, then make it yours. </AccordionContent> </AccordionItem></Accordion>Composition
Accordion
├── AccordionItem
│ ├── AccordionTrigger
│ └── AccordionContent
└── AccordionItem
├── AccordionTrigger
└── AccordionContentGive each AccordionItem a stable value. Put its label in AccordionTrigger and its body in AccordionContent.
Basic
Keep one panel open at a time. With collapsible, selecting the open trigger closes it.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger>What ships in the box?</AccordionTrigger> <AccordionContent> Source files you own. Install a component, then edit it like any other file in your app. </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger>Will this lock me to one stack?</AccordionTrigger> <AccordionContent> It targets React. The rest of your tooling stays yours — bundler, router, and styling approach included. </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger>How do I pull a new piece in?</AccordionTrigger> <AccordionContent> Run the CLI against the registry. The component lands in your repo ready to customize. </AccordionContent> </AccordionItem> </Accordion> )}Multiple
Set type="multiple" to keep several panels open. In this mode, defaultValue and value accept a string array.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="multiple" defaultValue={["item-1", "item-2"]}> <AccordionItem value="item-1"> <AccordionTrigger>Keyboard and screen readers</AccordionTrigger> <AccordionContent> Each trigger is a button with expanded state, and panels are exposed as labelled regions. </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger>Open more than one panel</AccordionTrigger> <AccordionContent> Pass type="multiple" when sections should stay open side by side. </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger>Motion without extra packages</AccordionTrigger> <AccordionContent> Height animates with CSS grid rows — no Framer or Radix required. </AccordionContent> </AccordionItem> </Accordion> )}Disabled
Add disabled to an AccordionItem when that panel should not open.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger>Available right now</AccordionTrigger> <AccordionContent> This panel opens and closes like any other item. </AccordionContent> </AccordionItem> <AccordionItem value="item-2" disabled> <AccordionTrigger>Coming in a later drop</AccordionTrigger> <AccordionContent> Disabled items stay closed and cannot be toggled. </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger>Where do tokens live?</AccordionTrigger> <AccordionContent> Theme values sit in your CSS variables — tweak once, every surface follows. </AccordionContent> </AccordionItem> </Accordion> )}Plus / Minus
Set indicator="plus" on AccordionTrigger for a plus that becomes a minus when open. The default is chevron.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger indicator="plus"> Expand with a plus mark </AccordionTrigger> <AccordionContent> The vertical stroke rotates into the horizontal one so plus becomes minus without swapping icons. </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger indicator="plus"> Same behavior, different cue </AccordionTrigger> <AccordionContent> Useful when a chevron feels too navigational and you want an add / remove hint instead. </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger indicator="plus"> Still one accordion </AccordionTrigger> <AccordionContent> indicator only changes the end mark — open state and content stay the same. </AccordionContent> </AccordionItem> </Accordion> )}Icons
Pass icon to AccordionTrigger to place an icon before the title. Expanded content stays aligned with the title text.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger icon={<BoxIcon />}> Packages and installs </AccordionTrigger> <AccordionContent> Drop components into your project tree. No opaque runtime package sitting between you and the markup. </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger icon={<CodeIcon />}> Edit the source directly </AccordionTrigger> <AccordionContent> Rename props, restyle triggers, or strip pieces you do not need — it is just React. </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger icon={<SparkIcon />}> Shape it to your product </AccordionTrigger> <AccordionContent> Pair icons, copy, and layout chrome however your docs or app need. </AccordionContent> </AccordionItem> </Accordion> )}Outlined
Add a border around the root when the group needs a clearer edge. This changes the presentation, not the behavior.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="single" collapsible defaultValue="item-1" className="rounded-md border border-foreground/10 px-3" > <AccordionItem value="item-1"> <AccordionTrigger>Give it a hard edge</AccordionTrigger> <AccordionContent> A light border frames the stack when the page needs a clearer block. </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger>Keep pieces separate</AccordionTrigger> <AccordionContent> Trigger, content, and item stay independent so chrome never leaks into behavior. </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger>Same API underneath</AccordionTrigger> <AccordionContent> Outlined is just className on the root — nothing new to learn. </AccordionContent> </AccordionItem> </Accordion> )}Separated
Outline each item and add gap to Accordion to present the panels as separate blocks.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="single" collapsible className="gap-2" defaultValue="item-1"> <AccordionItem value="item-1" className="rounded-md border border-foreground/10 px-3 last:!border-b" > <AccordionTrigger>Each item is its own box</AccordionTrigger> <AccordionContent> Border and radius sit on AccordionItem — the root only adds gap. </AccordionContent> </AccordionItem> <AccordionItem value="item-2" className="rounded-md border border-foreground/10 px-3 last:!border-b" > <AccordionTrigger>Space between the cards</AccordionTrigger> <AccordionContent> gap-2 on Accordion keeps panels from touching without extra wrappers. </AccordionContent> </AccordionItem> <AccordionItem value="item-3" className="rounded-md border border-foreground/10 px-3 last:!border-b" > <AccordionTrigger>Combine with icons or plus</AccordionTrigger> <AccordionContent> Still the same component — stack className with icon and indicator. </AccordionContent> </AccordionItem> </Accordion> )}Card
Place the accordion in a Card when it needs a title and a little context.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"import { Card, CardContent, CardDescription, CardHeader, CardTitle,} from "@/components/ui/card"export function Example() { return ( <Card className="w-full border-0"> <CardHeader> <CardTitle>Registry & CLI</CardTitle> <CardDescription> Short answers for installing and owning ui pieces. </CardDescription> </CardHeader> <CardContent> <Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger>What does add actually do?</AccordionTrigger> <AccordionContent> It copies the component source into your project and wires any local imports it needs. </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger>Can I change files after install?</AccordionTrigger> <AccordionContent> Yes. Once it is in your tree it is yours — no upstream unlock step. </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger>Do I need a private registry?</AccordionTrigger> <AccordionContent> Not to start. Use the public set first; host your own when you want internal blocks. </AccordionContent> </AccordionItem> </Accordion> </CardContent> </Card> )}Nested
Render another Accordion inside AccordionContent for nested sections. Add unmountOnExit to the parent content if nested panels should reset when it closes.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"export function Example() { return ( <Accordion type="multiple" defaultValue={["item-1"]}> <AccordionItem value="item-1"> <AccordionTrigger>Getting started</AccordionTrigger> <AccordionContent unmountOnExit> <Accordion type="multiple" className="pl-3"> <AccordionItem value="setup"> <AccordionTrigger>Install the package</AccordionTrigger> <AccordionContent> Pull the component with the CLI, then import it like any other local file. </AccordionContent> </AccordionItem> <AccordionItem value="theme"> <AccordionTrigger>Wire your theme</AccordionTrigger> <AccordionContent> Map tokens once in CSS. Nested panels inherit the same surface and type styles. </AccordionContent> </AccordionItem> </Accordion> </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger>Composition</AccordionTrigger> <AccordionContent unmountOnExit> <Accordion type="multiple" className="pl-3"> <AccordionItem value="nest"> <AccordionTrigger>Why nest at all?</AccordionTrigger> <AccordionContent> Put another Accordion inside AccordionContent when a section needs its own open state. </AccordionContent> </AccordionItem> <AccordionItem value="offset"> <AccordionTrigger>Keep a small indent</AccordionTrigger> <AccordionContent> A light pl on the inner root shows hierarchy without changing the look of the triggers. </AccordionContent> </AccordionItem> </Accordion> </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger>Still one primitive</AccordionTrigger> <AccordionContent> Outer and inner stacks are the same Accordion — only placement changes. </AccordionContent> </AccordionItem> </Accordion> )}Card grid
AccordionContent accepts any layout. Here, it opens into a grid of compact cards.
Auth shell
Sign-in layout with room for providers.
Settings
Account, billing, and preference sections.
Empty state
First-run copy and a single action.
Data table
Dense rows with filters above.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger,} from "@/components/ui/accordion"function MiniCard({ title, description,}: { title: string description: string}) { return ( <div className="rounded-md border border-foreground/10 p-3"> <p className="text-sm font-medium tracking-wide text-foreground">{title}</p> <p className="mt-1 text-[12px] leading-relaxed tracking-wide text-foreground/50"> {description} </p> </div> )}export function Example() { return ( <Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger>Starter blocks</AccordionTrigger> <AccordionContent> <div className="grid grid-cols-2 gap-2 pt-1"> <MiniCard title="Auth shell" description="Sign-in layout with room for providers." /> <MiniCard title="Settings" description="Account, billing, and preference sections." /> <MiniCard title="Empty state" description="First-run copy and a single action." /> <MiniCard title="Data table" description="Dense rows with filters above." /> </div> </AccordionContent> </AccordionItem> <AccordionItem value="item-2"> <AccordionTrigger>Charts & metrics</AccordionTrigger> <AccordionContent> <div className="grid grid-cols-2 gap-2 pt-1"> <MiniCard title="Spark line" description="Inline trend without a full chart page." /> <MiniCard title="KPI strip" description="Three numbers with quiet labels." /> <MiniCard title="Range compare" description="This week versus last week." /> <MiniCard title="Breakdown" description="Simple share of traffic by source." /> </div> </AccordionContent> </AccordionItem> <AccordionItem value="item-3"> <AccordionTrigger>Docs patterns</AccordionTrigger> <AccordionContent> <div className="grid grid-cols-2 gap-2 pt-1"> <MiniCard title="Install card" description="Command plus short context." /> <MiniCard title="API strip" description="Prop name and one-line meaning." /> <MiniCard title="Preview frame" description="Live demo with code under it." /> <MiniCard title="Token table" description="Name, value, and where it lands." /> </div> </AccordionContent> </AccordionItem> </Accordion> )}API Reference
Accordion
| Prop | Type | Default |
|---|---|---|
| type | "single" | "multiple" | "single" |
| collapsible | boolean | true |
| defaultValue | string | string[] | — |
| value | string | string[] | — |
| onValueChange | (value: string | string[]) => void | — |
| className | string | — |
<Accordion type="single" collapsible defaultValue="item-1"> <AccordionItem value="item-1"> <AccordionTrigger>Section</AccordionTrigger> <AccordionContent>Content</AccordionContent> </AccordionItem></Accordion>AccordionItem
| Prop | Type | Default |
|---|---|---|
| value | string | — |
| disabled | boolean | false |
| className | string | — |
<AccordionItem value="item-1"> <AccordionTrigger>Section</AccordionTrigger> <AccordionContent>Content</AccordionContent></AccordionItem>AccordionTrigger
| Prop | Type | Default |
|---|---|---|
| icon | ReactNode | — |
| indicator | "chevron" | "plus" | "chevron" |
| className | string | — |
<AccordionTrigger>Section</AccordionTrigger>AccordionContent
| Prop | Type | Default |
|---|---|---|
| unmountOnExit | boolean | false |
| className | string | — |
<AccordionContent>Content</AccordionContent>