Web InnoventixFreeCode

CTA Large Button

Original · free

Large marketing CTA buttons with a sublabel and icon.

byWeb InnoventixReact + Tailwind
btnctalargebuttons
Open

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
btn-cta-large.tsx
"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

motion

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 quote

Similar components

Browse all →