You have an idea. Maybe you are still building it. Maybe it is ready, but you want to build an audience before you launch. Either way, the worst thing you can do is wait until launch day to start collecting emails.
A waitlist page solves that problem. It lets you validate demand, build excitement, and collect real email addresses from genuinely interested people before you write a single line of production code or spend a dollar on ads.
In this guide, you will build a complete, working waitlist page from scratch using plain HTML and CSS, with a form backend powered by Formgrid.dev. Every email collected goes straight to your inbox and your dashboard. No backend. No server. No complex setup.
By the end, you will have a live waitlist page you can deploy on GitHub Pages, Netlify, Cloudflare Pages, or any static host for free.
Why a Waitlist Page Matters Before You Launch
Most founders make the same mistake. They build for months, launch to the public, and discover nobody is waiting. The problem was not the product. It was the absence of a pre-launch audience.
A waitlist page does three things at once:
It validates demand. If people will not give you their email address, they will not give you their money either. A hundred signups before launch is proof that real humans care about what you are building.
It gives you a launch list. When you are ready to go live, you have a warm audience to email. That first wave of users does not come from cold outreach. It comes from people who already said yes.
It creates momentum. Showing "1,200 people on the waitlist" on your landing page is social proof that compounds. Every new visitor sees it and thinks, "maybe I should sign up too."
The good news: you do not need a fancy tool or a monthly subscription to build one. You need HTML, CSS, and a form backend.
What You Will Build
A clean, professional waitlist page with:
- A compelling hero section with your headline and value proposition
- A working email capture form
- A success message after submission
- Spam protection built in
- Email notification to your inbox on every signup
- A submission dashboard to track all your signups
Step 1: Create Your Formgrid Account
Head to https://formgrid.dev and sign up using Google or Email.

No credit card required. The free plan gives you 50 submissions per month, which is more than enough to validate your idea before you launch.
Step 2: Create a New Form
Once logged in, click New Form from your dashboard.

Name your form something descriptive, like:
- "Waitlist Signups"
- "Early Access"
- "Pre-Launch Leads"
Click Create Form.
Step 3: Copy Your Endpoint URL
Go to the Overview tab of your new form and copy your unique endpoint URL.

