Skip to content
Template Empire
Browse components

Waitlist Form CTA

A free animated waitlist CTA section with a real, resilient email form.

Private beta

Be first in the room

Join a small launch group shaping a calmer way to run product work.

One product update a month. Unsubscribe whenever you like.

Customise

Code

<CtaWaitlistForm
  eyebrow="Private beta"
  title="Be first in the room"
  description="Join a small launch group shaping a calmer way to run product work."
  inputLabel="Work email"
  buttonLabel="Request early access"
  privacyNote="One product update a month. Unsubscribe whenever you like."
  formAction="#waitlist-demo"
  status={status}
  onSubmit={handleSubmit}
/>

Waitlist Form CTA is a free CTA section for Next.js and React, styled with Tailwind and animated with Motion; it pairs launch copy with a real email form whose idle, pending, success and error states stay inside one reserved-size capsule.

The parent controls request logic and status, while formAction, method and the named email field preserve native submission when JavaScript is unavailable.

Labels, validation, live announcements and immediate success focus are built in, while reduced motion removes travel, rotation and layout springs without changing form behaviour.

Install

Add the @te registry to your components.json once:

{
  "registries": {
    "@te": "https://templateempire.io/r/{name}.json"
  }
}

Then install the component:

npx shadcn@latest add @te/cta-waitlist-form

Usage

"use client";

import { useState, type FormEvent } from "react";
import {
  CtaWaitlistForm,
  type CtaWaitlistStatus,
} from "@/components/cta-waitlist-form";

export function Example() {
  const [status, setStatus] = useState<CtaWaitlistStatus>("idle");

  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (status === "pending") return;

    const form = event.currentTarget;
    setStatus("pending");

    try {
      const response = await fetch(form.action, {
        method: form.method,
        body: new FormData(form),
      });
      if (!response.ok) throw new Error("Waitlist request failed");
      setStatus("success");
    } catch {
      setStatus("error");
    }
  }

  return (
    <CtaWaitlistForm
      eyebrow="Private beta"
      title="Be first in the room"
      description="Join a small launch group shaping a calmer way to run product work."
      inputLabel="Work email"
      buttonLabel="Request early access"
      privacyNote="One product update a month. Unsubscribe whenever you like."
      formAction="/api/waitlist"
      status={status}
      onSubmit={handleSubmit}
    />
  );
}

Props

PropTypeDefaultDescription
eyebrowstringundefinedOptional label rendered above the heading.
titlestringrequiredMain section heading, rendered as an h2.
descriptionstringundefinedOptional supporting paragraph beneath the heading.
inputLabelstring"Email address"Visible label that provides the email field's accessible name.
placeholderstring"you@example.com"Supplementary placeholder for the email field.
buttonLabelstring"Join the waitlist"Submit label used in idle and error states.
pendingLabelstring"Joining…"Visual and announced submit label used while pending.
successMessagestring"You’re on the list. Check your inbox to confirm."Confirmation content shown and announced after success.
errorMessagestring"Something went wrong. Please try again."Alert text shown and associated with the email field in the error state.
privacyNoteReactNodeundefinedOptional in-flow note below the capsule, including an optional anchor.
formActionstringrequiredNative form endpoint used with or without JavaScript enhancement.
method"get" | "post""post"Native form submission method.
emailNamestring"email"Name used for the submitted email field.
status"idle" | "pending" | "success" | "error""idle"Parent-controlled request state; the component performs no fetch.
onSubmitFormEventHandler<HTMLFormElement>undefinedOptional enhanced handler for parent-owned validation and request logic.
layout"split" | "stacked""split"Balanced columns at large viewports or one centred vertical composition.
formWidth"compact" | "wide""compact"Maximum width of the form capsule.
classNamestringundefinedAdditional classes merged onto the section root.

Customisation

Waitlist Form CTA exposes four Tier 3 tokens. --cta-waitlist-signal colours the focus line, submit button and confirmation mark, --cta-waitlist-signal-text colours the eyebrow, --cta-waitlist-panel sets the capsule surface, and --cta-waitlist-rule sets its structural borders. Layout and form width are real component props, while wording remains regular content props:

<CtaWaitlistForm
  title="Join the studio list"
  formAction="/api/waitlist"
  layout="stacked"
  formWidth="wide"
  className="[--cta-waitlist-signal:var(--ring)] [--cta-waitlist-signal-text:var(--primary-accent)] [--cta-waitlist-panel:var(--surface)] [--cta-waitlist-rule:var(--border)]"
/>

Frequently asked questions

Is Waitlist Form CTA free for commercial use?
Yes. Waitlist Form CTA is MIT licensed, so you can use it in personal and commercial projects without attribution.
How does the form handle accessibility and reduced motion?
The email field has a real label and native email validation, pending and success use polite status announcements, errors are alerts associated with the field, and focus moves to confirmation only after a real success transition. Reduced motion removes entrance travel, spinner rotation and layout springs while every state remains immediately usable.
How do I connect formAction and the controlled request state?
Point formAction and method at an endpoint for native submission, then optionally prevent default in onSubmit to enhance it with your own request. The parent sets status to pending, success or error from that request; the component never fetches or invents a result.

Related components

Want the full page this belongs to?Template Empire builds complete Next.js templates with conversion sections, resilient forms and polished motion systems already composed.Explore Template Empire templates