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 installframer-motionlucide-react

Code

"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

PropTypeDefaultDescription
itemsAccordionItem[]itemsAn array of objects containing the id, title, tag, and content for each accordion panel.