Web InnoventixFreeCode

Social Proof CTA

Original · free

CTA with avatars and rating

byWeb InnoventixReact + Tailwind
ctaxsocialproofcta
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/ctax-social-proof.json
ctax-social-proof.tsx
"use client";

import { useId, useState, type ChangeEvent, type FormEvent } from "react";
import { motion, useReducedMotion, type Variants } from "motion/react";

type Avatar = { initials: string; grad: string };

const AVATARS: Avatar[] = [
  { initials: "AR", grad: "from-indigo-400 to-violet-500" },
  { initials: "MK", grad: "from-emerald-400 to-sky-500" },
  { initials: "JD", grad: "from-rose-400 to-amber-500" },
  { initials: "SL", grad: "from-sky-400 to-indigo-500" },
  { initials: "TN", grad: "from-violet-400 to-rose-500" },
];

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function Star({ fill }: { fill: number }) {
  const clamped = Math.max(0, Math.min(1, fill));
  return (
    <span className="relative inline-block h-4 w-4">
      <svg viewBox="0 0 20 20" aria-hidden="true" className="absolute inset-0 h-4 w-4 text-slate-300 dark:text-slate-600">
        <path
          fill="currentColor"
          d="M10 1.6l2.47 5 5.53.8-4 3.9.94 5.5L10 14.2l-4.94 2.6.94-5.5-4-3.9 5.53-.8z"
        />
      </svg>
      <span className="absolute inset-0 overflow-hidden" style={{ width: `${clamped * 100}%` }}>
        <svg viewBox="0 0 20 20" aria-hidden="true" className="h-4 w-4 text-amber-400">
          <path
            fill="currentColor"
            d="M10 1.6l2.47 5 5.53.8-4 3.9.94 5.5L10 14.2l-4.94 2.6.94-5.5-4-3.9 5.53-.8z"
          />
        </svg>
      </span>
    </span>
  );
}

