Docs
Waitlist Form

Waitlist Form

A pre-launch email collection component with counter and success states. Perfect for building anticipation before your product launch.

Launching Soon

Be the first to know

Join our waitlist and get early access to our revolutionary new product. Limited spots available.

No spam. Unsubscribe anytime.

1,247 people already joined
Early access before public launch
Exclusive founding member benefits
Priority customer support

Features

  • Live counter - Shows number of signups in real-time
  • Success animation - Celebratory confirmation state
  • Loading states - Visual feedback during submission
  • Pulsing badge - "Launching Soon" with animated indicator
  • Features list - Optional benefits with checkmarks
  • Position display - Shows user's place in line
  • Email validation - Built-in form validation
  • Vibrant design - Indigo gradient background
  • Responsive - Mobile-friendly layout
  • TypeScript support - Full type safety

Installation

Copy and paste the following code into your project.

"use client";
 
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Check, Loader2, Users } from "lucide-react";
import { useState } from "react";
 
interface WaitlistFormProps {
  headline: string;
  description?: string;
  features?: string[];
  placeholder?: string;
  buttonText?: string;
  showCounter?: boolean;
  initialCount?: number;
  onSubmit?: (email: string) => Promise<void> | void;
}
 
export function WaitlistForm({
  headline,
  description,
  features,
  placeholder = "Enter your email",
  buttonText = "Join Waitlist",
  showCounter = true,
  initialCount = 1247,
  onSubmit,
}: WaitlistFormProps) {
  const [email, setEmail] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [isSuccess, setIsSuccess] = useState(false);
  const [waitlistCount, setWaitlistCount] = useState(initialCount);
 
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (email && onSubmit) {
      setIsLoading(true);
      try {
        await onSubmit(email);
        setIsSuccess(true);
        setWaitlistCount((prev) => prev + 1);
        setEmail("");
      } catch (error) {
        console.error("Waitlist signup error:", error);
      } finally {
        setIsLoading(false);
      }
    }
  };
 
  return (
    <section className="relative overflow-hidden bg-indigo-600 rounded py-20 sm:py-24">
      <div className="absolute inset-0 bg-[linear-gradient(to_right,#ffffff15_1px,transparent_1px),linear-gradient(to_bottom,#ffffff15_1px,transparent_1px)] bg-[size:32px_32px]" />
      <div className="absolute right-0 top-0 h-96 w-96 rounded-full bg-indigo-500/30 blur-3xl" />
      <div className="absolute bottom-0 left-0 h-96 w-96 rounded-full bg-indigo-700/30 blur-3xl" />
 
      <div className="container relative mx-auto px-6">
        <div className="mx-auto max-w-3xl text-center">
          <div className="mb-6 inline-flex items-center gap-2 rounded-full bg-white/20 px-4 py-1.5 backdrop-blur-sm">
            <div className="relative flex h-2 w-2">
              <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-yellow-300 opacity-75"></span>
              <span className="relative inline-flex h-2 w-2 rounded-full bg-yellow-400"></span>
            </div>
            <span className="text-sm font-semibold text-white">
              Launching Soon
            </span>
          </div>
 
          <h2 className="mb-4 text-4xl font-bold tracking-tight text-white sm:text-5xl lg:text-6xl">
            {headline}
          </h2>
 
          {description && (
            <p className="mb-10 text-lg text-white/90 sm:text-xl">
              {description}
            </p>
          )}
 
          {isSuccess ? (
            <div className="animate-in fade-in slide-in-from-bottom-4 mx-auto max-w-md rounded-3xl bg-white p-8 text-center shadow-2xl duration-500">
              <div className="mx-auto mb-5 flex h-20 w-20 items-center justify-center rounded-full bg-green-500 shadow-lg">
                <Check className="h-10 w-10 text-white" strokeWidth={3} />
              </div>
              <h3 className="mb-3 text-2xl font-bold text-gray-900">
                You're on the list!
              </h3>
              <p className="mb-6 text-gray-600">
                We'll send you an email when we launch.
              </p>
              {showCounter && (
                <div className="rounded-xl border border-indigo-100 bg-indigo-50 p-5">
                  <div className="mb-1 text-xs font-semibold uppercase tracking-wider text-indigo-600">
                    Your Position
                  </div>
                  <div className="text-4xl font-bold text-indigo-600">
                    #{waitlistCount.toLocaleString()}
                  </div>
                </div>
              )}
            </div>
          ) : (
            <div className="mx-auto max-w-md space-y-6">
              <div className="rounded-3xl bg-white p-8 shadow-2xl">
                <form onSubmit={handleSubmit} className="space-y-4">
                  <Input
                    type="email"
                    placeholder={placeholder}
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                    required
                    disabled={isLoading}
                    className="h-14 rounded-xl border-2 border-gray-200 text-base text-gray-900 placeholder:text-gray-500 focus:border-indigo-500"
                  />
                  <Button
                    type="submit"
                    size="lg"
                    disabled={isLoading}
                    className="h-14 w-full rounded-xl bg-indigo-600 text-base font-semibold text-white shadow-lg transition-all hover:bg-indigo-700 hover:shadow-xl"
                  >
                    {isLoading ? (
                      <>
                        <Loader2 className="mr-2 h-5 w-5 animate-spin" />
                        Joining...
                      </>
                    ) : (
                      buttonText
                    )}
                  </Button>
                </form>
 
                <p className="mt-4 text-xs text-gray-500">
                  No spam. Unsubscribe anytime.
                </p>
              </div>
 
              {showCounter && (
                <div className="flex items-center justify-center gap-2 text-white/90">
                  <Users className="h-5 w-5" />
                  <span className="text-sm">
                    <strong className="font-bold">
                      {waitlistCount.toLocaleString()}
                    </strong>{" "}
                    people already joined
                  </span>
                </div>
              )}
 
              {features && features.length > 0 && (
                <div className="space-y-3 pt-2">
                  {features.map((feature, index) => (
                    <div
                      key={index}
                      className="flex items-center justify-center gap-3 text-white/90"
                    >
                      <div className="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-white/20">
                        <Check className="h-4 w-4 text-white" strokeWidth={3} />
                      </div>
                      <span className="text-sm font-medium">{feature}</span>
                    </div>
                  ))}
                </div>
              )}
            </div>
          )}
        </div>
      </div>
    </section>
  );
}

