Accordion Staggered
It is a dark-themed, cinematic UI component designed for sophisticated technical interfaces. It elevates the standard accordion pattern by using staggered orchestration, where internal elements (text and buttons) animate in sequence with a slight delay after the container expands.
Live Preview
Building robust foundations using modular design patterns and scalable infrastructure to ensure long-term reliability.
Dependencies
npm install
framer-motionlucide-reactCode
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Plus } from "lucide-react";
interface AccordionItem {
id: number;
title: string;
content: string;
tag: string;
}
const items: AccordionItem[] = [
{
id: 1,
title: "System Architecture",
tag: "Core",
content:
"Building robust foundations using modular design patterns and scalable infrastructure to ensure long-term reliability.",
},
{
id: 2,
title: "User Experience",
tag: "Design",
content:
"Crafting intuitive interfaces that prioritize human interaction over rigid, robotic patterns and artificial aesthetics.",
},
{
id: 3,
title: "Performance Metrics",
tag: "Engine",
content:
"Optimizing every millisecond of execution time to create a seamless, high-performance environment for the end user.",
},
];
export default function StaggeredAccordion() {
const [openId, setOpenId] = useState<number | null>(1);
const containerVariants = {
open: {
height: "auto",
transition: {
height: { duration: 0.4, ease: [0.04, 0.62, 0.23, 0.98] },
staggerChildren: 0.1,
delayChildren: 0.1,
},
},
closed: {
height: 0,
transition: {
height: { duration: 0.3, ease: [0.04, 0.62, 0.23, 0.98] },
staggerChildren: 0.05,
staggerDirection: -1,
},
},
};
const itemVariants = {
open: { x: 0, opacity: 1, transition: { duration: 0.3 } },
closed: { x: -20, opacity: 0, transition: { duration: 0.2 } },
};
return (
<div className="w-full max-w-2xl mx-auto space-y-4 p-6">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div
key={item.id}
className="group overflow-hidden rounded-2xl border border-stone-800 bg-stone-950 shadow-sm"
>
<button
onClick={() => setOpenId(isOpen ? null : item.id)}
className="flex w-full items-center justify-between p-6 text-left transition-colors hover:bg-stone-900"
>
<div className="flex items-center gap-4">
<span className="text-xs font-mono text-stone-500 uppercase tracking-tighter">
0{item.id}
</span>
<h3
className={`text-xl font-medium tracking-tight ${isOpen ? "text-stone-100" : "text-stone-400"}`}
>
{item.title}
</h3>
</div>
<div className="flex items-center gap-4">
<span className="hidden sm:inline-block text-[10px] px-2 py-1 rounded border border-stone-800 text-stone-500 uppercase font-semibold">
{item.tag}
</span>
<motion.div
animate={{ rotate: isOpen ? 45 : 0 }}
className={`p-1 rounded-full ${isOpen ? "bg-orange-900/20 text-orange-500" : "text-stone-600"}`}
>
<Plus size={20} />
</motion.div>
</div>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
variants={containerVariants}
initial="closed"
animate="open"
exit="closed"
className="bg-stone-900/30"
>
<div className="px-16 pb-8">
<motion.p
variants={itemVariants}
className="text-stone-400 leading-relaxed max-w-md"
>
{item.content}
</motion.p>
<motion.div
variants={itemVariants}
className="mt-6 flex gap-4"
>
<button className="text-xs font-bold uppercase tracking-widest text-orange-500 hover:text-orange-400 transition-colors border-b border-orange-900/50 pb-1">
View Specs
</button>
<button className="text-xs font-bold uppercase tracking-widest text-stone-500 hover:text-stone-300 transition-colors border-b border-stone-800 pb-1">
Documentation
</button>
</motion.div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | AccordionItem[] | items | An array of objects containing the id, title, tag, and content for each accordion panel. |