ComponentsBasic Accordion
Basic Accordion
An expandable accordion component with smooth animations that's perfect for organizing content into collapsible sections.
An accordion is a vertically stacked set of interactive headings that expand/collapse to reveal content. The animated version adds smooth transitions between states, improving user experience.
Code
Install with shadcn Beta
Terminal
npx shadcn@latest add "https://smoothui.dev/r/basic-accordion.json"
Manual install
Terminal
npm install motion lucide-react
BasicAccordion.tsx
"use client"
import { useState } from "react"
import { ChevronDown } from "lucide-react"
import { AnimatePresence, motion } from "motion/react"
export interface AccordionItem {
id: string | number
title: string
content: React.ReactNode
}
interface BasicAccordionProps {
items: AccordionItem[]
allowMultiple?: boolean
className?: string
defaultExpandedIds?: Array<string | number>
}
export default function BasicAccordion({
items,
allowMultiple = false,
className = "",
defaultExpandedIds = [],
}: BasicAccordionProps) {
const [expandedItems, setExpandedItems] =
useState<Array<string | number>>(defaultExpandedIds)
const toggleItem = (id: string | number) => {
if (expandedItems.includes(id)) {
setExpandedItems(expandedItems.filter((item) => item !== id))
} else {
if (allowMultiple) {
setExpandedItems([...expandedItems, id])
} else {
setExpandedItems([id])
}
}
}
return (
<div
className={`divide-light-300 dark:divide-dark-300 flex w-full flex-col divide-y rounded-lg border ${className}`}
>
{items.map((item) => {
const isExpanded = expandedItems.includes(item.id)
return (
<div key={item.id} className="overflow-hidden">
<button
onClick={() => toggleItem(item.id)}
className="flex w-full items-center justify-between gap-2 px-4 py-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5"
aria-expanded={isExpanded}
>
<h3 className="font-medium">{item.title}</h3>
<motion.div
animate={{ rotate: isExpanded ? 180 : 0 }}
transition={{ duration: 0.2 }}
className="flex-shrink-0"
>
<ChevronDown className="h-5 w-5" />
</motion.div>
</button>
<AnimatePresence initial={false}>
{isExpanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{
height: "auto",
opacity: 1,
transition: {
height: {
type: "spring",
stiffness: 500,
damping: 40,
duration: 0.3,
},
opacity: { duration: 0.25 },
},
}}
exit={{
height: 0,
opacity: 0,
transition: {
height: { duration: 0.25 },
opacity: { duration: 0.15 },
},
}}
className="overflow-hidden"
>
<div className="border-light-300 dark:border-dark-300 border-t px-4 py-3">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
})}
</div>
)
}
export function AccordionDemo() {
const accordionItems = [
{
id: 1,
title: "What is an animated accordion?",
content: (
<p className="text-sm">
An accordion is a vertically stacked set of interactive headings that
expand/collapse to reveal content. The animated version adds smooth
transitions between states, improving user experience.
</p>
),
},
{
id: 2,
title: "How to use this component?",
content: (
<div className="space-y-2 text-sm">
<p>
Import the component and provide an array of items with{" "}
<code>id</code>, <code>title</code>, and <code>content</code>. You
can also customize behavior with props like{" "}
<code>allowMultiple</code> and <code>defaultExpandedIds</code>.
</p>
<pre className="bg-light-200 dark:bg-dark-200 rounded p-2">
{`<BasicAccordion
items={accordionItems}
allowMultiple={true}
defaultExpandedIds={[1]}
/>`}
</pre>
</div>
),
},
{
id: 3,
title: "Is it accessible?",
content: (
<p className="text-sm">
Yes! The component follows accessibility guidelines by using proper
ARIA attributes, supporting keyboard navigation, and maintaining focus
properly through interactions.
</p>
),
},
]
return (
<div className="w-full max-w-xl p-4">
<BasicAccordion
items={accordionItems}
allowMultiple={true}
defaultExpandedIds={[1]}
className="border-light-300 dark:border-dark-300 bg-light-50 dark:bg-dark-50"
/>
</div>
)
}