Email Subscription Widget — HTML CSS JS Snippet

Email Subscription Widget · Forms · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Two-state card: form view and animated success view
Live email validation with regex and shake animation on error
Loading state disables button to prevent double-submission
Animated success checkmark with spring cubic-bezier easing
Social proof avatars with overlapping stack via negative margin
Benefit bullet list to reduce friction and handle objections
Enter-key submission support
Zero dependencies — pure HTML, CSS, JavaScript

About this UI Snippet

Email Subscription Widget — Live Validation, Animated Success State & Social Proof

Screenshot of the Email Subscription Widget snippet rendered live

An email subscription widget is the front door to a newsletter or mailing list. Its job is to convert a casual visitor into a subscriber by presenting a clear value proposition, reducing friction in the signup process, and confirming the subscription with positive feedback. This snippet implements all three: benefit bullets explain the value, a single email field minimises friction, and an animated success state provides satisfying confirmation.

Two-state architecture

The component has two HTML blocks side by side inside the card: #stateIdle (the form) and #stateSuccess (the confirmation). Initially, #stateSuccess has display: none. On successful submission, #stateIdle gets the .hide class (sets display: none) and #stateSuccess gets the .show class (sets display: block and triggers the fadeUp animation). The resetForm() function reverses both class changes. This toggle-between-states pattern avoids page navigation and keeps the widget self-contained.

Email validation

The validateEmail() function uses a regex: /^[^s@]+@[^s@]+.[^s@]+$/. This pattern checks for: at least one non-whitespace, non-@ character before the @, a domain segment after the @, and a dot with at least one character after it. It is intentionally simple — RFC 5321 full email validation is thousands of characters of regex and impractical for UI use. The goal is to catch obvious typos (no @, no dot) not to verify deliverability. Deliverability verification belongs server-side via SMTP probing.

Shake animation for invalid input

When validation fails, the input gets the .invalid class which triggers a CSS @keyframes shake animation: four keyframes moving the element ±4px on the X axis over 0.3s. This is a well-established error affordance from iOS and Android — the haptic-like motion draws the eye to the field without needing additional error text color alone. The animation class is removed by clearError() on the oninput event so it can retrigger on the next submission attempt.

Simulated loading state

A setTimeout of 900ms simulates a network request. In production, replace this with a real API call (Mailchimp, ConvertKit, your own endpoint). The button text changes to "Subscribing..." and a .loading class adds opacity: 0.7; pointer-events: none to prevent double-submission. Always disable the button during async operations.

Success ring animation

The green checkmark circle uses @keyframes popIn: it scales from 0 to 1 using a spring-like cubic-bezier (0.175, 0.885, 0.32, 1.275) which slightly overshoots scale(1) before settling — the same easing used in iOS app icon install animations. The surrounding fadeUp animation on the success state slides the entire block up from 16px below its final position with opacity 0, creating a fluid reveal that feels intentional.

Social proof section

The proof strip at the bottom shows three stacked avatars (initials-based) plus a subscriber count. The avatars use margin-left: -6px on all except the first to create the overlapping stack effect. Different background/text colors per avatar add variety. In production, replace the static "2,400+" with a live subscriber count fetched from your mailing list API.

Benefit bullets

