Innovations
modals /

Newsletter Popup

Email capture popup that auto-triggers after 2 seconds. Includes a success state and social proof.

Preview

Source

tsx
"use client";

import { useState, useEffect } from "react";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Sparkles, ArrowRight } from "lucide-react";

export default function NewsletterPopup() {
  const [open, setOpen] = useState(false);
  const [email, setEmail] = useState("");
  const [submitted, setSubmitted] = useState(false);

  // Auto-show after 2 seconds (SSR-guarded)
  useEffect(() => {
    const timer = setTimeout(() => setOpen(true), 2000);
    return () => clearTimeout(timer);
  }, []);

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!email) return;
    setSubmitted(true);
  }

  return (
    <div className="flex min-h-[200px] items-center justify-center p-8">
      {/* Demo trigger */}
      <Button variant="outline" onClick={() => setOpen(true)}>
        Preview popup
      </Button>

      <Dialog open={open} onOpenChange={setOpen}>
        <DialogContent className="sm:max-w-md overflow-hidden p-0">
          {/* Top accent bar */}
          <div className="h-1 w-full bg-gradient-to-r from-violet-500 via-purple-500 to-fuchsia-500" />

          <div className="p-6 sm:p-8">
            {submitted ? (
              <div className="py-8 text-center space-y-3">
                <div className="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-green-100 text-green-600 text-2xl">

                </div>
                <h3 className="text-xl font-bold text-foreground">You're in!</h3>
                <p className="text-muted-foreground text-sm">
                  Your free guide is on its way to <strong>{email}</strong>.
                  Check your inbox in the next few minutes.
                </p>
                <Button className="mt-2" onClick={() => setOpen(false)}>
                  Got it
                </Button>
              </div>
            ) : (
              <>
                <DialogHeader className="text-left mb-5">
                  <div className="flex items-center gap-2 mb-3">
                    <div className="rounded-full bg-violet-100 p-2 text-violet-600">
                      <Sparkles className="h-4 w-4" />
                    </div>
                    <span className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
                      Free resource
                    </span>
                  </div>
                  <DialogTitle className="text-2xl font-extrabold leading-tight">
                    Get the free guide
                  </DialogTitle>
                  <DialogDescription className="text-base text-muted-foreground mt-1">
                    The exact playbook we used to grow 14 clients from zero to $100K+.
                    No fluff, just what works.
                  </DialogDescription>
                </DialogHeader>

                <form onSubmit={handleSubmit} className="space-y-3">
                  <Input
                    type="email"
                    placeholder="[email protected]"
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                    required
                    className="h-11"
                  />
                  <Button type="submit" className="w-full h-11 gap-2 text-base font-semibold">
                    Send me the guide
                    <ArrowRight className="h-4 w-4" />
                  </Button>
                </form>

                <p className="mt-4 text-center text-xs text-muted-foreground">
                  Join{" "}
                  <span className="font-semibold text-foreground">1,200+ marketers</span>.
                  No spam. Unsubscribe anytime.
                </p>
              </>
            )}
          </div>
        </DialogContent>
      </Dialog>
    </div>
  );
}
Claude Code Instructions

CLI Install

npx innovations add newsletter-popup

Where to use it

Add this to your root layout so it appears site-wide after 2 seconds. In Next.js root layout (app/layout.tsx): import NewsletterPopup from '@/components/innovations/modals/newsletter-popup'; // Render inside <body> In Astro root layout: <NewsletterPopup client:load /> The auto-show timer is inside useEffect so it only runs in the browser (SSR-safe). Wire the form's onSubmit to your email service (Mailchimp, ConvertKit, Resend, etc.). Consider adding localStorage logic to suppress the popup for users who already subscribed.