Accordion
ComponentsCollapsible content sections.
Default Accordion
Item 1
Content for item 1.
Live Functional Playground Interactive Hooks • useState
No bloated npm dependencies. Copy production-grade TypeScript components directly into your codebase.
Yes! Every component includes functional React variants with useState, useEffect, and strict TypeScript types.
Motion Physics & Animation Showcase Framer Motion ✨
Motion Physics
✨
Item 1
Content for item 1.
Hover to trigger spring elevation lift
Format:
Required Animation Library: framer-motion & lucide-react
Install this package in your project before importing the animated component.
npm i framer-motion lucide-react <div class="w-full max-w-md space-y-2 border-b border-border pb-4"><details class="group [&_summary::-webkit-details-marker]:hidden"><summary class="flex cursor-pointer items-center justify-between font-medium py-2"><span>Item 1</span><span class="transition group-open:rotate-180"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 9l6 6 6-6"></path></svg></span></summary><p class="text-muted-foreground mt-2">Content for item 1.</p></details></div> <div className="w-full max-w-md space-y-2 border-b border-border pb-4"><details className="group [&_summary::-webkit-details-marker]:hidden"><summary className="flex cursor-pointer items-center justify-between font-medium py-2"><span>Item 1</span><span className="transition group-open:rotate-180"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 9l6 6 6-6"></path></svg></span></summary><p className="text-muted-foreground mt-2">Content for item 1.</p></details></div> "use client";
import React, { useState } from "react";
interface AccordionItem {
id: string;
title: string;
content: string;
}
const defaultItems: AccordionItem[] = [
{ id: "1", title: "What is ToolMate UI?", content: "A production-grade copy-and-paste Tailwind CSS UI library." },
{ id: "2", title: "How do I install dependencies?", content: "No npm packages required. Copy the code straight into your project." },
{ id: "3", title: "Is TypeScript supported?", content: "Yes! Every functional component includes strict TypeScript definitions." },
];
export default function AccDefault({
items = defaultItems,
}: {
items?: AccordionItem[];
}) {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId((prev) => (prev === id ? null : id));
};
return (
<div className="w-full max-w-md space-y-2.5">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden transition-all">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs sm:text-sm text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<svg
className={`h-4 w-4 text-muted-foreground transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
)}
</div>
);
})}
</div>
);
} "use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown } from "lucide-react";
const items = [
{ id: "1", title: "Smooth spring transitions", content: "Powered by Framer Motion physics for ultra-fluid collapse animations." },
{ id: "2", title: "Zero CSS calculation lag", content: "Uses auto-height transitions with zero hardcoded pixel heights." },
{ id: "3", title: "Accessible keyboard navigation", content: "Supports space and enter keys for full WAI-ARIA compliance." },
];
export default function AnimatedAccDefault() {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId(openId === id ? null : id);
};
return (
<div className="w-full max-w-md space-y-3">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<motion.div
animate={{ rotate: isOpen ? 180 : 0 }}
transition={{ duration: 0.25 }}
>
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</motion.div>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25, ease: "easeInOut" }}
>
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
);
} Bordered Accordion
Item 1
Content for item 1.
Live Functional Playground Interactive Hooks • useState
No bloated npm dependencies. Copy production-grade TypeScript components directly into your codebase.
Yes! Every component includes functional React variants with useState, useEffect, and strict TypeScript types.
Motion Physics & Animation Showcase Framer Motion ✨
Motion Physics
✨
Item 1
Content for item 1.
Hover to trigger spring elevation lift
Format:
Required Animation Library: framer-motion & lucide-react
Install this package in your project before importing the animated component.
npm i framer-motion lucide-react <div class="w-full max-w-md border border-border rounded-lg overflow-hidden"><details class="group [&_summary::-webkit-details-marker]:hidden border-b border-border last:border-0"><summary class="flex cursor-pointer items-center justify-between font-medium p-4 bg-muted/20 hover:bg-muted/50"><span>Item 1</span><span class="transition group-open:rotate-180"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 9l6 6 6-6"></path></svg></span></summary><p class="p-4 text-muted-foreground border-t border-border bg-background">Content for item 1.</p></details></div> <div className="w-full max-w-md border border-border rounded-lg overflow-hidden"><details className="group [&_summary::-webkit-details-marker]:hidden border-b border-border last:border-0"><summary className="flex cursor-pointer items-center justify-between font-medium p-4 bg-muted/20 hover:bg-muted/50"><span>Item 1</span><span className="transition group-open:rotate-180"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 9l6 6 6-6"></path></svg></span></summary><p className="p-4 text-muted-foreground border-t border-border bg-background">Content for item 1.</p></details></div> "use client";
import React, { useState } from "react";
interface AccordionItem {
id: string;
title: string;
content: string;
}
const defaultItems: AccordionItem[] = [
{ id: "1", title: "What is ToolMate UI?", content: "A production-grade copy-and-paste Tailwind CSS UI library." },
{ id: "2", title: "How do I install dependencies?", content: "No npm packages required. Copy the code straight into your project." },
{ id: "3", title: "Is TypeScript supported?", content: "Yes! Every functional component includes strict TypeScript definitions." },
];
export default function AccBordered({
items = defaultItems,
}: {
items?: AccordionItem[];
}) {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId((prev) => (prev === id ? null : id));
};
return (
<div className="w-full max-w-md space-y-2.5">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden transition-all">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs sm:text-sm text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<svg
className={`h-4 w-4 text-muted-foreground transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
)}
</div>
);
})}
</div>
);
} "use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown } from "lucide-react";
const items = [
{ id: "1", title: "Smooth spring transitions", content: "Powered by Framer Motion physics for ultra-fluid collapse animations." },
{ id: "2", title: "Zero CSS calculation lag", content: "Uses auto-height transitions with zero hardcoded pixel heights." },
{ id: "3", title: "Accessible keyboard navigation", content: "Supports space and enter keys for full WAI-ARIA compliance." },
];
export default function AnimatedAccBordered() {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId(openId === id ? null : id);
};
return (
<div className="w-full max-w-md space-y-3">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<motion.div
animate={{ rotate: isOpen ? 180 : 0 }}
transition={{ duration: 0.25 }}
>
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</motion.div>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25, ease: "easeInOut" }}
>
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
);
} Separated Accordion
Item 1
Content for item 1.
Live Functional Playground Interactive Hooks • useState
No bloated npm dependencies. Copy production-grade TypeScript components directly into your codebase.
Yes! Every component includes functional React variants with useState, useEffect, and strict TypeScript types.
Motion Physics & Animation Showcase Framer Motion ✨
Motion Physics
✨
Item 1
Content for item 1.
Hover to trigger spring elevation lift
Format:
Required Animation Library: framer-motion & lucide-react
Install this package in your project before importing the animated component.
npm i framer-motion lucide-react <div class="w-full max-w-md space-y-4"><details class="group [&_summary::-webkit-details-marker]:hidden border border-border rounded-lg"><summary class="flex cursor-pointer items-center justify-between font-medium p-4"><span>Item 1</span><span class="transition group-open:rotate-180"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 9l6 6 6-6"></path></svg></span></summary><p class="px-4 pb-4 text-muted-foreground">Content for item 1.</p></details></div> <div className="w-full max-w-md space-y-4"><details className="group [&_summary::-webkit-details-marker]:hidden border border-border rounded-lg"><summary className="flex cursor-pointer items-center justify-between font-medium p-4"><span>Item 1</span><span className="transition group-open:rotate-180"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 9l6 6 6-6"></path></svg></span></summary><p className="px-4 pb-4 text-muted-foreground">Content for item 1.</p></details></div> "use client";
import React, { useState } from "react";
interface AccordionItem {
id: string;
title: string;
content: string;
}
const defaultItems: AccordionItem[] = [
{ id: "1", title: "What is ToolMate UI?", content: "A production-grade copy-and-paste Tailwind CSS UI library." },
{ id: "2", title: "How do I install dependencies?", content: "No npm packages required. Copy the code straight into your project." },
{ id: "3", title: "Is TypeScript supported?", content: "Yes! Every functional component includes strict TypeScript definitions." },
];
export default function AccSeparated({
items = defaultItems,
}: {
items?: AccordionItem[];
}) {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId((prev) => (prev === id ? null : id));
};
return (
<div className="w-full max-w-md space-y-2.5">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden transition-all">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs sm:text-sm text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<svg
className={`h-4 w-4 text-muted-foreground transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
)}
</div>
);
})}
</div>
);
} "use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown } from "lucide-react";
const items = [
{ id: "1", title: "Smooth spring transitions", content: "Powered by Framer Motion physics for ultra-fluid collapse animations." },
{ id: "2", title: "Zero CSS calculation lag", content: "Uses auto-height transitions with zero hardcoded pixel heights." },
{ id: "3", title: "Accessible keyboard navigation", content: "Supports space and enter keys for full WAI-ARIA compliance." },
];
export default function AnimatedAccSeparated() {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId(openId === id ? null : id);
};
return (
<div className="w-full max-w-md space-y-3">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<motion.div
animate={{ rotate: isOpen ? 180 : 0 }}
transition={{ duration: 0.25 }}
>
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</motion.div>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25, ease: "easeInOut" }}
>
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
);
} Plus/Minus Icon
Item 1
Content for item 1.
Live Functional Playground Interactive Hooks • useState
No bloated npm dependencies. Copy production-grade TypeScript components directly into your codebase.
Yes! Every component includes functional React variants with useState, useEffect, and strict TypeScript types.
Motion Physics & Animation Showcase Framer Motion ✨
Motion Physics
✨
Item 1
Content for item 1.
Hover to trigger spring elevation lift
Format:
Required Animation Library: framer-motion & lucide-react
Install this package in your project before importing the animated component.
npm i framer-motion lucide-react <div class="w-full max-w-md border-b border-border pb-4"><details class="group [&_summary::-webkit-details-marker]:hidden"><summary class="flex cursor-pointer items-center justify-between font-medium py-2"><span>Item 1</span><span class="relative h-5 w-5 shrink-0"><svg class="absolute inset-0 h-5 w-5 opacity-100 group-open:opacity-0 transition-opacity" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 4v16m8-8H4"></path></svg><svg class="absolute inset-0 h-5 w-5 opacity-0 group-open:opacity-100 transition-opacity" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M20 12H4"></path></svg></span></summary><p class="text-muted-foreground mt-2">Content for item 1.</p></details></div> <div className="w-full max-w-md border-b border-border pb-4"><details className="group [&_summary::-webkit-details-marker]:hidden"><summary className="flex cursor-pointer items-center justify-between font-medium py-2"><span>Item 1</span><span className="relative h-5 w-5 shrink-0"><svg className="absolute inset-0 h-5 w-5 opacity-100 group-open:opacity-0 transition-opacity" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 4v16m8-8H4"></path></svg><svg className="absolute inset-0 h-5 w-5 opacity-0 group-open:opacity-100 transition-opacity" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M20 12H4"></path></svg></span></summary><p className="text-muted-foreground mt-2">Content for item 1.</p></details></div> "use client";
import React, { useState } from "react";
interface AccordionItem {
id: string;
title: string;
content: string;
}
const defaultItems: AccordionItem[] = [
{ id: "1", title: "What is ToolMate UI?", content: "A production-grade copy-and-paste Tailwind CSS UI library." },
{ id: "2", title: "How do I install dependencies?", content: "No npm packages required. Copy the code straight into your project." },
{ id: "3", title: "Is TypeScript supported?", content: "Yes! Every functional component includes strict TypeScript definitions." },
];
export default function AccPlusMinus({
items = defaultItems,
}: {
items?: AccordionItem[];
}) {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId((prev) => (prev === id ? null : id));
};
return (
<div className="w-full max-w-md space-y-2.5">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden transition-all">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs sm:text-sm text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<svg
className={`h-4 w-4 text-muted-foreground transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
)}
</div>
);
})}
</div>
);
} "use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown } from "lucide-react";
const items = [
{ id: "1", title: "Smooth spring transitions", content: "Powered by Framer Motion physics for ultra-fluid collapse animations." },
{ id: "2", title: "Zero CSS calculation lag", content: "Uses auto-height transitions with zero hardcoded pixel heights." },
{ id: "3", title: "Accessible keyboard navigation", content: "Supports space and enter keys for full WAI-ARIA compliance." },
];
export default function AnimatedAccPlusMinus() {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId(openId === id ? null : id);
};
return (
<div className="w-full max-w-md space-y-3">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<motion.div
animate={{ rotate: isOpen ? 180 : 0 }}
transition={{ duration: 0.25 }}
>
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</motion.div>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25, ease: "easeInOut" }}
>
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
);
} Colored Accordion
Item 1
Colored content for item 1.
Live Functional Playground Interactive Hooks • useState
No bloated npm dependencies. Copy production-grade TypeScript components directly into your codebase.
Yes! Every component includes functional React variants with useState, useEffect, and strict TypeScript types.
Motion Physics & Animation Showcase Framer Motion ✨
Motion Physics
✨
Item 1
Colored content for item 1.
Hover to trigger spring elevation lift
Format:
Required Animation Library: framer-motion & lucide-react
Install this package in your project before importing the animated component.
npm i framer-motion lucide-react <div class="w-full max-w-md space-y-2"><details class="group [&_summary::-webkit-details-marker]:hidden rounded-lg bg-primary/5 text-primary"><summary class="flex cursor-pointer items-center justify-between font-medium p-4"><span>Item 1</span><span class="transition group-open:rotate-180"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 9l6 6 6-6"></path></svg></span></summary><p class="px-4 pb-4 opacity-80">Colored content for item 1.</p></details></div> <div className="w-full max-w-md space-y-2"><details className="group [&_summary::-webkit-details-marker]:hidden rounded-lg bg-primary/5 text-primary"><summary className="flex cursor-pointer items-center justify-between font-medium p-4"><span>Item 1</span><span className="transition group-open:rotate-180"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 9l6 6 6-6"></path></svg></span></summary><p className="px-4 pb-4 opacity-80">Colored content for item 1.</p></details></div> "use client";
import React, { useState } from "react";
interface AccordionItem {
id: string;
title: string;
content: string;
}
const defaultItems: AccordionItem[] = [
{ id: "1", title: "What is ToolMate UI?", content: "A production-grade copy-and-paste Tailwind CSS UI library." },
{ id: "2", title: "How do I install dependencies?", content: "No npm packages required. Copy the code straight into your project." },
{ id: "3", title: "Is TypeScript supported?", content: "Yes! Every functional component includes strict TypeScript definitions." },
];
export default function AccColored({
items = defaultItems,
}: {
items?: AccordionItem[];
}) {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId((prev) => (prev === id ? null : id));
};
return (
<div className="w-full max-w-md space-y-2.5">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden transition-all">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs sm:text-sm text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<svg
className={`h-4 w-4 text-muted-foreground transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
)}
</div>
);
})}
</div>
);
} "use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown } from "lucide-react";
const items = [
{ id: "1", title: "Smooth spring transitions", content: "Powered by Framer Motion physics for ultra-fluid collapse animations." },
{ id: "2", title: "Zero CSS calculation lag", content: "Uses auto-height transitions with zero hardcoded pixel heights." },
{ id: "3", title: "Accessible keyboard navigation", content: "Supports space and enter keys for full WAI-ARIA compliance." },
];
export default function AnimatedAccColored() {
const [openId, setOpenId] = useState<string | null>("1");
const toggle = (id: string) => {
setOpenId(openId === id ? null : id);
};
return (
<div className="w-full max-w-md space-y-3">
{items.map((item) => {
const isOpen = openId === item.id;
return (
<div key={item.id} className="rounded-xl border border-border bg-card overflow-hidden">
<button
type="button"
onClick={() => toggle(item.id)}
className="w-full flex items-center justify-between p-4 text-left font-semibold text-xs text-foreground hover:text-primary transition-colors"
>
<span>{item.title}</span>
<motion.div
animate={{ rotate: isOpen ? 180 : 0 }}
transition={{ duration: 0.25 }}
>
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</motion.div>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25, ease: "easeInOut" }}
>
<div className="px-4 pb-4 pt-1 text-xs text-muted-foreground leading-relaxed border-t border-border/40">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
);
}