Accordion
A component that expands or collapses vertically stacked content.
A small, natural stream of fresh water flowing along a course towards a river, lake, or sea.
accordion-demo.tsx
import { Accordion, AccordionItem, AccordionHeader, AccordionTrigger, AccordionPanel } from "@/components/ui/accordion/accordion";
export default function AccordionDemo() {
return (
<Accordion defaultValue={["item-1"]}>
<AccordionItem value="item-1">
<AccordionHeader>
<AccordionTrigger>What is a brook?</AccordionTrigger>
</AccordionHeader>
<AccordionPanel>
A small, natural stream of fresh water flowing along a course towards a river, lake, or sea.
</AccordionPanel>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionHeader>
<AccordionTrigger>What is a stream?</AccordionTrigger>
</AccordionHeader>
<AccordionPanel>
A small narrow river that flows continuously in one direction, often through valleys and plains.
</AccordionPanel>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionHeader>
<AccordionTrigger>What is a creek?</AccordionTrigger>
</AccordionHeader>
<AccordionPanel>
A narrow waterway smaller than a river, often found in wooded areas and feeding into larger bodies of water.
</AccordionPanel>
</AccordionItem>
</Accordion>
);
}
Installation
npx shadcn@latest add https://roiui.com/r/accordion.json
Anatomy
anatomy
import {
Accordion,
AccordionItem,
AccordionHeader,
AccordionTrigger,
AccordionPanel,
} from '../ui/accordion'
<Accordion>
<AccordionItem>
<AccordionHeader>
<AccordionTrigger />
</AccordionHeader>
<AccordionPanel />
</AccordionItem>
</Accordion>
Examples
With Motion
Smooth animations are achieved through proper easing curves, appropriate durations, and understanding the physics of motion. Motion provides spring animations that feel natural and responsive to user interactions.
"use client";
import { Accordion } from "@base-ui-components/react/accordion";
import { motion, AnimatePresence } from "motion/react";
import { useState } from "react";
import styles from "./accordion-motion.module.css";
const accordionItemVariants = {
default: {},
spring: {},
};
const accordionItemTransition = { type: "spring" as const };
const iconVariants = {
closed: { rotate: 0 },
open: { rotate: 45 },
};
const iconTransition = { type: "spring" as const };
const hoverVariants = {
default: {
width: "50px",
height: "75px",
opacity: 0,
},
hover: {
width: "100%",
height: "100%",
borderRadius: "12px",
opacity: 1,
},
};
const hoverTransition = {
duration: 0.2,
ease: "linear" as const,
};
const contentVariants = {
closed: { height: 0, opacity: 0.8 },
open: { height: "auto", opacity: 1 },
};
const contentTransition = {
duration: 0.2,
ease: "easeInOut" as const,
opacity: { duration: 0.2 },
};
const contentInnerVariants = {
closed: { y: -10 },
open: { y: 0 },
};
const contentInnerTransition = {
duration: 0.2,
delay: 0.1,
};
const accordionItems = [
{
id: "item-1",
title: "What makes animations smooth?",
content:
"Smooth animations are achieved through proper easing curves, appropriate durations, and understanding the physics of motion. Motion provides spring animations that feel natural and responsive to user interactions.",
},
{
id: "item-2",
title: "How do spring animations work?",
content:
"Spring animations simulate real-world physics by using mass, tension, and friction values. This creates more natural movement that feels organic rather than mechanical. The motion has bounce and elasticity that mimics how objects move in the physical world.",
},
{
id: "item-3",
title: "What about performance?",
content:
"Motion is optimized for performance by using hardware acceleration when possible and only animating properties that don't trigger layout recalculations. Transform and opacity changes are handled efficiently by the GPU.",
},
];
export default function AccordionFramerMotion() {
const [openItems, setOpenItems] = useState<string[]>(["item-1"]);
const handleValueChange = (newValues: string[]) => {
if (newValues.length > 0 && (openItems.length === 0 || !openItems.includes(newValues[newValues.length - 1]))) {
setOpenItems([newValues[newValues.length - 1]]);
} else {
setOpenItems(newValues);
}
};
return (
<div className={styles.container}>
{accordionItems.map((item) => {
const isOpen = openItems.includes(item.id);
return (
<motion.div key={item.id} variants={accordionItemVariants} animate="spring" transition={accordionItemTransition} className={styles.accordionItem}>
<Accordion.Root value={openItems} onValueChange={handleValueChange}>
<Accordion.Item value={item.id}>
<motion.div>
<Accordion.Header className={styles.header}>
<motion.div whileHover="hover" className={styles.triggerContainer}>
<Accordion.Trigger className={styles.trigger}>
<div>{item.title}</div>
<motion.div
variants={iconVariants}
animate={isOpen ? "open" : "closed"}
transition={iconTransition}
className={styles.icon}
>
+
</motion.div>
</Accordion.Trigger>
<motion.div
variants={hoverVariants}
initial="default"
transition={hoverTransition}
className={styles.hoverBackground}
/>
</motion.div>
</Accordion.Header>
</motion.div>
<AnimatePresence>
{isOpen && (
<motion.div
key="content"
layout
variants={contentVariants}
initial="closed"
animate="open"
exit="closed"
transition={contentTransition}
className={styles.content}
>
<motion.div
variants={contentInnerVariants}
initial="closed"
animate="open"
exit="closed"
transition={contentInnerTransition}
className={styles.contentInner}
>
{item.content}
</motion.div>
</motion.div>
)}
</AnimatePresence>
</Accordion.Item>
</Accordion.Root>
</motion.div>
);
})}
</div>
);
}