Components
Interactive Accordion
Interactive Accordion
Smooth expanding panels with animated transitions, icons, and accessibility features.
Smooth Accordion
Expandable sections with staggered animations, rotating icons, and contextual actions.
Installation
1
Install the packages
npm i motion clsx tailwind-merge2
Add util file
lib/util.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
3
Copy and paste the following code into your project
interactive-accordion.tsx
"use client";
import { motion, AnimatePresence } from "motion/react";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { FiChevronDown, FiChevronUp } from "react-icons/fi";
type AccordionItem = {
id: string;
title: string;
content: string;
icon?: React.ReactNode;
};
type InteractiveAccordionProps = {
items?: AccordionItem[];
allowMultiple?: boolean;
};
const InteractiveAccordion = ({
items = [
{
id: "1",
title: "Getting Started",
content: "Learn how to get started with our platform and set up your account.",
icon: "🚀",
},
{
id: "2",
title: "Features",
content: "Explore all the amazing features we offer to enhance your experience.",
icon: "✨",
},
{
id: "3",
title: "Support",
content: "Get help and support whenever you need it from our dedicated team.",
icon: "💬",
},
],
allowMultiple = false,
}: InteractiveAccordionProps) => {
const [openItems, setOpenItems] = useState<string[]>([]);
const toggleItem = (id: string) => {
if (allowMultiple) {
setOpenItems(prev =>
prev.includes(id)
? prev.filter(item => item !== id)
: [...prev, id]
);
} else {
setOpenItems(prev => prev.includes(id) ? [] : [id]);
}
};
const isOpen = (id: string) => openItems.includes(id);
return (
<div className="w-full max-w-2xl mx-auto space-y-2">
{items.map((item, index) => (
<motion.div
key={item.id}
className="border border-neutral-200 dark:border-neutral-700 rounded-lg overflow-hidden"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1, duration: 0.3 }}
>
<motion.button
className="w-full px-6 py-4 text-left bg-white dark:bg-neutral-900 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors flex items-center justify-between"
onClick={() => toggleItem(item.id)}
whileTap={{ scale: 0.98 }}
>
<div className="flex items-center gap-3">
{item.icon && (
<motion.span
className="text-lg"
animate={{ rotate: isOpen(item.id) ? 360 : 0 }}
transition={{ duration: 0.3 }}
>
{item.icon}
</motion.span>
)}
<span className="font-medium text-neutral-900 dark:text-white">
{item.title}
</span>
</div>
<motion.div
animate={{ rotate: isOpen(item.id) ? 180 : 0 }}
transition={{ duration: 0.3 }}
>
<FiChevronDown className="w-5 h-5 text-neutral-500" />
</motion.div>
</motion.button>
<AnimatePresence>
{isOpen(item.id) && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="overflow-hidden"
>
<div className="px-6 pb-4 text-neutral-600 dark:text-neutral-400">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
))}
</div>
);
};
export default InteractiveAccordion;"use client";
import { motion, AnimatePresence } from "motion/react";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { FiChevronDown, FiChevronUp } from "react-icons/fi";
type AccordionItem = {
id: string;
title: string;
content: string;
icon?: React.ReactNode;
};
type InteractiveAccordionProps = {
items?: AccordionItem[];
allowMultiple?: boolean;
};
const InteractiveAccordion = ({
items = [
{
id: "1",
title: "Getting Started",
content: "Learn how to get started with our platform and set up your account.",
icon: "🚀",
},
{
id: "2",
title: "Features",
content: "Explore all the amazing features we offer to enhance your experience.",
icon: "✨",
},
{
id: "3",
title: "Support",
content: "Get help and support whenever you need it from our dedicated team.",
icon: "💬",
},
],
allowMultiple = false,
}: InteractiveAccordionProps) => {
const [openItems, setOpenItems] = useState<string[]>([]);
const toggleItem = (id: string) => {
if (allowMultiple) {
setOpenItems(prev =>
prev.includes(id)
? prev.filter(item => item !== id)
: [...prev, id]
);
} else {
setOpenItems(prev => prev.includes(id) ? [] : [id]);
}
};
const isOpen = (id: string) => openItems.includes(id);
return (
<div className="w-full max-w-2xl mx-auto space-y-2">
{items.map((item, index) => (
<motion.div
key={item.id}
className="border border-neutral-200 dark:border-neutral-700 rounded-lg overflow-hidden"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1, duration: 0.3 }}
>
<motion.button
className="w-full px-6 py-4 text-left bg-white dark:bg-neutral-900 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors flex items-center justify-between"
onClick={() => toggleItem(item.id)}
whileTap={{ scale: 0.98 }}
>
<div className="flex items-center gap-3">
{item.icon && (
<motion.span
className="text-lg"
animate={{ rotate: isOpen(item.id) ? 360 : 0 }}
transition={{ duration: 0.3 }}
>
{item.icon}
</motion.span>
)}
<span className="font-medium text-neutral-900 dark:text-white">
{item.title}
</span>
</div>
<motion.div
animate={{ rotate: isOpen(item.id) ? 180 : 0 }}
transition={{ duration: 0.3 }}
>
<FiChevronDown className="w-5 h-5 text-neutral-500" />
</motion.div>
</motion.button>
<AnimatePresence>
{isOpen(item.id) && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="overflow-hidden"
>
<div className="px-6 pb-4 text-neutral-600 dark:text-neutral-400">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
))}
</div>
);
};
export default InteractiveAccordion;4
Update the import paths to match your project setup
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | array | [] | Array of accordion items with title and content. |
| allowMultiple | boolean | false | Whether multiple panels can be open at once. |