Icon Left Accordion
Original · freeAccordion with a leading icon per item and a right chevron.
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-icon-left.json"use client";
import { useId, useRef, useState, type KeyboardEvent, type ReactNode } from "react";
type Item = {
id: string;
title: string;
meta: string;
body: string;
icon: ReactNode;
accent: string;
accentDark: string;
tint: string;
tintDark: string;
};
const items: Item[] = [
{
id: "billing",
title: "How does billing work after the trial ends?",
meta: "Plans & payments",
body: "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 between monthly and annual at any time from Settings, with the difference prorated to the cent.",
accent: "text-indigo-600",
accentDark: "dark:text-indigo-400",
tint: "bg-indigo-50 ring-indigo-100",
tintDark: "dark:bg-indigo-500/10 dark:ring-indigo-500/20",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-5 w-5">
<rect x="2.5" y="5" width="19" height="14" rx="2.5" />
<path d="M2.5 9.5h19" />
<path d="M6.5 15h4" />
</svg>
),
},
{
id: "security",
title: "Where is my data stored and who can see it?",
meta: "Security & privacy",
body: "Everything lives in encrypted storage in the EU region by default, with AES-256 at rest and TLS 1.3 in transit. Only workspace members you invite can read your data, and admins can revoke a session instantly. We never sell or train on your content.",
accent: "text-emerald-600",
accentDark: "dark:text-emerald-400",
tint: "bg-emerald-50 ring-emerald-100",
tintDark: "dark:bg-emerald-500/10 dark:ring-emerald-500/20",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-5 w-5">
<path d="M12 3l7 3v5c0 4.4-3 7.6-7 9-4-1.4-7-4.6-7-9V6l7-3Z" />
<path d="M9.2 12l1.9 1.9L15 10" />
</svg>
),
},
{
id: "team",
title: "Can I add teammates and set their permissions?",
meta: "Workspace & roles",
body: "Invite unlimited teammates by email on every paid plan. Each person gets one of four roles — Owner, Admin, Editor, or Viewer — and you can scope access down to a single project. Pending invites expire after seven days for safety.",
accent: "text-violet-600",
accentDark: "dark:text-violet-400",
tint: "bg-violet-50 ring-violet-100",
tintDark: "dark:bg-violet-500/10 dark:ring-violet-500/20",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-5 w-5">
<circle cx="9" cy="8" r="3.2" />
<path d="M3.5 19a5.5 5.5 0 0 1 11 0" />
<path d="M16 5.2a3.2 3.2 0 0 1 0 5.6" />
<path d="M17.5 13.6A5.5 5.5 0 0 1 20.5 19" />
</svg>
),
},
{
id: "support",
title: "What happens if I need help at 2 a.m.?",
meta: "Support & response times",
body: "Live chat is staffed 9-6 in your local time zone, and urgent tickets after hours are answered within four hours by an on-call engineer. Growth and Scale plans include a private Slack channel with a named contact who actually knows your setup.",
accent: "text-amber-600",
accentDark: "dark:text-amber-400",
tint: "bg-amber-50 ring-amber-100",
tintDark: "dark:bg-amber-500/10 dark:ring-amber-500/20",
icon: (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-5 w-5">
<path d="M4 12a8 8 0 0 1 16 0v4.5a2.5 2.5 0 0 1-2.5 2.5H16" />
<rect x="3" y="12" width="3.5" height="6" rx="1.5" />
<rect x="17.5" y="12" width="3.5" height="6" rx="1.5" />
</svg>
),
},
];
export default function AccordionIconLeft() {
const [open, setOpen] = useState<string | null>("billing");
const uid = useId();
const btnRefs = useRef<Array<HTMLButtonElement | null>>([]);
const toggle = (id: string) => setOpen((cur) => (cur === id ? null : id));
const onKeyNav = (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) {
e.preventDefault();
btnRefs.current[next]?.focus();
}
};
return (
<section className="relative w-full bg-slate-50 px-4 py-20 dark:bg-slate-950 sm:px-6 lg:py-28">
<style>{`
@keyframes acil-reveal {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.acil-panel-inner { animation: acil-reveal .28s cubic-bezier(.16,1,.3,1); }
@media (prefers-reduced-motion: reduce) {
.acil-panel-inner { animation: none; }
.acil-grid, .acil-chevron, .acil-icon { 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 bg-indigo-50 px-3 py-1 text-xs font-semibold uppercase tracking-wider text-indigo-600 ring-1 ring-inset ring-indigo-100 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-500/20">
Answers
</span>
<h2 className="mt-4 text-3xl font-semibold tracking-tight text-slate-900 dark:text-white sm:text-4xl">
Frequently asked questions
</h2>
<p className="mt-3 text-base text-slate-600 dark:text-slate-400">
Everything you need to know before you start. Can't find it here? Chat with us.
</p>
</div>
<div className="space-y-3">
{items.map((item, i) => {
const isOpen = open === item.id;
const panelId = `${uid}-panel-${item.id}`;
const btnId = `${uid}-btn-${item.id}`;
return (
<div
key={item.id}
className={`overflow-hidden rounded-2xl border bg-white shadow-sm transition-colors duration-200 dark:bg-slate-900 ${
isOpen
? "border-indigo-200 dark:border-indigo-500/40"
: "border-slate-200 dark:border-slate-800"
}`}
>
<h3>
<button
id={btnId}
ref={(el) => {
btnRefs.current[i] = el;
}}
type="button"
aria-expanded={isOpen}
aria-controls={panelId}
onClick={() => toggle(item.id)}
onKeyDown={(e) => onKeyNav(e, i)}
className="group flex w-full items-center gap-4 px-4 py-4 text-left outline-none transition-colors hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:hover:bg-slate-800/50 dark:focus-visible:ring-offset-slate-900 sm:px-5"
>
<span
className={`acil-icon flex h-10 w-10 shrink-0 items-center justify-center rounded-xl ring-1 ring-inset transition-transform duration-300 ${item.accent} ${item.accentDark} ${item.tint} ${item.tintDark} ${
isOpen ? "scale-105" : "group-hover:scale-105"
}`}
>
{item.icon}
</span>
<span className="min-w-0 flex-1">
<span className="block text-[11px] font-semibold uppercase tracking-wide text-slate-400 dark:text-slate-500">
{item.meta}
</span>
<span className="mt-0.5 block text-[15px] font-medium leading-snug text-slate-900 dark:text-white sm:text-base">
{item.title}
</span>
</span>
<span
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-slate-400 transition-colors dark:text-slate-500 ${
isOpen
? "bg-indigo-50 text-indigo-600 dark:bg-indigo-500/15 dark:text-indigo-300"
: "group-hover:bg-slate-100 dark:group-hover:bg-slate-800"
}`}
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
className={`acil-chevron h-4.5 w-4.5 transition-transform duration-300 ease-out ${
isOpen ? "rotate-180" : "rotate-0"
}`}
>
<path d="m6 9 6 6 6-6" />
</svg>
</span>
</button>
</h3>
<div
id={panelId}
role="region"
aria-labelledby={btnId}
hidden={!isOpen}
className="acil-grid"
>
<div className="acil-panel-inner px-4 pb-5 pl-[72px] pr-5 sm:pl-[76px]">
<p className="text-sm leading-relaxed text-slate-600 dark:text-slate-400">
{item.body}
</p>
</div>
</div>
</div>
);
})}
</div>
<p className="mt-8 text-center text-sm text-slate-500 dark:text-slate-400">
Still stuck?{" "}
<a
href="#contact"
className="font-medium text-indigo-600 underline-offset-4 hover:underline dark:text-indigo-400"
>
Talk to our team
</a>
.
</p>
</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.

Filled Accordion
OriginalFilled, tinted accordion panels; the active one is highlighted.

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

Numbered Accordion
OriginalA numbered-step accordion (01, 02, 03...).

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.

