Accordion

Expandable sections for FAQs, settings, and layered details.

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.

Source files you own. Install a component, then edit it like any other file in your app.
tsx
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 accordion

Usage

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
    └── AccordionContent

Give 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.

Source files you own. Install a component, then edit it like any other file in your app.
tsx
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.

Each trigger is a button with expanded state, and panels are exposed as labelled regions.
Pass type="multiple" when sections should stay open side by side.
tsx
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.

This panel opens and closes like any other item.
tsx
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.

The vertical stroke rotates into the horizontal one so plus becomes minus without swapping icons.
tsx
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.

Drop components into your project tree. No opaque runtime package sitting between you and the markup.
tsx
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.

A light border frames the stack when the page needs a clearer block.
tsx
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.

Border and radius sit on AccordionItem — the root only adds gap.
tsx
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.

Registry & CLI
Short answers for installing and owning ui pieces.
It copies the component source into your project and wires any local imports it needs.
tsx
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.

tsx
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.

tsx
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

PropTypeDefault
type"single" | "multiple""single"
collapsiblebooleantrue
defaultValuestring | string[]
valuestring | string[]
onValueChange(value: string | string[]) => void
classNamestring
<Accordion type="single" collapsible defaultValue="item-1">  <AccordionItem value="item-1">    <AccordionTrigger>Section</AccordionTrigger>    <AccordionContent>Content</AccordionContent>  </AccordionItem></Accordion>

AccordionItem

PropTypeDefault
valuestring
disabledbooleanfalse
classNamestring
<AccordionItem value="item-1">  <AccordionTrigger>Section</AccordionTrigger>  <AccordionContent>Content</AccordionContent></AccordionItem>

AccordionTrigger

PropTypeDefault
iconReactNode
indicator"chevron" | "plus""chevron"
classNamestring
<AccordionTrigger>Section</AccordionTrigger>

AccordionContent

PropTypeDefault
unmountOnExitbooleanfalse
classNamestring
<AccordionContent>Content</AccordionContent>