Dark CTA
Original · freedark CTA section
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/ctax-dark.json"use client";
import {
useId,
useRef,
useState,
type FormEvent,
type KeyboardEvent,
} from "react";
import { motion, useReducedMotion, type MotionProps } from "motion/react";
type Audience = "startups" | "enterprise";
type Status = "idle" | "invalid" | "success";
type Stat = { value: string; label: string };
type AudienceCopy = {
tab: string;
heading: string;
sub: string;
stats: Stat[];
};
const TABS: Audience[] = ["startups", "enterprise"];
const AUDIENCES: Record<Audience, AudienceCopy> = {
startups: {
tab: "For startups",
heading: "Launch faster than your runway shrinks.",
sub: "Push to a global edge in minutes, scale straight through your first ten thousand users, and stop babysitting infrastructure you were never hired to run.",
stats: [
{ value: "4 min", label: "median first deploy" },
{ value: "12k+", label: "teams on the free tier" },
{ value: "99.99%", label: "uptime, tracked in public" },
],
},
enterprise: {
tab: "For enterprise",
heading: "Standardize every deploy across every team.",
sub: "Role-based access, immutable audit logs, private networking, and a senior on-call line — all governed from a single control plane your security team will actually sign off on.",
stats: [
{ value: "SOC 2", label: "Type II certified" },
{ value: "50 ms", label: "p95 API latency" },
{ value: "24 / 7", label: "senior on-call support" },
],
},
};
const AVATAR_GRADIENTS = [
"from-indigo-400 to-violet-500",
"from-sky-400 to-indigo-500",
"from-violet-400 to-rose-400",
"from-emerald-400 to-sky-500",
];
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export default function CtaxDark() {
const prefersReduced = useReducedMotion();
const uid = useId();
const [audience, setAudience] = useState<Audience>("startups");
const [email, setEmail] = useState("");
const [status, setStatus] = useState<Status>("idle");
const tabRefs = useRef<Array<HTMLButtonElement | null>>([]);
const copy = AUDIENCES[audience];
const emailId = `${uid}-email`;
const errId = `${uid}-email-error`;
const panelId = `${uid}-panel`;
const reveal = (delay: number): MotionProps =>
prefersReduced
? {}
: {
initial: { opacity: 0, y: 24 },
whileInView: { opacity: 1, y: 0 },
viewport: { once: true, margin: "-60px" },
transition: { duration: 0.6, delay, ease: "easeOut" },
};
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (EMAIL_RE.test(email.trim())) {
setStatus("success");
} else {
setStatus("invalid");
}
};
const handleTabKey = (
e: KeyboardEvent<HTMLButtonElement>,
index: number,
) => {
const keys = ["ArrowRight", "ArrowLeft", "Home", "End"];
if (!keys.includes(e.key)) return;
e.preventDefault();
let next = index;
if (e.key === "ArrowRight") next = (index + 1) % TABS.length;
else if (e.key === "ArrowLeft") next = (index - 1 + TABS.length) % TABS.length;
else if (e.key === "Home") next = 0;
else next = TABS.length - 1;
setAudience(TABS[next]);
tabRefs.current[next]?.focus();
};
return (
<section className="relative w-full overflow-hidden bg-slate-50 px-4 py-24 dark:bg-slate-950 sm:py-32">
<style>{`
@keyframes ctaxdk-drift-a {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(26px, -30px, 0) scale(1.08); }
}
@keyframes ctaxdk-drift-b {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(-28px, 24px, 0) scale(1.1); }
}
@keyframes ctaxdk-spin {
to { transform: rotate(360deg); }
}
@keyframes ctaxdk-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.35; transform: scale(0.65); }
}
@keyframes ctaxdk-shimmer {
0% { background-position: 0% 50%; }
100% { background-position: 200% 50%; }
}
.ctaxdk-orb-a { animation: ctaxdk-drift-a 16s ease-in-out infinite; }
.ctaxdk-orb-b { animation: ctaxdk-drift-b 19s ease-in-out infinite; }
.ctaxdk-sheen { animation: ctaxdk-spin 26s linear infinite; }
.ctaxdk-dot { animation: ctaxdk-pulse 2.4s ease-in-out infinite; }
.ctaxdk-shimmer {
background-size: 200% auto;
animation: ctaxdk-shimmer 5s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.ctaxdk-orb-a,
.ctaxdk-orb-b,
.ctaxdk-sheen,
.ctaxdk-dot,
.ctaxdk-shimmer {
animation: none !important;
}
}
`}</style>
<div className="relative mx-auto max-w-6xl">
{/* soft colored glow behind the dark panel */}
<div
aria-hidden="true"
className="pointer-events-none absolute -inset-x-6 -bottom-12 -top-12"
>
<div className="ctaxdk-orb-a absolute left-[8%] top-0 h-56 w-56 rounded-full bg-indigo-500/30 blur-3xl dark:bg-indigo-500/20" />
<div className="ctaxdk-orb-b absolute bottom-0 right-[6%] h-64 w-64 rounded-full bg-violet-500/30 blur-3xl dark:bg-violet-500/20" />
</div>
<motion.div
{...reveal(0)}
className="relative overflow-hidden rounded-[2rem] border border-white/10 bg-slate-900 px-6 py-14 shadow-2xl shadow-slate-950/40 sm:px-12 sm:py-16 lg:px-16"
>
{/* inner ambient decoration (dark in both themes by design) */}
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
{/* rotating conic sheen */}
<div className="ctaxdk-sheen absolute left-1/2 top-1/2 h-[140%] w-[140%] -translate-x-1/2 -translate-y-1/2 opacity-40 [background:conic-gradient(from_0deg,transparent_0deg,rgba(99,102,241,0.25)_60deg,transparent_140deg,rgba(139,92,246,0.22)_260deg,transparent_360deg)]" />
{/* drifting orbs */}
<div className="ctaxdk-orb-a absolute -left-10 top-4 h-52 w-52 rounded-full bg-indigo-600/40 blur-3xl" />
<div className="ctaxdk-orb-b absolute -right-8 bottom-2 h-56 w-56 rounded-full bg-violet-600/40 blur-3xl" />
{/* fine grid, faded toward edges */}
<div
className="absolute inset-0 opacity-[0.18] [background-image:linear-gradient(to_right,rgba(255,255,255,0.6)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.6)_1px,transparent_1px)] [background-size:44px_44px]"
style={{
maskImage:
"radial-gradient(120% 120% at 50% 0%, black 20%, transparent 72%)",
WebkitMaskImage:
"radial-gradient(120% 120% at 50% 0%, black 20%, transparent 72%)",
}}
/>
{/* top hairline highlight */}
<div className="absolute inset-x-8 top-0 h-px bg-gradient-to-r from-transparent via-white/40 to-transparent" />
</div>
<div className="relative z-10 mx-auto max-w-3xl text-center">
{/* eyebrow badge */}
<motion.span
{...reveal(0.05)}
className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3.5 py-1.5 text-xs font-medium text-slate-300 backdrop-blur"
>
<span className="relative flex h-2 w-2">
<span className="ctaxdk-dot absolute inline-flex h-full w-full rounded-full bg-emerald-400" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-400" />
</span>
Free tier forever · No credit card required
</motion.span>
{/* audience tabs */}
<motion.div {...reveal(0.1)} className="mt-6 flex justify-center">
<div
role="tablist"
aria-label="Choose your team size"
className="inline-flex rounded-full border border-white/10 bg-white/5 p-1 backdrop-blur"
>
{TABS.map((key, i) => {
const selected = key === audience;
return (
<button
key={key}
ref={(el) => {
tabRefs.current[i] = el;
}}
role="tab"
id={`${uid}-tab-${key}`}
type="button"
aria-selected={selected}
aria-controls={panelId}
tabIndex={selected ? 0 : -1}
onClick={() => setAudience(key)}
onKeyDown={(e) => handleTabKey(e, i)}
className={`rounded-full px-4 py-1.5 text-sm font-medium transition focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 ${
selected
? "bg-white text-slate-900 shadow-sm"
: "text-slate-300 hover:text-white"
}`}
>
{AUDIENCES[key].tab}
</button>
);
})}
</div>
</motion.div>
{/* headline + sub (tab panel) */}
<div
role="tabpanel"
id={panelId}
aria-labelledby={`${uid}-tab-${audience}`}
>
<motion.h2
key={`h-${audience}`}
initial={prefersReduced ? false : { opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, ease: "easeOut" }}
className="mt-7 text-balance text-4xl font-semibold tracking-tight text-white sm:text-5xl"
>
{copy.heading}
</motion.h2>
<motion.p
key={`p-${audience}`}
initial={prefersReduced ? false : { opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35, delay: 0.05, ease: "easeOut" }}
className="mx-auto mt-5 max-w-2xl text-pretty text-base leading-relaxed text-slate-300 sm:text-lg"
>
{copy.sub}
</motion.p>
</div>
{/* email capture / success */}
<motion.div {...reveal(0.15)}>
{status === "success" ? (
<div
role="status"
className="mx-auto mt-9 flex max-w-md items-center gap-3 rounded-2xl border border-emerald-400/25 bg-emerald-400/10 px-5 py-4 text-left"
>
<span className="flex h-9 w-9 flex-none items-center justify-center rounded-full bg-emerald-400/20 text-emerald-300">
<svg
viewBox="0 0 20 20"
fill="none"
className="h-5 w-5"
aria-hidden="true"
>
<path
d="M4.5 10.5l3.5 3.5 7.5-8"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</span>
<div className="min-w-0">
<p className="text-sm font-semibold text-emerald-100">
You’re on the list.
</p>
<p className="truncate text-sm text-emerald-100/70">
We sent a magic link to {email.trim()}.
</p>
</div>
<button
type="button"
onClick={() => {
setStatus("idle");
setEmail("");
}}
className="ml-auto flex-none rounded-md px-2 py-1 text-xs font-medium text-emerald-200/80 underline-offset-2 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-400"
>
Undo
</button>
</div>
) : (
<>
<form
onSubmit={handleSubmit}
noValidate
className="mx-auto mt-9 flex w-full max-w-md flex-col gap-3 sm:flex-row"
>
<div className="flex-1 text-left">
<label htmlFor={emailId} className="sr-only">
Work email
</label>
<input
id={emailId}
name="email"
type="email"
inputMode="email"
autoComplete="email"
placeholder="you@company.com"
value={email}
onChange={(e) => {
setEmail(e.target.value);
if (status !== "idle") setStatus("idle");
}}
aria-invalid={status === "invalid"}
aria-describedby={status === "invalid" ? errId : undefined}
className={`h-12 w-full rounded-xl border bg-white/5 px-4 text-sm text-white placeholder-slate-400 outline-none transition focus:bg-white/10 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 ${
status === "invalid"
? "border-rose-400/60 focus-visible:ring-rose-400"
: "border-white/10 focus-visible:ring-indigo-400"
}`}
/>
</div>
<button
type="submit"
className="group relative inline-flex h-12 flex-none items-center justify-center gap-2 overflow-hidden rounded-xl bg-gradient-to-r from-indigo-500 to-violet-500 px-6 text-sm font-semibold text-white shadow-lg shadow-indigo-500/30 transition hover:from-indigo-400 hover:to-violet-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-300 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
>
Start building free
<svg
viewBox="0 0 20 20"
fill="none"
aria-hidden="true"
className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5"
>
<path
d="M4 10h11m0 0l-4-4m4 4l-4 4"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</form>
{status === "invalid" && (
<p
id={errId}
role="alert"
className="mx-auto mt-2.5 max-w-md text-left text-sm text-rose-300"
>
Please enter a valid email address.
</p>
)}
<p className="mt-3 text-xs text-slate-400">
14-day Pro trial · Cancel anytime · SOC 2 Type II
</p>
</>
)}
</motion.div>
{/* stats */}
<motion.dl
{...reveal(0.2)}
className="mx-auto mt-12 grid max-w-2xl grid-cols-1 gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/5 sm:grid-cols-3"
>
{copy.stats.map((s) => (
<div key={s.label} className="bg-slate-900/40 px-6 py-6 text-center">
<dt className="sr-only">{s.label}</dt>
<dd>
<span className="block text-2xl font-semibold text-white">
{s.value}
</span>
<span className="mt-1 block text-xs text-slate-400">
{s.label}
</span>
</dd>
</div>
))}
</motion.dl>
{/* social proof + secondary CTA */}
<motion.div
{...reveal(0.25)}
className="mt-10 flex flex-col items-center gap-5 sm:flex-row sm:justify-center sm:gap-6"
>
<div className="flex items-center gap-3">
<div className="flex -space-x-2" aria-hidden="true">
{AVATAR_GRADIENTS.map((g, i) => (
<span
key={i}
className={`h-8 w-8 rounded-full bg-gradient-to-br ${g} ring-2 ring-slate-900`}
/>
))}
</div>
<p className="text-sm text-slate-400">
<span className="ctaxdk-shimmer bg-gradient-to-r from-indigo-300 via-white to-violet-300 bg-clip-text font-semibold text-transparent">
2,400 teams
</span>{" "}
shipped to Orbit this week
</p>
</div>
<a
href="#book-a-demo"
className="inline-flex items-center gap-1.5 rounded-xl border border-white/15 bg-white/0 px-5 py-2.5 text-sm font-medium text-slate-200 transition hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
>
Book a demo
<svg
viewBox="0 0 20 20"
fill="none"
aria-hidden="true"
className="h-4 w-4"
>
<path
d="M7 5l5 5-5 5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</a>
</motion.div>
</div>
</motion.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 →
Gradient CTA
OriginalA bold gradient call-to-action banner with two buttons.

Full-Bleed Dark CTA
OriginalA full-bleed dark call-to-action banner with a live status badge, subtle grid backdrop and a primary plus secondary button pair to drive discovery calls.

Split CTA With Lead Form
OriginalA two-column call-to-action that pairs a benefit checklist with an inline name, email and message form for capturing qualified leads directly in the section.

Newsletter CTA With Stats
OriginalA newsletter sign-up call-to-action combining an inline email subscribe form with a three-cell social-proof stat strip to boost conversions.

Aurora Glow CTA Banner
OriginalA full-bleed dark call-to-action banner with a rotating aurora gradient, floating orbs and a shine-on-hover button, with staggered blur-in text that reveals on scroll.

Animated Border Beam CTA
OriginalAn email-capture call-to-action card wrapped in a rotating conic-gradient border beam, with staggered reveal, animated shine text and an inline success state on submit.

Marquee Ribbon CTA
OriginalA dark call-to-action band framed by two infinite scrolling tag ribbons with a continuously flowing animated-gradient button and staggered centre content.

Centered CTA
Originalcentred CTA band

Split CTA
Originalsplit CTA with image side

Gradient Band CTA
Originalgradient CTA band

Newsletter CTA
OriginalCTA with newsletter input

App Download CTA
Originalapp download CTA with store buttons