It looks like this: https://formgrid.dev/api/f/your-form-id
This is the URL your waitlist form will submit data to. Keep it handy for the next step.
Step 4: Build Your Waitlist Page
Create a new file called index.html and paste
this complete waitlist page. Replace
YOUR_FORMGRID_ENDPOINT_URL with the endpoint
you copied in Step 3, and update the headline,
description, and colours to match your product.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width,
initial-scale=1.0" />
<title>Join the Waitlist</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Outfit:wght@600;700;800&family=Plus+Jakarta+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap"
rel="stylesheet"
/>
<style>
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--brand: #1d4f3d;
--brand-light: #e8f3ee;
--text: #111827;
--muted: #6b7280;
--border: #e5e7eb;
--bg: #f9fafb;
--font-sans: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
--font-display: "Outfit", "Plus Jakarta Sans", sans-serif;
}
body {
font-family: var(--font-sans);
background: var(--bg);
color: var(--text);
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* NAV */
.site-nav {
position: sticky;
top: 0;
z-index: 50;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(229, 231, 235, 0.85);
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8) inset,
0 4px 24px -4px rgba(17, 24, 39, 0.06);
}
.site-nav-inner {
max-width: min(1100px, 94vw);
margin: 0 auto;
padding: 1rem clamp(1.25rem, 4vw, 2rem);
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.logo {
font-family: var(--font-sans);
display: inline-flex;
align-items: center;
gap: 0.65rem;
font-size: 1.25rem;
font-weight: 800;
letter-spacing: -0.02em;
color: var(--text);
text-decoration: none;
transition: opacity 0.2s ease;
}
.logo:hover {
opacity: 0.85;
}
.logo-mark {
width: 2.25rem;
height: 2.25rem;
border-radius: 10px;
background: linear-gradient(
145deg,
var(--brand) 0%,
#163d30 100%
);
box-shadow: 0 2px 8px rgba(29, 79, 61, 0.25);
flex-shrink: 0;
position: relative;
}
.logo-mark::after {
content: "";
position: absolute;
inset: 6px;
border-radius: 4px;
border: 2px solid rgba(255, 255, 255, 0.35);
opacity: 0.9;
}
.logo-text {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.1rem;
}
.logo-name {
line-height: 1.15;
color: var(--text);
}
.logo-kicker {
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.nav-cta {
font-size: 0.875rem;
font-weight: 600;
color: var(--brand);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 999px;
border: 1px solid rgba(29, 79, 61, 0.2);
background: var(--brand-light);
transition: background 0.2s ease, border-color 0.2s ease;
white-space: nowrap;
}
.nav-cta:hover {
background: #dceee5;
border-color: rgba(29, 79, 61, 0.35);
}
@media (max-width: 480px) {
.logo-kicker {
display: none;
}
.logo {
font-size: 1.1rem;
}
.logo-mark {
width: 2rem;
height: 2rem;
}
.nav-cta {
font-size: 0.8125rem;
padding: 0.45rem 0.75rem;
}
}
/* HERO — wide column, large type */
.hero {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: clamp(3rem, 8vw, 6rem) clamp(1.25rem, 4vw, 3rem);
max-width: min(1100px, 94vw);
margin: 0 auto;
width: 100%;
}
.badge {
display: inline-block;
background: var(--brand-light);
color: var(--brand);
font-family: var(--font-sans);
font-size: 0.8125rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
padding: 10px 20px;
border-radius: 999px;
margin-bottom: clamp(1.25rem, 3.5vw, 2rem);
}
.hero h1 {
font-family: var(--font-display);
font-size: clamp(3.125rem, 9vw + 0.75rem, 5.75rem);
font-weight: 800;
letter-spacing: -0.035em;
line-height: 1.02;
color: var(--text);
margin-bottom: clamp(1.25rem, 3vw, 2rem);
width: 100%;
}
.hero h1 .hero-accent {
display: inline-block;
font-weight: 800;
color: var(--brand);
}
@supports (-webkit-background-clip: text) or (background-clip: text) {
.hero h1 .hero-accent {
background: linear-gradient(
120deg,
var(--brand) 0%,
#2d7a5c 45%,
#1a5c46 100%
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
}
.subtitle {
font-family: var(--font-sans);
font-size: clamp(1.35rem, 2.8vw, 1.75rem);
font-weight: 500;
line-height: 1.6;
letter-spacing: -0.015em;
color: var(--muted);
max-width: min(48rem, 100%);
margin: 0 auto clamp(2rem, 4vw, 3rem);
}
/* FORM */
#waitlist-form {
scroll-margin-top: 5.5rem;
}
.form-wrap {
width: 100%;
max-width: min(640px, 100%);
margin: 0 auto;
}
.form-row {
display: flex;
gap: 10px;
}
.form-row input[type="email"] {
flex: 1;
padding: 16px 20px;
font-size: 1.0625rem;
border: 1px solid var(--border);
border-radius: 12px;
outline: none;
background: #fff;
transition: border-color 0.2s;
}
.form-row input[type="email"]:focus {
border-color: var(--brand);
}
.form-row button {
padding: 16px 28px;
background: var(--brand);
color: #fff;
font-size: 1.0625rem;
font-weight: 700;
border: none;
border-radius: 12px;
cursor: pointer;
white-space: nowrap;
transition: opacity 0.2s;
}
.form-row button:hover {
opacity: 0.9;
}
.form-row button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.form-hint {
font-size: 0.82rem;
color: var(--muted);
margin-top: 10px;
text-align: center;
}
/* SUCCESS STATE */
.success {
display: none;
background: var(--brand-light);
border: 1px solid #cde3d8;
border-radius: 14px;
padding: 20px 24px;
text-align: center;
}
.success h3 {
color: var(--brand);
margin-bottom: 6px;
}
.success p {
color: var(--muted);
font-size: 0.95rem;
}
/* SOCIAL PROOF */
.proof {
margin-top: clamp(2rem, 5vw, 3rem);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: clamp(0.95rem, 1.5vw, 1.05rem);
color: var(--muted);
max-width: min(52rem, 100%);
margin-left: auto;
margin-right: auto;
}
.proof-dot {
width: 8px;
height: 8px;
background: var(--brand);
border-radius: 50%;
}
/* FEATURES */
.features {
padding: 64px 24px;
max-width: 960px;
margin: 0 auto;
width: 100%;
}
.features h2 {
font-family: var(--font-display);
text-align: center;
font-size: clamp(1.5rem, 3vw, 1.85rem);
font-weight: 800;
letter-spacing: -0.03em;
margin-bottom: 40px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(
auto-fit, minmax(240px, 1fr)
);
gap: 24px;
}
.feature-card {
background: #fff;
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
}
.feature-icon {
width: 40px;
height: 40px;
background: var(--brand-light);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16px;
}
.feature-icon svg {
width: 20px;
height: 20px;
stroke: var(--brand);
fill: none;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.feature-card h3 {
font-size: 1rem;
font-weight: 700;
margin-bottom: 8px;
}
.feature-card p {
font-size: 0.9rem;
color: var(--muted);
}
/* FOOTER */
footer {
text-align: center;
padding: 24px;
font-size: 0.85rem;
color: var(--muted);
border-top: 1px solid var(--border);
}
@media (max-width: 560px) {
.form-row {
flex-direction: column;
}
}
</style>
</head>
<body>
<header class="site-nav" role="banner">
<div class="site-nav-inner">
<a href="#" class="logo" aria-label="YourProduct home">
<span class="logo-mark" aria-hidden="true"></span>
<span class="logo-text">
<span class="logo-name">YourProduct</span>
<span class="logo-kicker">Waitlist</span>
</span>
</a>
<a href="#waitlist-form" class="nav-cta">Join waitlist</a>
</div>
</header>
<section class="hero">
<span class="badge">Coming Soon</span>
<h1>
The smarter way to<br />
<span class="hero-accent">do the thing you do</span>
</h1>
<p class="subtitle">
We are building something that solves
your problem better than anything else
out there. Join the waitlist and be
the first to know when we launch.
</p>
<div class="form-wrap">
<form id="waitlist-form">
<input
type="hidden"
name="_subject"
value="New Waitlist Signup"
/>
<!-- Honeypot spam protection -->
<input
type="text"
name="_honey"
style="display:none"
tabindex="-1"
autocomplete="off"
/>
<div class="form-row">
<input
type="email"
name="email"
placeholder="Enter your email address"
required
/>
<button type="submit" id="submit-btn">
Join Waitlist
</button>
</div>
<p class="form-hint">
No spam. Unsubscribe anytime.
</p>
</form>
<div class="success" id="success-msg">
<h3>You are on the list!</h3>
<p>
We will email you the moment we launch.
Thank you for your interest.
</p>
</div>
</div>
<div class="proof">
<span class="proof-dot"></span>
<span>Join 500+ people already on the waitlist</span>
</div>
</section>
<section class="features">
<h2>Why people are excited</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<svg viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</div>
<h3>Benefit One</h3>
<p>
Describe the first key benefit of your
product in one or two sentences.
Be specific and outcome focused.
</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14">
</polyline>
</svg>
</div>
<h3>Benefit Two</h3>
<p>
Describe the second key benefit.
What problem does this solve?
How does it save time or money?
</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg viewBox="0 0 24 24">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z">
</path>
</svg>
</div>
<h3>Benefit Three</h3>
<p>
Describe the third benefit.
What makes your approach different
from everything else out there?
</p>
</div>
</div>
</section>
<footer>
Built with HTML and
<a href="https://formgrid.dev"
style="color: var(--brand);">
Formgrid
</a>
</footer>
<script>
const form = document.getElementById(
'waitlist-form'
);
const btn = document.getElementById(
'submit-btn'
);
const successMsg = document.getElementById(
'success-msg'
);
form.addEventListener('submit', async (e) => {
e.preventDefault();
btn.disabled = true;
btn.textContent = 'Joining...';
const data = new FormData(form);
try {
const res = await fetch(
'YOUR_FORMGRID_ENDPOINT_URL',
{ method: 'POST', body: data }
);
if (res.ok) {
form.style.display = 'none';
successMsg.style.display = 'block';
} else {
btn.disabled = false;
btn.textContent = 'Join Waitlist';
alert(
'Something went wrong. Please try again.'
);
}
} catch (err) {
btn.disabled = false;
btn.textContent = 'Join Waitlist';
alert(
'Network error. Please check your connection.'
);
}
});
</script>
</body>
</html>
Replace these three things before you deploy:
YourProduct with your actual product name
The headline and subtitle with your real value proposition
YOUR_FORMGRID_ENDPOINT_URL with the endpoint URL from Step 3
Step 5: Email Notifications Are Already Set Up
Good news: you do not need to configure anything. Formgrid automatically sends every new signup to the email address you used to create your account.
The moment someone joins your waitlist, you will receive an email like this:

If you want signups sent to a different address, go to your form's Settings tab, find Email Notifications, and update it there.
Step 6: Deploy Your Waitlist Page for Free
You have several free options for hosting your waitlist page. All of them serve static HTML with no setup required.
GitHub Pages: Push your index.html to a
GitHub repo and enable Pages in the repository
settings. Your page goes live at
username.github.io/repo-name.
Netlify: Drag and drop your index.html file
onto netlify.com/drop
and it goes live instantly at a random Netlify URL.
Connect a custom domain in settings.
Cloudflare Pages: Connect your GitHub repo and Cloudflare deploys on every push. Fast, free, and global CDN included.
All three options are completely free for a static HTML page. Pick whichever you are most comfortable with.
Step 7: View and Manage Your Signups
Every signup is stored automatically in your Formgrid dashboard. Go to the Submissions tab to see everyone who has joined your waitlist.

You can search, filter, and export all your signups as a CSV file at any time. When you are ready to launch, export the list, and import it into your email tool of choice to send your launch announcement.
What Makes a Waitlist Page Convert
Based on the research behind this guide, here are the elements that separate high-converting waitlist pages from ones that collect zero signups:
A specific headline. "The future of productivity." converts nobody. "The form backend for developers who hate maintaining servers" converts the right people. Be specific about who it is for and what it does.
One clear action. Your only CTA should be the email field. No navigation links, no secondary buttons, no distractions. One page. One goal.
Social proof near the form. "Join 500 people already on the waitlist" creates FOMO and builds trust. Even if you are starting from zero, update this number as you grow.
A reason to sign up now. "Early access" or "be first to know" gives people a reason to act today instead of coming back later and forgetting.
Keep the form short. Email only for first contact. You can ask for more information in a follow-up survey after signing up. Every extra field reduces conversion.
Customising the Page for Your Brand
The template above uses a green brand colour
defined as --brand: #1d4f3d in the CSS variables
at the top. To match your own brand, just change
that one value:
:root {
--brand: #your-colour-here;
--brand-light: #lighter-version-here;
}Everything else on the page inherits those two values automatically. No need to hunt through the CSS changing colours one by one.
Final Thoughts
A waitlist page is not a nice-to-have. It is the difference between launching to silence and launching to a warm, engaged audience.
The page in this guide takes less than 30 minutes to set up. It costs nothing to host. And every signup goes straight to your inbox and your dashboard the moment it happens.
Stop waiting to start collecting. Build your waitlist today.
With Formgrid.dev you get:
✅ Working form with email capture in minutes
✅ Instant email notification on every signup
✅ Spam protection built in
✅ Submission dashboard to track all signups
✅ CSV export when you are ready to launch
✅ Free to start, no credit card required
