Filled Accordion
Original · freeFilled, tinted accordion panels; the active one is highlighted.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/accordion-filled.json"use client";
import { useId, useRef, useState, type KeyboardEvent } from "react";
type Item = {
id: string;
question: string;
answer: string;
tint: "indigo" | "violet" | "emerald" | "amber";
};
const ITEMS: Item[] = [
{
id: "billing",
question: "How does billing work after the free trial ends?",
answer:
"Your card is charged on the day the 14-day trial closes, then monthly on that same date. We email a receipt every cycle and you can switch to annual billing anytime to save two months.",
tint: "indigo",
},
{
id: "seats",
question: "Can I add or remove teammates mid-cycle?",
answer:
"Yes. Add a seat and you are prorated for the days remaining in the current month. Remove one and the credit is applied to your next invoice automatically — no support ticket required.",
tint: "violet",
},
{
id: "export",
question: "Do I own my data, and can I export it?",
answer:
"Every project you create is yours. Export a full JSON or CSV archive from Settings → Data at any time, including deleted items kept in the 30-day recovery window.",
tint: "emerald",
},
{
id: "cancel",
question: "What happens to my content if I cancel?",
answer:
"Your workspace switches to read-only at the end of the paid period. Nothing is deleted for 90 days, so you can reactivate and pick up exactly where you left off.",
tint: "amber",
},
];
const TINTS: Record<
Item["tint"],
{
idle: string;
active: string;
ring: string;
dot: string;
chevron: string;
heading: string;
}
> = {
indigo: {
idle: "border-indigo-200/70 bg-indigo-50/60 hover:bg-indigo-50 dark:border-indigo-400/20 dark:bg-indigo-500/10 dark:hover:bg-indigo-500/[0.15]",
active:
"border-indigo-300 bg-indigo-100/80 shadow-indigo-500/10 dark:border-indigo-400/40 dark:bg-indigo-500/20",
ring: "focus-visible:ring-indigo-500/60",
dot: "bg-indigo-500",
chevron: "text-indigo-600 dark:text-indigo-300",
heading: "text-indigo-950 dark:text-indigo-50",
},
violet: {
idle: "border-violet-200/70 bg-violet-50/60 hover:bg-violet-50 dark:border-violet-400/20 dark:bg-violet-500/10 dark:hover:bg-violet-500/[0.15]",
active:
"border-violet-300 bg-violet-100/80 shadow-violet-500/10 dark:border-violet-400/40 dark:bg-violet-500/20",
ring: "focus-visible:ring-violet-500/60",
dot: "bg-violet-500",
chevron: "text-violet-600 dark:text-violet-300",
heading: "text-violet-950 dark:text-violet-50",
},
emerald: {
idle: "border-emerald-200/70 bg-emerald-50/60 hover:bg-emerald-50 dark:border-emerald-400/20 dark:bg-emerald-500/10 dark:hover:bg-emerald-500/[0.15]",
active:
"border-emerald-300 bg-emerald-100/80 shadow-emerald-500/10 dark:border-emerald-400/40 dark:bg-emerald-500/20",
ring: "focus-visible:ring-emerald-500/60",
dot: "bg-emerald-500",
chevron: "text-emerald-600 dark:text-emerald-300",
heading: "text-emerald-950 dark:text-emerald-50",
},
amber: {
idle: "border-amber-200/70 bg-amber-50/60 hover:bg-amber-50 dark:border-amber-400/20 dark:bg-amber-500/10 dark:hover:bg-amber-500/[0.15]",
active:
"border-amber-300 bg-amber-100/80 shadow-amber-500/10 dark:border-amber-400/40 dark:bg-amber-500/20",
ring: "focus-visible:ring-amber-500/60",
dot: "bg-amber-500",
chevron: "text-amber-600 dark:text-amber-300",
heading: "text-amber-950 dark:text-amber-50",
},
};
export default function AccordionFilled() {
const [openId, setOpenId] = useState<string | null>(ITEMS[0].id);
const baseId = useId();
const btnRefs = useRef<Array<HTMLButtonElement | null>>([]);
const toggle = (id: string) => {
setOpenId((cur) => (cur === id ? null : id));
};
const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>, index: number) => {
const last = ITEMS.length - 1;
let next = -1;
if (e.key === "ArrowDown") next = index === last ? 0 : index + 1;
else if (e.key === "ArrowUp") next = index === 0 ? last : index - 1;
else if (e.key === "Home") next = 0;
else if (e.key === "End") next = last;
if (next < 0) return;
e.preventDefault();
btnRefs.current[next]?.focus();
};
return (
<section className="relative w-full bg-white px-4 py-20 sm:px-6 sm:py-28 dark:bg-slate-950">
<style>{`
@keyframes accfilled_fade {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.accfilled-panel[data-open="true"] .accfilled-inner {
animation: accfilled_fade 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
@media (prefers-reduced-motion: reduce) {
.accfilled-panel { transition: none !important; }
.accfilled-panel[data-open="true"] .accfilled-inner { animation: none !important; }
.accfilled-chevron { transition: none !important; }
}
`}</style>
<div className="mx-auto max-w-2xl">
<div className="mb-10 text-center">
<span className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs font-medium tracking-wide text-slate-600 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-400">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
Billing & accounts
</span>
<h2 className="mt-4 text-3xl font-semibold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
Questions, answered
</h2>
<p className="mt-3 text-base text-slate-500 dark:text-slate-400">
Everything about trials, seats, and your data — one panel at a time.
</p>
</div>
<div className="flex flex-col gap-3">
{ITEMS.map((item, i) => {
const open = openId === item.id;
const t = TINTS[item.tint];
const headingId = `${baseId}-h-${item.id}`;
const panelId = `${baseId}-p-${item.id}`;
return (
<div
key={item.id}
data-open={open}
className={[
"accfilled-panel rounded-2xl border shadow-sm transition-colors duration-300",
open ? t.active : t.idle,
].join(" ")}
>
<h3 id={headingId} className="m-0">
<button
type="button"
ref={(el) => {
btnRefs.current[i] = el;
}}
aria-expanded={open}
aria-controls={panelId}
onClick={() => toggle(item.id)}
onKeyDown={(e) => onKeyDown(e, i)}
className={[
"flex w-full items-center gap-4 rounded-2xl px-5 py-4 text-left outline-none transition",
"focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950",
t.ring,
].join(" ")}
>
<span
aria-hidden="true"
className={[
"h-2 w-2 shrink-0 rounded-full transition-transform duration-300",
t.dot,
open ? "scale-125" : "scale-100 opacity-60",
].join(" ")}
/>
<span
className={[
"flex-1 text-base font-medium leading-snug",
open ? t.heading : "text-slate-800 dark:text-slate-200",
].join(" ")}
>
{item.question}
</span>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className={[
"accfilled-chevron h-5 w-5 shrink-0 transition-transform duration-300 ease-out",
t.chevron,
open ? "rotate-180" : "rotate-0",
].join(" ")}
>
<path d="m6 9 6 6 6-6" />
</svg>
</button>
</h3>
<div
id={panelId}
role="region"
aria-labelledby={headingId}
hidden={!open}
className="accfilled-inner px-5 pb-5"
>
<div className="pl-6">
<p className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
{item.answer}
</p>
</div>
</div>
</div>
);
})}
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Simple Accordion
OriginalA clean single-column accordion, one panel open at a time, chevron rotates.

Multi Accordion
OriginalAn accordion where multiple panels can be open at once.

Plus Minus Accordion
OriginalAccordion with a plus/minus icon that toggles per item.

Bordered Accordion
OriginalBordered card-style accordion panels with rounded corners.

FAQ Two Col Accordion
OriginalA two-column FAQ accordion layout.

Numbered Accordion
OriginalA numbered-step accordion (01, 02, 03...).
Icon Left Accordion
OriginalAccordion with a leading icon per item and a right chevron.

Gradient Active Accordion
OriginalAccordion where the open panel gets a soft gradient background.

Nested Accordion
OriginalAn accordion with nested sub-accordions.

Flush Accordion
OriginalA borderless, flush accordion separated by hairlines.

Pill Accordion
OriginalAccordion with rounded pill-shaped headers.

Dark Card Accordion
OriginalA dark, glassy card accordion.