Three concise bullet points with green checkmarks (not list-style markers but actual Unicode ✓ characters styled with color: #16a34a) build pre-commitment by naming specific deliverables. The "unsubscribe anytime" bullet reduces anxiety about commitment — studies show this objection-handling increases opt-in rates.

React integration

Manage email, error, loading, and submitted state with useState. The form and success view are conditional renders: {submitted ? <SuccessState /> : <FormState />}. Pass an onSubmit prop for the API call and use the loading flag to disable the button and show spinner text. Validate in a handleSubmit function before calling the prop.

Accessibility

The email input should have type="email" which triggers the @ keyboard on iOS. Add aria-describedby pointing to the #errorMsg element so screen readers announce the error when it appears. The success state should receive focus programmatically (successRef.current.focus()) so keyboard users know the state changed.

See also the newsletter signup snippet for a lighter inline version, the contact form snippet for a full multi-field form, and the glassmorphism login snippet for another card-based form pattern.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to trace the two-state toggle by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the hide and show classes swap the idle form and the success confirmation, and why the invalid input's shake animation is retriggerable from the oninput handler rather than firing only once. The same assistant can help optimize it — for instance whether the email regex in validateEmail is too permissive or too strict for your real signup flow, or whether the 900ms setTimeout that fakes the network request should be replaced with a real fetch call that also handles error responses. It's also useful for extending the widget: ask it to add a name field with its own validation, wire the "2,400+ developers" count to a live subscriber total, or persist a dismissed/already-subscribed state to localStorage. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build an email subscription widget in plain HTML, CSS, and JavaScript with two mutually exclusive states — no framework, no build step.

Requirements:
- A card containing two full sections: an idle form state (icon, headline, benefit bullets, email input with an inline mail icon, subscribe button, error message area, and a social-proof row with overlapping avatar circles) and a success state (checkmark icon, confirmation headline, the submitted email, and a "subscribe another" reset button) — only one of the two visible at a time via class toggling, not conditional rendering.
- A validateEmail function using a simple regex that checks for a non-whitespace non-@ segment, an @ symbol, a domain segment, and a dot followed by at least one character — intentionally simple, not full RFC validation.
- Submitting with an empty field or a failing regex must show a specific error message, add an invalid class to the input that triggers a CSS keyframe shake animation (a few small left-right translateX keyframes), and refocus the input; typing again must clear both the error text and the invalid class immediately.
- Clicking subscribe with a valid email must switch the button to a loading label and a disabled/dimmed visual state, wait roughly 900ms via setTimeout to simulate a network call, then swap from the idle state to the success state and display the submitted email in the confirmation.
- The success state's checkmark icon must play a spring-style pop-in keyframe animation (scale from 0 to 1 with a cubic-bezier overshoot) the moment it becomes visible.
- A reset button in the success state must clear the input, remove the loading state, and switch back to the idle form so another email can be entered.
- Enter key press inside the email input must trigger the same subscribe logic as clicking the button.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Copy the full HTML cardThe widget needs both #stateIdle and #stateSuccess divs. Both must be present — JavaScript toggles between them. Do not remove either block.
  2. 2
    Add the CSS blockPaste the CSS. Change #4f46e5 (indigo) to your brand accent color throughout. The gradient background on body is optional — remove it to use your own page background.
  3. 3
    Include the JavaScriptAdd the five JS functions. subscribe() validates, shows loading, then transitions to the success state after 900ms. Replace the setTimeout with your real API call.
  4. 4
    Connect to your mailing listReplace the setTimeout in subscribe() with a fetch POST to your email service endpoint (Mailchimp, ConvertKit, etc.). Keep the btn.classList.add("loading") and the success state transition.
  5. 5
    Update the social proof numbersChange "2,400+" in the proof-text span to reflect your real subscriber count. Optionally fetch it from your mailing list API and inject it dynamically.

Real-world uses

Common Use Cases

Newsletter Signups
Developer blogs, design newsletters, and tech publications
SaaS Waitlists
Pre-launch email capture for product waitlists and early access
Course Launches
Online course early-bird and launch notification opt-ins
Developer Tools
Release notifications for open-source libraries and dev tools

Got questions?

Frequently Asked Questions

Replace the setTimeout in subscribe() with a fetch POST to your mailing list API endpoint. Include the email in the request body and handle the response to show success or error states.

Use useState for email, error, loading, and submitted. Render the form or success state conditionally. Pass an onSubmit handler prop for the actual API call.

Add a second input for name above the email row. Include it in the form-row or as a separate row. Add name validation (non-empty check) in the subscribe() function before the email check.

Yes — wrap the .widget in a modal overlay and control visibility with a class toggle. See the modal snippet at /ui-snippets/modal/ for the overlay pattern.

Open the Export menu (or the Test Exports preview) in the toolbar. It generates a Vue 3 single-file component with the validation and submit logic in script setup, an Angular standalone component, a plain React component, and a React + Tailwind version where the widget styles become utility classes. Each export maps the inline handlers to the matching framework event bindings and keeps the success-state transition intact, so the form behaves the same across React, Vue, and Angular — swap the mock submit for your mailing-list API call afterwards.