CTA Large Button
Original · freeLarge marketing CTA buttons with a sublabel and icon.
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/btn-cta-large.json"use client";
import { useState } from "react";
import { useReducedMotion } from "motion/react";
type Variant = "solid" | "gradient" | "outline" | "soft" | "dark";
type Size = "md" | "lg" | "xl";
const SIZE: Record<
Size,
{ pad: string; icon: string; label: string; sub: string; radius: string; gap: string }
> = {
md: {
pad: "px-5 py-3.5",
icon: "h-9 w-9",
label: "text-[15px]",
sub: "text-[11px]",
radius: "rounded-xl",
gap: "gap-3",
},
lg: {
pad: "px-6 py-4",
icon: "h-11 w-11",
label: "text-base",
sub: "text-xs",
radius: "rounded-2xl",
gap: "gap-3.5",
},
xl: {
pad: "px-7 py-5",
icon: "h-14 w-14",
label: "text-lg",
sub: "text-[13px]",
radius: "rounded-[1.25rem]",
gap: "gap-4",
},
};
function ArrowGlyph({ className = "" }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<path
d="M5 12h13m0 0-5.5-5.5M18 12l-5.5 5.5"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function RocketGlyph({ className = "" }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<path
d="M12 3c3 1.2 5 4.2 5 8.2 0 1.6-.4 3-1 4.2H8c-.6-1.2-1-2.6-1-4.2C7 7.2 9 4.2 12 3Z"
stroke="currentColor"
strokeWidth="1.8"
strokeLinejoin="round"
/>
<circle cx="12" cy="9.5" r="1.7" stroke="currentColor" strokeWidth="1.8" />
<path
d="M8 15.4c-1.6.7-2.4 2-2.6 4 1.9-.2 3.3-.9 4-2.4M16 15.4c1.6.7 2.4 2 2.6 4-1.9-.2-3.3-.9-4-2.4"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function CalendarGlyph({ className = "" }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<rect x="3.5" y="5" width="17" height="15" rx="2.5" stroke="currentColor" strokeWidth="1.8" />
<path
d="M3.5 9.5h17M8 3.5v3M16 3.5v3"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
/>
<path d="M7.5 13.5h3v3h-3z" fill="currentColor" />
</svg>
);
}
function SparkGlyph({ className = "" }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<path
d="M12 3.5c.4 3.6 1.9 5.1 5.5 5.5-3.6.4-5.1 1.9-5.5 5.5-.4-3.6-1.9-5.1-5.5-5.5 3.6-.4 5.1-1.9 5.5-5.5Z"
stroke="currentColor"
strokeWidth="1.7"
strokeLinejoin="round"
/>
<path
d="M18.5 15c.2 1.6.9 2.3 2.5 2.5-1.6.2-2.3.9-2.5 2.5-.2-1.6-.9-2.3-2.5-2.5 1.6-.2 2.3-.9 2.5-2.5Z"
stroke="currentColor"
strokeWidth="1.7"
strokeLinejoin="round"
/>
</svg>
);
}
function Spinner({ className = "" }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className={className}>
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2.4" opacity="0.25" />
<path
d="M21 12a9 9 0 0 0-9-9"
stroke="currentColor"
strokeWidth="2.4"
strokeLinecap="round"
/>
</svg>
);
}
const VARIANT: Record<
Variant,
{
shell: string;
iconWrap: string;
label: string;
sub: string;
ring: string;
}
> = {
solid: {
shell:
"bg-indigo-600 text-white shadow-lg shadow-indigo-600/25 hover:bg-indigo-500 hover:shadow-indigo-600/35 active:bg-indigo-700 border border-indigo-500/60",
iconWrap: "bg-white/15 text-white",
label: "text-white",
sub: "text-indigo-100/80",
ring: "focus-visible:ring-indigo-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950",
},
gradient: {
shell:
"bg-gradient-to-br from-violet-600 via-indigo-600 to-sky-500 text-white shadow-lg shadow-violet-600/30 hover:brightness-110 active:brightness-95 border border-white/10",
iconWrap: "bg-white/20 text-white",
label: "text-white",
sub: "text-violet-100/85",
ring: "focus-visible:ring-violet-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950",
},
outline: {
shell:
"bg-white text-slate-900 border border-slate-300 shadow-sm hover:border-indigo-400 hover:bg-slate-50 active:bg-slate-100 dark:bg-slate-900 dark:text-slate-50 dark:border-slate-700 dark:hover:border-indigo-500 dark:hover:bg-slate-800/70",
iconWrap:
"bg-indigo-50 text-indigo-600 dark:bg-indigo-500/15 dark:text-indigo-300",
label: "text-slate-900 dark:text-slate-50",
sub: "text-slate-500 dark:text-slate-400",
ring: "focus-visible:ring-indigo-500 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950",
},
soft: {
shell:
"bg-indigo-50 text-indigo-950 border border-indigo-100 shadow-sm hover:bg-indigo-100 active:bg-indigo-200/70 dark:bg-indigo-500/10 dark:text-indigo-50 dark:border-indigo-400/20 dark:hover:bg-indigo-500/20",
iconWrap: "bg-indigo-600 text-white dark:bg-indigo-500",
label: "text-indigo-950 dark:text-indigo-50",
sub: "text-indigo-700/70 dark:text-indigo-200/70",
ring: "focus-visible:ring-indigo-500 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950",
},
dark: {
shell:
"bg-slate-900 text-white border border-slate-800 shadow-lg shadow-black/20 hover:bg-slate-800 active:bg-slate-950 dark:bg-slate-50 dark:text-slate-900 dark:border-slate-200 dark:hover:bg-white",
iconWrap:
"bg-white/10 text-white dark:bg-slate-900/10 dark:text-slate-900",
label: "text-white dark:text-slate-900",
sub: "text-slate-400 dark:text-slate-500",
ring: "focus-visible:ring-slate-400 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950",
},
};
type Glyph = "arrow" | "rocket" | "calendar" | "spark";
function renderGlyph(g: Glyph, className: string) {
switch (g) {
case "rocket":
return <RocketGlyph className={className} />;
case "calendar":
return <CalendarGlyph className={className} />;
case "spark":
return <SparkGlyph className={className} />;
default:
return <ArrowGlyph className={className} />;
}
}
interface CtaProps {
variant: Variant;
size?: Size;
glyph?: Glyph;
sublabel: string;
label: string;
onActivate?: () => void;
loading?: boolean;
disabled?: boolean;
reduced: boolean;
}
function CtaButton({
variant,
size = "lg",
glyph = "arrow",
sublabel,
label,
onActivate,
loading = false,
disabled = false,
reduced,
}: CtaProps) {
const s = SIZE[size];
const v = VARIANT[variant];
const iconInner = size === "xl" ? "h-6 w-6" : size === "lg" ? "h-5 w-5" : "h-[18px] w-[18px]";
return (
<button
type="button"
onClick={onActivate}
disabled={disabled || loading}
aria-busy={loading || undefined}
className={[
"group relative inline-flex w-full items-center overflow-hidden text-left",
"transition-all duration-200 ease-out outline-none select-none",
"focus-visible:ring-2 focus-visible:ring-offset-2",
reduced ? "" : "hover:-translate-y-0.5 active:translate-y-0",
"disabled:cursor-not-allowed disabled:opacity-55 disabled:hover:translate-y-0",
s.pad,
s.radius,
s.gap,
v.shell,
v.ring,
].join(" ")}
>
{!reduced && (
<span
aria-hidden="true"
className="cta-sheen pointer-events-none absolute inset-y-0 -left-3/4 w-1/2 -skew-x-12 bg-white/20 opacity-0 group-hover:opacity-100"
/>
)}
<span
aria-hidden="true"
className={[
"relative grid shrink-0 place-items-center rounded-xl transition-transform duration-300",
reduced ? "" : "group-hover:scale-105 group-active:scale-95",
s.icon,
v.iconWrap,
].join(" ")}
>
{loading ? (
<Spinner className={`cta-spin ${iconInner}`} />
) : (
renderGlyph(glyph, iconInner)
)}
</span>
<span className="relative flex min-w-0 flex-1 flex-col leading-tight">
<span className={`font-medium tracking-wide uppercase ${s.sub} ${v.sub}`}>
{loading ? "Just a moment" : sublabel}
</span>
<span className={`truncate font-semibold ${s.label} ${v.label}`}>{label}</span>
</span>
<span
aria-hidden="true"
className={[
"relative shrink-0 opacity-70 transition-transform duration-300",
reduced ? "" : "group-hover:translate-x-1",
v.label,
].join(" ")}
>
<ArrowGlyph className={size === "xl" ? "h-5 w-5" : "h-4 w-4"} />
</span>
</button>
);
}
export default function BtnCtaLarge() {
const reduced = useReducedMotion() ?? false;
const [loading, setLoading] = useState(false);
const [count, setCount] = useState(0);
function simulate() {
if (loading) return;
setLoading(true);
window.setTimeout(() => {
setLoading(false);
setCount((c) => c + 1);
}, 1600);
}
return (
<section className="relative w-full bg-slate-50 px-4 py-16 sm:px-6 sm:py-24 dark:bg-slate-950">
<style>{`
@keyframes ctacta_sheen {
0% { transform: translateX(0) skewX(-12deg); }
100% { transform: translateX(340%) skewX(-12deg); }
}
@keyframes ctacta_spin {
to { transform: rotate(360deg); }
}
.cta-sheen { animation: ctacta_sheen 0.9s ease-in-out; }
.cta-spin { animation: ctacta_spin 0.8s linear infinite; transform-origin: center; }
@media (prefers-reduced-motion: reduce) {
.cta-sheen, .cta-spin { animation: none !important; }
}
`}</style>
<div className="mx-auto max-w-4xl">
<header className="mb-12 text-center">
<span className="inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-white px-3 py-1 text-xs font-medium text-indigo-600 dark:border-indigo-400/25 dark:bg-slate-900 dark:text-indigo-300">
<SparkGlyph className="h-3.5 w-3.5" />
FreeCode / Buttons
</span>
<h2 className="mt-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
Large CTA Buttons
</h2>
<p className="mx-auto mt-3 max-w-lg text-sm text-slate-600 sm:text-base dark:text-slate-400">
Tactile marketing calls-to-action with a sublabel, icon and motion — five
finishes, three sizes, real loading and disabled states.
</p>
</header>
<div className="grid gap-5 sm:grid-cols-2">
<CtaButton
variant="gradient"
size="xl"
glyph="rocket"
sublabel="No card required"
label="Start your free trial"
reduced={reduced}
onActivate={simulate}
loading={loading}
/>
<CtaButton
variant="solid"
size="xl"
glyph="calendar"
sublabel="30-minute walkthrough"
label="Book a live demo"
reduced={reduced}
/>
<CtaButton
variant="outline"
size="lg"
glyph="spark"
sublabel="Join 12,000+ teams"
label="Get the Pro plan"
reduced={reduced}
/>
<CtaButton
variant="soft"
size="lg"
glyph="arrow"
sublabel="From $19 / month"
label="Compare all plans"
reduced={reduced}
/>
<CtaButton
variant="dark"
size="md"
glyph="rocket"
sublabel="Ships in minutes"
label="Deploy to production"
reduced={reduced}
/>
<CtaButton
variant="outline"
size="md"
glyph="calendar"
sublabel="Currently unavailable"
label="Talk to sales"
disabled
reduced={reduced}
/>
</div>
<p
className="mt-8 text-center text-sm text-slate-500 dark:text-slate-400"
aria-live="polite"
>
{loading
? "Setting up your trial workspace…"
: count > 0
? `Trial started ${count} ${count === 1 ? "time" : "times"} — the button really works.`
: "Tap “Start your free trial” to see the loading state."}
</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 →
Shimmer Button
MITA dark pill button with a continuous conic-gradient light shimmer sweeping around its border.

Rainbow Gradient Button
MITA button wrapped in an animated multi-colour gradient border with a matching blurred underglow.

Interactive Hover Button
MITA pill button whose dot expands to fill the surface and reveals an arrow label on hover.

Pulsating Button
MITA solid button that emits a soft, continuously pulsing glow ring to draw the eye to the primary action.

Shiny Button
MITA frosted-glass button with a spring-animated light sweeping across its text and border.

Solid Set Button
OriginalA set of solid buttons: primary, secondary, success and danger, in three sizes.

Outline Set Button
OriginalOutline button variants across colours and sizes with a hover fill.

Ghost Set Button
OriginalGhost and text buttons with subtle hover backgrounds.

Soft Set Button
OriginalSoft, tinted buttons across semantic colours.

Gradient Set Button
OriginalGradient-filled buttons with a hover shift.
Icon Leading Button
OriginalButtons with leading and trailing icons.
Icon Only Button
OriginalSquare and circular icon-only buttons with tooltips and aria-labels.