Update the import paths to match your project setup.

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

Usage

Basic Usage

import WaitlistForm from "@/components/ui/waitlist-form";
 
export default function Page() {
    return (
        <WaitlistForm
            headline="Be the first to know"
            description="Join our waitlist and get early access."
            onSubmit={(email) => console.log("Signed up:", email)}
        />
    );
}

With Features List

<WaitlistForm
    headline="Be the first to know"
    description="Join our waitlist and get early access."
    features={[
        "Early access before public launch",
        "Exclusive founding member benefits",
        "Priority customer support",
    ]}
    onSubmit={(email) => console.log(email)}
/>

Without Counter

<WaitlistForm
    headline="Coming Soon"
    description="Sign up to be notified when we launch."
    showCounter={false}
    onSubmit={(email) => console.log(email)}
/>

Custom Counter

<WaitlistForm
    headline="Join the Revolution"
    description="Be part of something amazing."
    initialCount={5000}
    onSubmit={(email) => console.log(email)}
/>

Minimal Version

<WaitlistForm
    headline="Get Early Access"
    onSubmit={(email) => console.log(email)}
/>

Custom Text

<WaitlistForm
    headline="Reserve Your Spot"
    description="Limited spots available."
    placeholder="your@email.com"
    buttonText="Reserve Now"
    onSubmit={(email) => console.log(email)}
/>

With API Integration

"use client";
 
import { useState } from "react";
import WaitlistForm from "@/components/ui/waitlist-form";
 
export default function Page() {
    const handleSubmit = async (email: string) => {
        try {
            const response = await fetch("/api/waitlist", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ email }),
            });
 
            if (response.ok) {
                alert("You're on the list!");
            }
        } catch (error) {
            console.error("Signup failed:", error);
        }
    };
 
    return (
        <WaitlistForm
            headline="Be the first to know"
            onSubmit={handleSubmit}
        />
    );
}

Props

PropTypeDefaultDescription
headlinestringRequiredMain heading text
descriptionstringundefinedSupporting description
featuresstring[]undefinedList of features or benefits
placeholderstring"Enter your email"Input placeholder text
buttonTextstring"Join Waitlist"Submit button text
showCounterbooleantrueShow waitlist counter
initialCountnumber1247Initial waitlist count
onSubmit(email: string) => voidundefinedForm submission handler

TypeScript Interface

interface WaitlistFormProps {
    headline: string;
    description?: string;
    features?: string[];
    placeholder?: string;
    buttonText?: string;
    showCounter?: boolean;
    initialCount?: number;
    onSubmit?: (email: string) => Promise<void> | void;
}

Use Cases

Perfect for:

  • Product launches
  • Beta program signups
  • Early access campaigns
  • Pre-order collections
  • Event registrations
  • Feature announcements
  • App launches
  • Service previews