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
| Prop | Type | Default | Description |
|---|---|---|---|
headline | string | Required | Main heading text |
description | string | undefined | Supporting description |
features | string[] | undefined | List of features or benefits |
placeholder | string | "Enter your email" | Input placeholder text |
buttonText | string | "Join Waitlist" | Submit button text |
showCounter | boolean | true | Show waitlist counter |
initialCount | number | 1247 | Initial waitlist count |
onSubmit | (email: string) => void | undefined | Form 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