Back in Stock Notify Me Form — Free Email Capture Widget (HTML/CSS/JS)
Back in Stock Notify Me Form · Forms · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="bis-card">
<div class="bis-product">
<div class="bis-swatch">🔊</div>
<div>
<h3>Aria Wireless Headphones</h3>
<span class="bis-oos-tag">Out of stock</span>
</div>
</div>
<div class="bis-form" id="bisForm">
<p class="bis-copy">Get notified the moment this is back in stock.</p>
<form id="bisNotifyForm" novalidate>
<div class="bis-field">
<input type="email" id="bisEmail" placeholder="[email protected]" autocomplete="email" />
<button type="submit">Notify me</button>
</div>
<p class="bis-error" id="bisError" hidden>Enter a valid email address.</p>
</form>
</div>
<div class="bis-success" id="bisSuccess" hidden>
<div class="bis-success-icon">✓</div>
<p class="bis-success-title">You're on the list</p>
<p class="bis-success-copy">We'll email <b id="bisSuccessEmail"></b> the moment it's back.</p>
<button type="button" class="bis-edit-link" id="bisEdit">Wrong email? Edit</button>
</div>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0b0f14;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.bis-card{background:#111720;border:1px solid #1f2733;border-radius:18px;padding:24px;width:100%;max-width:380px;box-shadow:0 20px 50px rgba(0,0,0,.45)}
.bis-product{display:flex;align-items:center;gap:14px;margin-bottom:20px;padding-bottom:18px;border-bottom:1px solid #1f2733}
.bis-swatch{width:52px;height:52px;flex-shrink:0;border-radius:12px;background:#1a2230;display:flex;align-items:center;justify-content:center;font-size:22px;filter:grayscale(1) opacity(.7)}
.bis-product h3{font-size:14.5px;font-weight:700;color:#e9edf5}
.bis-oos-tag{display:inline-block;margin-top:5px;font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;padding:3px 8px;border-radius:999px;background:rgba(248,113,113,.14);color:#f87171}
.bis-copy{font-size:12.5px;color:#8993a8;margin-bottom:14px;line-height:1.5}
.bis-field{display:flex;gap:8px}
.bis-field input{flex:1;min-width:0;background:#0c1118;border:1.5px solid #232c3b;border-radius:9px;padding:10px 12px;color:#e9edf5;font-size:13px;font-family:inherit;transition:border-color .15s}
.bis-field input:focus{outline:none;border-color:#38bdf8}
.bis-field input.is-invalid{border-color:#f87171}
.bis-field button{background:#38bdf8;border:none;border-radius:9px;padding:10px 16px;color:#06212e;font-size:12.5px;font-weight:800;cursor:pointer;white-space:nowrap;transition:background .15s}
.bis-field button:hover{background:#22a8ea}
.bis-error{margin-top:8px;font-size:11.5px;color:#f87171}
.bis-error[hidden]{display:none}
.bis-success{display:flex;flex-direction:column;align-items:center;text-align:center;gap:5px;padding:8px 0 4px}
.bis-success[hidden]{display:none}
.bis-success-icon{width:38px;height:38px;border-radius:999px;background:rgba(74,222,128,.14);color:#4ade80;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:800;margin-bottom:6px}
.bis-success-title{font-size:14.5px;font-weight:800;color:#e9edf5}
.bis-success-copy{font-size:12.5px;color:#8993a8;line-height:1.5;margin-bottom:8px}
.bis-success-copy b{color:#c7d0e3;font-weight:700}
.bis-edit-link{background:none;border:none;color:#38bdf8;font-size:12px;font-weight:700;cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.bis-edit-link:hover{color:#7dd8fb}var formWrap = document.getElementById('bisForm');
var successWrap = document.getElementById('bisSuccess');
var form = document.getElementById('bisNotifyForm');
var emailInput = document.getElementById('bisEmail');
var errorEl = document.getElementById('bisError');
var successEmailEl = document.getElementById('bisSuccessEmail');
var editBtn = document.getElementById('bisEdit');
function isValidEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
form.addEventListener('submit', function (e) {
e.preventDefault();
var value = emailInput.value.trim();
if (!isValidEmail(value)) {
emailInput.classList.add('is-invalid');
errorEl.hidden = false;
return;
}
emailInput.classList.remove('is-invalid');
errorEl.hidden = true;
successEmailEl.textContent = value;
formWrap.hidden = true;
successWrap.hidden = false;
});
emailInput.addEventListener('input', function () {
if (emailInput.classList.contains('is-invalid') && isValidEmail(emailInput.value.trim())) {
emailInput.classList.remove('is-invalid');
errorEl.hidden = true;
}
});
editBtn.addEventListener('click', function () {
successWrap.hidden = true;
formWrap.hidden = false;
emailInput.focus();
emailInput.select();
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Back in Stock Notify Me Form — Capture Demand Without Losing the Sale

An out-of-stock page is a moment of lost intent — the shopper wanted to buy, and a plain "sold out" message sends them away for good. A "notify me when available" form turns that lost sale into a captured lead, but only if the form is trustworthy: it needs to validate the email before submitting, confirm clearly that the request went through, and let the shopper fix a typo without starting over. This snippet builds that complete three-state flow in plain HTML, CSS, and vanilla JavaScript.
Inline validation before submission
The form validates on submit with a simple email-shape regex, and — this is the detail that matters — once an invalid attempt has shown an error, the input re-validates on every keystroke so the error clears itself the moment the address becomes valid, rather than requiring a second submit click to discover the error is gone. No network request or backend is simulated; validation is purely client-side shape-checking, which is exactly what should happen before you ever hit an API.
A success state that repeats the email back
After a valid submit, the form swaps for a confirmation view that explicitly echoes the submitted address — "We'll email [email protected] the moment it's back" — rather than a generic "Thanks, you're signed up." Repeating the value back is what lets a shopper immediately notice if they made a typo, instead of finding out weeks later when the notification never arrives.
An edit path back, not a dead end
Right below the confirmation, an "Wrong email? Edit" link returns to the form with the previously entered address still filled in and selected, ready to be corrected and resubmitted. This is a small but important piece of forgiving form design — a mistake shouldn't require abandoning the whole flow and starting from a blank field.
Where it fits
Pair it with a product card or product quick view for the surrounding product context, a waitlist signup for a similar pre-launch capture pattern, or a promo-code-input for the same inline-validation-plus-confirmation approach applied to a different field.
Customizing it
Swap the regex for a stricter validator, add a debounced "check availability" call to confirm the product is genuinely still out of stock before showing the form, or extend the success state with a secondary CTA to browse similar in-stock products.
Step by step
How to Use
- 1Paste HTML, CSS, and JSAn out-of-stock product card renders with a Notify me form.
- 2Submit with a bad emailAn inline error appears and the input outlines in red.
- 3Start typing a valid addressThe error clears automatically as soon as it's valid.
- 4Submit a valid emailThe form swaps for a success state that echoes the address back.
- 5Click "Wrong email? Edit"You return to the form with your previous entry pre-filled and selected.
- 6Wire up the backendReplace the local success-state swap with a real API call in the submit handler.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
On submit, the value is trimmed and tested against a basic email-shape regex ([email protected]). If it fails, the input gets an invalid outline and an inline error shows. If it passes, the form immediately swaps to the success state — there's no simulated network delay since this snippet handles only client-side shape validation.
Once an invalid submission has shown the error, an input listener re-checks the value on every keystroke and removes the error and invalid styling as soon as the address becomes valid — so the shopper isn't stuck staring at a stale error message after they've already fixed it, and doesn't need to click submit again just to clear it.
Echoing the exact address back ("We'll email [email protected]...") is what lets a shopper catch a typo immediately, while they're still on the page, instead of discovering weeks later that the restock notification never arrived because of a mistyped domain.
Clicking it hides the success view and re-shows the form, then focuses and selects the email input's existing text — so the shopper can either overwrite the whole thing or fix a small typo, without needing to re-type an address that was already mostly correct.
Track two booleans in state — hasError and isSubmitted (or a single status enum with 'form' | 'error' | 'success' values) — and conditionally render the three views. The isValidEmail() regex function and the submit handler's logic port over unchanged; only the DOM hidden-attribute toggles become conditional rendering.