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

1 Configure registry — once per project
{
  "registries": {
    "@designrevision": "https://registry.designrevision.com/r/{name}.json"
  }
}

Add to your existing components.json. Requires shadcn/ui v2.3+.

2 Install the component

Packages

@radix-ui/react-accordion
utils

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.

Copied!
components/ui/accordion.tsx
"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

@designrevision/accordion

Props

No props documented yet.

Copied!
components/ui/accordion-demo-01.tsx
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

@designrevision/accordion

Props

No props documented yet.

Copied!
components/ui/accordion-faq-01.tsx
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

@designrevision/accordion

Props

No props documented yet.

Copied!
components/ui/accordion-multiple-01.tsx
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

lucide-react
@designrevision/accordion

Props

No props documented yet.

Copied!
components/ui/accordion-icons-01.tsx
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

@designrevision/accordion

Props

No props documented yet.

Copied!
components/ui/accordion-card-01.tsx
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

@radix-ui/react-accordion
lucide-react
@designrevision/accordion

Props

No props documented yet.

Copied!
components/ui/accordion-plus-01.tsx
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

@designrevision/accordion
button

Props

No props documented yet.

Copied!
components/ui/accordion-controlled-01.tsx
"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>
  )
}

Frequently asked questions

Yes — it is the canonical shadcn/ui accordion, built on Radix Accordion with four parts (Accordion, AccordionItem, AccordionTrigger, AccordionContent) and a chevron that rotates on open. Official ships one demo; we add an FAQ, multiple-open, leading icons, a contained-card style, a plus/minus toggle, and controlled state.
Use type="single" collapsible on Accordion and map your questions to AccordionItems — the question in AccordionTrigger, the answer in AccordionContent. "collapsible" lets the open question close again. For SEO, pair it with FAQPage structured data (JSON-LD) so the Q&A can show as rich results — this gallery does exactly that on every component page. The FAQ example is the markup.
Set type="multiple" on Accordion. The value/defaultValue then becomes an array of the open item ids instead of a single string. With type="single" only one item opens at a time (add collapsible to allow closing it). The Multiple open example shows it with two sections open by default.
The default AccordionTrigger renders a chevron. To use a different icon, compose AccordionPrimitive.Trigger directly and add your own icons that toggle on state — e.g. a PlusIcon with group-data-[state=open]:hidden and a MinusIcon with group-data-[state=closed]:hidden (with the group class on the trigger). The Plus / minus icon example does this.
The open/close animation uses the accordion-down and accordion-up keyframes, which are not part of core Tailwind — they come from tw-animate-css (or the older tailwindcss-animate). Add @import "tw-animate-css"; to your global CSS. Radix sets --radix-accordion-content-height, which those keyframes animate to; without them the panel just appears.
Use an Accordion when sections stack vertically and the user may want several open (or none) — FAQs, settings groups, long forms — and it works well on narrow screens. Use Tabs when the sections are peers shown one at a time in a fixed area. Accordions expand downward; tabs switch in place.
Override the AccordionItem className with a border, rounded corners, and horizontal padding (rounded-lg border px-4), and add space-y-2 on the Accordion for the gaps. The default item is a flush row with only a bottom border; the Contained / card example turns each into a bordered card.