Numbered Accordion
Original · freeA numbered-step accordion (01, 02, 03...).
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-numbered.json"use client";
import { useId, useState, type KeyboardEvent } from "react";
type Step = {
title: string;
body: string;
meta: string;
};
const STEPS: Step[] = [
{
title: "Discovery & scope",
meta: "Week 1",
body: "We map your goals, audience and constraints in a single working session, then translate them into a written brief with success metrics everyone signs off on. No build starts until the scope is unambiguous.",
},
{
title: "Wireframes & flow",
meta: "Week 1–2",
body: "Low-fidelity screens establish structure, navigation and content hierarchy before a single pixel is styled. You review clickable flows early, so layout debates happen when they are cheap to resolve.",
},
{
title: "Visual design",
meta: "Week 2–3",
body: "We apply your brand into a responsive design system: type scale, colour tokens, spacing and reusable components. Every screen is delivered in both light and dark, ready for handoff.",
},
{
title: "Build & integrate",
meta: "Week 3–5",
body: "Front-end is built as accessible, component-driven code and wired to your CMS or APIs. We commit in small, reviewable increments with a staging URL you can open at any time.",
},
{
title: "QA & launch",
meta: "Week 5–6",
body: "Cross-browser testing, Lighthouse passes and an accessibility audit run before go-live. We ship behind a checklist, then monitor the first 48 hours and hand over full documentation.",
},
];
export default function AccordionNumbered() {
const [open, setOpen] = useState<number | null>(0);
const baseId = useId();
const toggle = (i: number) => setOpen((cur) => (cur === i ? null : i));
const onKey = (e: KeyboardEvent<HTMLButtonElement>, i: number) => {
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
e.preventDefault();
const dir = e.key === "ArrowDown" ? 1 : -1;
const next = (i + dir + STEPS.length) % STEPS.length;
const el = document.getElementById(`${baseId}-btn-${next}`);
el?.focus();
} else if (e.key === "Home") {
e.preventDefault();
document.getElementById(`${baseId}-btn-0`)?.focus();
} else if (e.key === "End") {
e.preventDefault();
document.getElementById(`${baseId}-btn-${STEPS.length - 1}`)?.focus();
}
};
return (
<section className="relative w-full bg-white px-5 py-20 text-slate-900 sm:px-8 sm:py-28 dark:bg-slate-950 dark:text-slate-100">
<style>{`
@keyframes accnum-reveal {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
.accnum-panel-inner { animation: none !important; }
.accnum-chevron { transition: none !important; }
.accnum-grid { transition: none !important; }
}
`}</style>
<div className="mx-auto max-w-2xl">
<div className="mb-10 sm:mb-14">
<span className="inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-medium tracking-wide text-indigo-700 dark:border-indigo-400/20 dark:bg-indigo-400/10 dark:text-indigo-300">
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
How we work
</span>
<h2 className="mt-4 text-3xl font-semibold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
A five-step path to launch
</h2>
<p className="mt-3 text-base leading-relaxed text-slate-600 dark:text-slate-400">
Each engagement follows the same predictable rhythm. Expand any
step to see exactly what happens and when.
</p>
</div>
<div className="flex flex-col gap-3">
{STEPS.map((step, i) => {
const isOpen = open === i;
const num = String(i + 1).padStart(2, "0");
return (
<div
key={step.title}
className={`group overflow-hidden rounded-2xl border transition-colors ${
isOpen
? "border-indigo-300 bg-indigo-50/40 dark:border-indigo-400/30 dark:bg-indigo-400/5"
: "border-slate-200 bg-slate-50/60 hover:border-slate-300 dark:border-slate-800 dark:bg-slate-900/40 dark:hover:border-slate-700"
}`}
>
<h3>
<button
id={`${baseId}-btn-${i}`}
type="button"
aria-expanded={isOpen}
aria-controls={`${baseId}-panel-${i}`}
onClick={() => toggle(i)}
onKeyDown={(e) => onKey(e, i)}
className="flex w-full items-center gap-4 rounded-2xl px-4 py-4 text-left outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white sm:px-5 dark:focus-visible:ring-offset-slate-950"
>
<span
aria-hidden="true"
className={`flex h-11 w-11 flex-none items-center justify-center rounded-xl text-sm font-semibold tabular-nums transition-colors ${
isOpen
? "bg-indigo-600 text-white dark:bg-indigo-500"
: "bg-white text-slate-500 ring-1 ring-slate-200 group-hover:text-slate-700 dark:bg-slate-800 dark:text-slate-400 dark:ring-slate-700 dark:group-hover:text-slate-200"
}`}
>
{num}
</span>
<span className="min-w-0 flex-1">
<span className="block truncate text-base font-medium text-slate-900 dark:text-white">
{step.title}
</span>
<span className="mt-0.5 block text-xs font-medium text-slate-500 dark:text-slate-400">
{step.meta}
</span>
</span>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
className={`accnum-chevron h-5 w-5 flex-none text-slate-400 transition-transform duration-300 ${
isOpen ? "rotate-180 text-indigo-600 dark:text-indigo-400" : ""
}`}
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m6 9 6 6 6-6" />
</svg>
</button>
</h3>
<div
id={`${baseId}-panel-${i}`}
role="region"
aria-labelledby={`${baseId}-btn-${i}`}
aria-hidden={!isOpen}
className="accnum-grid grid transition-[grid-template-rows] duration-300 ease-out"
style={{ gridTemplateRows: isOpen ? "1fr" : "0fr" }}
>
<div className="overflow-hidden">
<div
className="accnum-panel-inner pl-[3.75rem] pr-4 pb-5 sm:pr-5"
style={{ animation: "accnum-reveal 0.35s ease both" }}
>
<p className="text-sm leading-relaxed text-slate-600 dark:text-slate-300">
{step.body}
</p>
</div>
</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.

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

FAQ Two Col Accordion
OriginalA two-column FAQ accordion layout.
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.