export default function CtaxSocialProof() {
  const reduce = useReducedMotion();
  const inputId = useId();
  const msgId = useId();

  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<"idle" | "error" | "success">("idle");

  const rating = 4.9;
  const stars = Array.from({ length: 5 }, (_, i) => Math.max(0, Math.min(1, rating - i)));

  function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    if (!EMAIL_RE.test(email.trim())) {
      setStatus("error");
      return;
    }
    setStatus("success");
  }

  function handleChange(e: ChangeEvent<HTMLInputElement>) {
    setEmail(e.target.value);
    if (status === "error") setStatus("idle");
  }

  const containerV: Variants = {
    hidden: {},
    show: { transition: { staggerChildren: reduce ? 0 : 0.09, delayChildren: reduce ? 0 : 0.05 } },
  };
  const itemV: Variants = reduce
    ? { hidden: { opacity: 1, y: 0 }, show: { opacity: 1, y: 0 } }
    : {
        hidden: { opacity: 0, y: 18 },
        show: { opacity: 1, y: 0, transition: { duration: 0.55, ease: "easeOut" } },
      };

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-4 py-24 text-slate-900 sm:py-32 dark:bg-slate-950 dark:text-slate-100">
      <style>{`
        @keyframes ctaxsp-float {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
          50% { transform: translate3d(0, -22px, 0) scale(1.06); }
        }
        @keyframes ctaxsp-drift {
          0%, 100% { transform: translate3d(0, 0, 0); }
          50% { transform: translate3d(18px, 14px, 0); }
        }
        .ctaxsp-blob-a { animation: ctaxsp-float 13s ease-in-out infinite; }
        .ctaxsp-blob-b { animation: ctaxsp-drift 17s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .ctaxsp-blob-a, .ctaxsp-blob-b { animation: none !important; }
        }
      `}</style>

      {/* Decorative atmosphere */}
      <div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
        <div className="ctaxsp-blob-a absolute -left-24 top-4 h-72 w-72 rounded-full bg-indigo-400/25 blur-3xl dark:bg-indigo-500/20" />
        <div className="ctaxsp-blob-b absolute -right-16 bottom-0 h-80 w-80 rounded-full bg-violet-400/25 blur-3xl dark:bg-violet-600/20" />
        <div className="absolute inset-0 bg-[radial-gradient(60%_60%_at_50%_0%,rgba(99,102,241,0.10),transparent)]" />
      </div>

      <motion.div
        variants={containerV}
        initial="hidden"
        whileInView="show"
        viewport={{ once: true, amount: 0.3 }}
        className="relative mx-auto max-w-3xl"
      >
        <div className="rounded-[2rem] border border-slate-200/80 bg-white/70 p-8 shadow-[0_24px_70px_-30px_rgba(30,41,59,0.35)] backdrop-blur-xl sm:p-12 dark:border-slate-800 dark:bg-slate-900/60">
          {/* Eyebrow */}
          <motion.div variants={itemV} className="flex justify-center">
            <span className="inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-indigo-50 px-3.5 py-1.5 text-xs font-semibold tracking-wide text-indigo-700 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300">
              <span className="relative flex h-2 w-2">
                <span className="absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-60 motion-safe:animate-ping" />
                <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
              </span>
              Trusted by 12,000+ teams
            </span>
          </motion.div>

          {/* Headline */}
          <motion.h2
            variants={itemV}
            className="mt-6 text-center text-3xl font-bold tracking-tight text-balance sm:text-5xl"
          >
            Ship work you're proud of,{" "}
            <span className="bg-gradient-to-r from-indigo-500 via-violet-500 to-sky-500 bg-clip-text text-transparent">
              together
            </span>
          </motion.h2>

          {/* Subhead */}
          <motion.p
            variants={itemV}
            className="mx-auto mt-4 max-w-xl text-center text-base leading-relaxed text-slate-600 text-pretty sm:text-lg dark:text-slate-400"
          >
            Cascade pulls your docs, tasks, and timelines into one calm workspace — so your team
            spends less time chasing status updates and more time doing the work that matters.
          </motion.p>

          {/* Avatars + rating */}
          <motion.div
            variants={itemV}
            className="mt-8 flex flex-col items-center justify-center gap-4 sm:flex-row sm:gap-5"
          >
            <div className="flex -space-x-3">
              {AVATARS.map((a) => (
                <span
                  key={a.initials}
                  className={`inline-flex h-10 w-10 items-center justify-center rounded-full bg-gradient-to-br ${a.grad} text-xs font-bold text-white ring-2 ring-white dark:ring-slate-900`}
                >
                  {a.initials}
                </span>
              ))}
              <span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-slate-200 text-[10px] font-bold text-slate-700 ring-2 ring-white dark:bg-slate-700 dark:text-slate-100 dark:ring-slate-900">
                +12K
              </span>
            </div>

            <div className="hidden h-8 w-px bg-slate-200 sm:block dark:bg-slate-700" />

            <div className="flex flex-col items-center sm:items-start">
              <div className="flex items-center gap-1.5">
                <span className="flex" role="img" aria-label={`Rated ${rating} out of 5`}>
                  {stars.map((f, i) => (
                    <Star key={i} fill={f} />
                  ))}
                </span>
                <span className="text-sm font-bold">{rating}</span>
              </div>
              <span className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">
                from 2,147 verified reviews
              </span>
            </div>
          </motion.div>

          {/* CTA form / success */}
          <motion.div variants={itemV} className="mt-9">
            {status === "success" ? (
              <div
                id={msgId}
                role="status"
                aria-live="polite"
                className="mx-auto flex max-w-md items-start gap-3 rounded-2xl border border-emerald-200 bg-emerald-50 p-4 text-left dark:border-emerald-500/30 dark:bg-emerald-500/10"
              >
                <svg viewBox="0 0 20 20" aria-hidden="true" className="mt-0.5 h-5 w-5 shrink-0 text-emerald-500">
                  <path
                    fill="currentColor"
                    d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.7-9.3a1 1 0 00-1.4-1.4L9 10.6 7.7 9.3a1 1 0 10-1.4 1.4l2 2a1 1 0 001.4 0z"
                  />
                </svg>
                <div>
                  <p className="text-sm font-semibold text-emerald-800 dark:text-emerald-200">
                    You're on the list.
                  </p>
                  <p className="mt-0.5 text-sm text-emerald-700/90 dark:text-emerald-300/80">
                    We sent a workspace invite to <span className="font-medium">{email.trim()}</span>.
                    Check your inbox to finish setting up.
                  </p>
                  <button
                    type="button"
                    onClick={() => {
                      setEmail("");
                      setStatus("idle");
                    }}
                    className="mt-2 rounded-md text-sm font-semibold text-emerald-700 underline decoration-emerald-400 underline-offset-2 hover:text-emerald-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 dark:text-emerald-300 dark:hover:text-emerald-100"
                  >
                    Use a different email
                  </button>
                </div>
              </div>
            ) : (
              <form onSubmit={handleSubmit} noValidate className="mx-auto max-w-md">
                <label htmlFor={inputId} className="sr-only">
                  Work email address
                </label>
                <div className="flex flex-col gap-3 sm:flex-row">
                  <input
                    id={inputId}
                    type="email"
                    inputMode="email"
                    autoComplete="email"
                    value={email}
                    onChange={handleChange}
                    placeholder="you@company.com"
                    aria-invalid={status === "error"}
                    aria-describedby={status === "error" ? msgId : undefined}
                    className="w-full flex-1 rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 placeholder:text-slate-400 focus-visible:border-indigo-500 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-indigo-500/20 dark:border-slate-700 dark:bg-slate-950/60 dark:text-slate-100 dark:placeholder:text-slate-500"
                  />
                  <button
                    type="submit"
                    className="inline-flex items-center justify-center gap-2 rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 px-6 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-500/25 transition-transform duration-150 hover:-translate-y-0.5 hover:shadow-xl hover:shadow-indigo-500/30 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-indigo-500/40 active:translate-y-0"
                  >
                    Start free trial
                    <svg viewBox="0 0 20 20" aria-hidden="true" className="h-4 w-4">
                      <path
                        fill="currentColor"
                        d="M10.5 3.3a1 1 0 00-1.4 1.4L12.6 8H4a1 1 0 100 2h8.6l-3.5 3.3a1 1 0 101.4 1.4l5-5a1 1 0 000-1.4z"
                      />
                    </svg>
                  </button>
                </div>
                <p
                  id={msgId}
                  role={status === "error" ? "alert" : undefined}
                  aria-live="polite"
                  className={`mt-2 min-h-[1.25rem] text-center text-xs sm:text-left ${
                    status === "error"
                      ? "text-rose-600 dark:text-rose-400"
                      : "text-slate-500 dark:text-slate-400"
                  }`}
                >
                  {status === "error"
                    ? "Please enter a valid email address."
                    : "14-day free trial · No credit card required · Cancel anytime."}
                </p>
              </form>
            )}
          </motion.div>

          {/* Micro social-proof footer */}
          <motion.div
            variants={itemV}
            className="mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-xs font-medium text-slate-500 dark:text-slate-400"
          >
            <span className="inline-flex items-center gap-1.5">
              <svg viewBox="0 0 20 20" aria-hidden="true" className="h-4 w-4 text-emerald-500">
                <path
                  fill="currentColor"
                  d="M10 1l7 3v5c0 4.4-3 8.3-7 10-4-1.7-7-5.6-7-10V4z"
                  opacity="0.25"
                />
                <path
                  fill="currentColor"
                  d="M8.9 12.2L6.7 10a1 1 0 011.4-1.4l1.2 1.2 3-3.3a1 1 0 011.5 1.3l-3.7 4a1 1 0 01-1.4.1z"
                />
              </svg>
              SOC 2 Type II certified
            </span>
            <span className="inline-flex items-center gap-1.5">
              <svg viewBox="0 0 20 20" aria-hidden="true" className="h-4 w-4 text-indigo-500">
                <path
                  fill="currentColor"
                  d="M10 2a4 4 0 014 4v2h1a1 1 0 011 1v7a1 1 0 01-1 1H5a1 1 0 01-1-1V9a1 1 0 011-1h1V6a4 4 0 014-4zm2 6V6a2 2 0 10-4 0v2z"
                />
              </svg>
              99.98% uptime SLA
            </span>
            <span className="inline-flex items-center gap-1.5">
              <svg viewBox="0 0 20 20" aria-hidden="true" className="h-4 w-4 text-amber-500">
                <path
                  fill="currentColor"
                  d="M10 1.6l2.47 5 5.53.8-4 3.9.94 5.5L10 14.2l-4.94 2.6.94-5.5-4-3.9 5.53-.8z"
                />
              </svg>
              Rated #1 in team workflow
            </span>
          </motion.div>
        </div>
      </motion.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 →