Accordion
Expandable sections with smooth height and a turning plus.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/accordion.jsonYes. Within 14 days of purchase, no questions asked.
components/examples/accordion-demo.tsx
"use client";
import { Accordion } from "@/components/ui/accordion";
export default function AccordionDemo() {
return (
<Accordion
className="w-full max-w-md bg-card"
defaultValue={["refunds"]}
items={[
{ value: "refunds", title: "Can I get a refund?", content: "Yes. Within 14 days of purchase, no questions asked." },
{ value: "seats", title: "How do seats work?", content: "Each person on your team takes one seat. Add or remove seats any time from Billing." },
{ value: "cancel", title: "What happens if I cancel?", content: "You keep everything you installed. New releases stop at the end of your billing period." },
]}
/>
);
}When to use
- FAQs and long settings pages where people scan headings first.
When not to use
- Content everyone needs: show it.
- Switching views: use tabs.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/accordion.json1Install the dependencies
npm install motion lucide-react2Copy the source into your project
components/ui/accordion.tsx"use client"; import * as React from "react"; import { AnimatePresence, motion } from "motion/react"; import { Plus } from "lucide-react"; import { cn } from "@/lib/utils"; export interface AccordionItem { value: string; title: React.ReactNode; content: React.ReactNode; } export interface AccordionProps { items: AccordionItem[]; /** "single" closes the others when one opens. */ type?: "single" | "multiple"; defaultValue?: string[]; className?: string; } export function Accordion({ items, type = "single", defaultValue = [], className }: AccordionProps) { const id = React.useId(); const [open, setOpen] = React.useState<string[]>(defaultValue); const toggle = (v: string) => setOpen((cur) => (cur.includes(v) ? cur.filter((x) => x !== v) : type === "single" ? [v] : [...cur, v])); return ( <div className={cn("divide-y divide-border rounded-lg border border-border", className)}> {items.map((item) => { const isOpen = open.includes(item.value); return ( <div key={item.value}> <h3> <button type="button" id={`${id}-${item.value}-trigger`} aria-expanded={isOpen} aria-controls={`${id}-${item.value}-panel`} onClick={() => toggle(item.value)} className="flex w-full items-center justify-between gap-4 px-4 py-3.5 text-left text-sm font-medium outline-none transition-colors hover:bg-muted/50 focus-visible:bg-muted/50" > {item.title} <motion.span animate={{ rotate: isOpen ? 45 : 0 }} transition={{ type: "spring", stiffness: 400, damping: 25 }} className="text-muted-foreground"> <Plus className="size-4" /> </motion.span> </button> </h3> <AnimatePresence initial={false}> {isOpen && ( <motion.div id={`${id}-${item.value}-panel`} role="region" aria-labelledby={`${id}-${item.value}-trigger`} initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ type: "spring", bounce: 0, duration: 0.3 }} className="overflow-hidden" > <div className="px-4 pb-4 text-sm leading-relaxed text-muted-foreground">{item.content}</div> </motion.div> )} </AnimatePresence> </div> ); })} </div> ); }
Usage
Import it from @/components/ui/accordion after installing.
example.tsx
import { Accordion } from "@/components/ui/accordion";
export function Faq() {
return <Accordion items={[{ value: "refunds", title: "Can I get a refund?", content: "Yes, within 14 days." }]} />;
}API reference
Accordion
Expandable sections.
| Prop | Type | Default | Description |
|---|---|---|---|
| items | { value; title; content }[] | – | Sections. |
| type | "single" | "multiple" | "single" | Whether opening one closes the others. |
| defaultValue | string[] | – | Open on first render. |
Keyboard interactions
| Keys | Action |
|---|---|
| EnterorSpace | Toggle the focused section |
| Tab | Move between headers |
Accessibility
- Headers are buttons with aria-expanded controlling labelled regions.
Motion
- Height animates to auto; the plus rotates into a cross.
Responsive behavior
- Full width; titles wrap.
Performance
- Closed panels are unmounted.
Notes for AI
- Values must be unique.
This page as Markdown, for assistants and coding agents:
/components/accordion/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt