Shadcn Accordion
Free shadcn/ui accordion component for React — Radix-powered collapsible sections. The FAQ accordion, multiple-open, leading icons, contained cards, a plus/minus toggle, and controlled state. The examples official shadcn never shipped.
Installation
{
"registries": {
"@designrevision": "https://registry.designrevision.com/r/{name}.json"
}
}
Add to your existing components.json. Requires shadcn/ui v2.3+.
Packages
Props
type*
= —
"single" | "multiple"
On Accordion. "single" opens one item at a time; "multiple" allows several.
collapsible
= false
boolean
On Accordion (type="single") — lets the open item close again.
value / defaultValue
= —
string | string[]
The open item(s) — controlled (value + onValueChange) or uncontrolled (defaultValue).
disabled
= false
boolean
On AccordionItem — prevents it from opening.
A Radix accordion — four parts (Accordion, AccordionItem, AccordionTrigger, AccordionContent), with a chevron that rotates on open. Set type="single" collapsible for an FAQ, or type="multiple" to allow several open. The open/close animation uses the accordion-down / accordion-up keyframes from tw-animate-css (or tailwindcss-animate) — without it the panel snaps open.
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}
function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("border-b last:border-b-0", className)}
{...props}
/>
)
}
function AccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
)
}
function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
)
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
Examples
Accordion
The canonical single-open, collapsible accordion — one item expanded at a time.
Packages
Props
No props documented yet.
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
export default function AccordionDemo01() {
return (
<Accordion
type="single"
collapsible
defaultValue="item-1"
className="w-[420px]"
>
<AccordionItem value="item-1">
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern and is fully keyboard
navigable.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Is it styled?</AccordionTrigger>
<AccordionContent>
Yes. It comes with default styles that match your design tokens, and
you can override them with a className.
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger>Is it animated?</AccordionTrigger>
<AccordionContent>
Yes. It animates open and closed with the accordion-down /
accordion-up keyframes.
</AccordionContent>
</AccordionItem>
</Accordion>
)
}
FAQ
A frequently-asked-questions accordion — the most common use, and a natural pair for **FAQPage** schema.
Packages
Props
No props documented yet.
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
const faqs = [
{
q: "What is your refund policy?",
a: "We offer a 30-day money-back guarantee — no questions asked. Email support and we'll process it within 48 hours.",
},
{
q: "Can I change my plan later?",
a: "Yes. Upgrade or downgrade at any time from your billing settings; changes are prorated automatically.",
},
{
q: "Do you offer a free trial?",
a: "Every paid plan includes a 14-day free trial. No credit card required to start.",
},
{
q: "How do I cancel my subscription?",
a: "Cancel anytime from Settings → Billing. Your plan stays active until the end of the current period.",
},
]
export default function AccordionFaq01() {
return (
<Accordion type="single" collapsible className="w-[480px]">
{faqs.map((faq, index) => (
<AccordionItem key={index} value={`faq-${index}`}>
<AccordionTrigger>{faq.q}</AccordionTrigger>
<AccordionContent className="text-muted-foreground">
{faq.a}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
)
}
Multiple open
`type="multiple"` lets several sections stay open at once, with two open by default.
Packages
Props
No props documented yet.
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
const sections = [
{ value: "shipping", title: "Shipping", body: "Free standard shipping on orders over $50. Express options at checkout." },
{ value: "returns", title: "Returns", body: "Return unworn items within 30 days for a full refund." },
{ value: "warranty", title: "Warranty", body: "All products carry a two-year limited warranty against defects." },
]
export default function AccordionMultiple01() {
return (
<Accordion
type="multiple"
defaultValue={["shipping", "returns"]}
className="w-[460px]"
>
{sections.map((section) => (
<AccordionItem key={section.value} value={section.value}>
<AccordionTrigger>{section.title}</AccordionTrigger>
<AccordionContent className="text-muted-foreground">
{section.body}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
)
}
Leading icons
An icon before each trigger label — the help-center category pattern.
Packages
Props
No props documented yet.
import { CreditCardIcon, ShieldCheckIcon, TruckIcon } from "lucide-react"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
const items = [
{ value: "shipping", icon: TruckIcon, title: "Shipping & delivery", body: "Orders ship within 1–2 business days with tracked delivery." },
{ value: "payment", icon: CreditCardIcon, title: "Payment methods", body: "We accept all major cards, Apple Pay, and PayPal." },
{ value: "security", icon: ShieldCheckIcon, title: "Security", body: "Every transaction is protected with 256-bit encryption." },
]
export default function AccordionIcons01() {
return (
<Accordion type="single" collapsible className="w-[460px]">
{items.map((item) => (
<AccordionItem key={item.value} value={item.value}>
<AccordionTrigger>
<span className="flex items-center gap-3">
<item.icon className="text-muted-foreground size-4 shrink-0" />
{item.title}
</span>
</AccordionTrigger>
<AccordionContent className="text-muted-foreground pl-7">
{item.body}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
)
}
Contained / card
Each item wrapped as a bordered card with spacing between, instead of the default flush rows.
Packages
Props
No props documented yet.
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
const items = [
{ value: "a", title: "Getting started", body: "Install the CLI and run the init command to scaffold your project." },
{ value: "b", title: "Configuration", body: "Edit the config file to set your theme tokens and import aliases." },
{ value: "c", title: "Deployment", body: "Push to your provider and connect the repo — builds run automatically." },
]
export default function AccordionCard01() {
return (
<Accordion type="single" collapsible className="w-[460px] space-y-2">
{items.map((item) => (
<AccordionItem
key={item.value}
value={item.value}
className="bg-card rounded-lg border px-4 last:border-b"
>
<AccordionTrigger className="hover:no-underline">
{item.title}
</AccordionTrigger>
<AccordionContent className="text-muted-foreground">
{item.body}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
)
}
Plus / minus icon
A custom trigger that swaps the default chevron for a plus/minus that toggles via `group-data-[state]`.
Packages
Props
No props documented yet.
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { MinusIcon, PlusIcon } from "lucide-react"
import {
Accordion,
AccordionContent,
AccordionItem,
} from "@/components/ui/accordion"
const items = [
{ value: "a", title: "Can I use this in a commercial project?", body: "Yes — it's free to use in personal and commercial projects." },
{ value: "b", title: "Do I need to credit you?", body: "No attribution is required, though it's always appreciated." },
{ value: "c", title: "Will there be updates?", body: "Yes. New components and examples ship regularly." },
]
export default function AccordionPlus01() {
return (
<Accordion type="single" collapsible className="w-[480px]">
{items.map((item) => (
<AccordionItem key={item.value} value={item.value}>
<AccordionPrimitive.Header className="flex">
{/* Custom trigger: swap the default chevron for a +/- toggle. */}
<AccordionPrimitive.Trigger className="group focus-visible:ring-ring/50 flex flex-1 items-center justify-between gap-4 rounded-md py-4 text-left text-sm font-medium outline-none focus-visible:ring-[3px]">
{item.title}
<PlusIcon className="text-muted-foreground size-4 shrink-0 group-data-[state=open]:hidden" />
<MinusIcon className="text-muted-foreground size-4 shrink-0 group-data-[state=closed]:hidden" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
<AccordionContent className="text-muted-foreground">
{item.body}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
)
}
Controlled & disabled
Drive the open item from external buttons via `value` / `onValueChange`, with one disabled step.
Packages
Props
No props documented yet.
"use client"
import * as React from "react"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
import { Button } from "@/components/ui/button"
const steps = [
{ value: "account", title: "Account", body: "Create your account and verify your email address." },
{ value: "profile", title: "Profile", body: "Add your name and a profile photo." },
{ value: "billing", title: "Billing", body: "Add a payment method — available once your profile is complete.", disabled: true },
]
export default function AccordionControlled01() {
const [value, setValue] = React.useState("account")
return (
<div className="grid w-[460px] gap-3">
<div className="flex items-center gap-2">
{steps.map((step) => (
<Button
key={step.value}
variant={value === step.value ? "default" : "outline"}
size="sm"
disabled={step.disabled}
onClick={() => setValue(step.value)}
>
{step.title}
</Button>
))}
</div>
<Accordion type="single" collapsible value={value} onValueChange={setValue}>
{steps.map((step) => (
<AccordionItem
key={step.value}
value={step.value}
disabled={step.disabled}
>
<AccordionTrigger>{step.title}</AccordionTrigger>
<AccordionContent className="text-muted-foreground">
{step.body}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
)
}
The Shadcn Accordion is the collapsible-sections component — the canonical shadcn/ui accordion built on Radix. Its number-one job is the FAQ, and the examples below cover that plus multiple-open, icons, a contained-card style, a plus/minus toggle, and controlled state.
Built on Radix
Accordion is a thin wrapper over Radix Accordion with four parts — Accordion, AccordionItem, AccordionTrigger, AccordionContent. Radix handles the WAI-ARIA accordion pattern: keyboard navigation, focus, and the --radix-accordion-content-height measurement that drives the open/close animation. The chevron rotates on open via [&[data-state=open]>svg]:rotate-180.
The FAQ accordion
The most common accordion is the FAQ — questions as triggers, answers as content, type="single" collapsible so one opens at a time and can close again:
<Accordion type="single" collapsible>
<AccordionItem value="refunds">
<AccordionTrigger>What is your refund policy?</AccordionTrigger>
<AccordionContent>30-day money-back guarantee…</AccordionContent>
</AccordionItem>
</Accordion>
For search, pair it with FAQPage structured data (JSON-LD) so the Q&A can surface as rich results — this gallery emits that schema on every component page. The FAQ example is the markup.
Single vs multiple
type="single" opens one item at a time (add collapsible to allow closing it); type="multiple" lets several stay open and makes value an array. Use single for FAQs where focus matters, multiple for reference content the user scans across (the Multiple open example).
Styling
The default is flush rows with a bottom border. Restyle with classNames — no custom component:
- Leading icons — put an icon before the label inside
AccordionTrigger(the Leading icons example). - Contained cards — give
AccordionItemrounded-lg border px-4and theAccordionspace-y-2for separated cards (the Contained / card example). - Plus / minus — compose
AccordionPrimitive.Triggerdirectly and toggle aPlusIcon/MinusIconwithgroup-data-[state](the Plus / minus example).
Controlled & disabled
Use defaultValue for an uncontrolled start, or value + onValueChange to control which item is open from elsewhere (a stepper, a URL). Disable an item with the disabled prop on AccordionItem. The Controlled & disabled example drives the accordion from external buttons and disables one step.
Animation
The expand/collapse uses the accordion-down and accordion-up keyframes, which animate to Radix's --radix-accordion-content-height. These aren't core Tailwind — they come from tw-animate-css (or tailwindcss-animate). Add @import "tw-animate-css"; to your global CSS, or the panel snaps open with no transition.
Tabs vs Accordion
Use an Accordion for stacked, vertical sections where several may be open — FAQs, settings, long forms — especially on narrow screens. Use Tabs for peer sections shown one at a time in a fixed area. Accordions expand downward; tabs switch in place.
Accessibility & theming
Radix gives the accordion full keyboard support — Enter/Space toggles, arrow keys move between triggers, Home/End jump to the ends — and wires the trigger to its panel with aria-controls. It reads your design tokens — --border, --ring (focus), --muted-foreground (the chevron) — so it follows your theme, including dark mode, with no overrides.