HTML Formatterhtmlformatteronline.com

Newsletter signup

An inline email capture with validation and a success state, in three layouts.

formnewsletteremailsignupcta
<!-- 1. inline -->
<form class="news inline" novalidate>
  <label for="e1">Get the monthly dispatch</label>
  <div class="row">
    <input type="email" id="e1" name="email" placeholder="you@example.com"
           autocomplete="email" required aria-describedby="e1-err">
    <button type="submit">Subscribe</button>
  </div>
  <p class="hint">One email a month. Unsubscribe in one click.</p>
  <p class="err" id="e1-err" hidden>Please enter a valid email address.</p>
  <p class="done" hidden>Thanks — check your inbox to confirm.</p>
</form>

<!-- 2. boxed -->
<form class="news boxed" novalidate>
  <h3>The monthly dispatch</h3>
  <p class="blurb">Notes on cloth, making and the occasional mistake. Sent once a month, never sold.</p>
  <label for="e2">Email address</label>
  <input type="email" id="e2" name="email" autocomplete="email" required aria-describedby="e2-err">
  <p class="err" id="e2-err" hidden>Please enter a valid email address.</p>
  <button type="submit">Subscribe</button>
  <p class="done" hidden>Thanks — check your inbox to confirm.</p>
</form>

<!-- 3. footer strip -->
<form class="news strip" novalidate>
  <div>
    <strong>Stay in touch</strong>
    <span>One email a month.</span>
  </div>
  <div class="row">
    <label class="sr" for="e3">Email address</label>
    <input type="email" id="e3" name="email" placeholder="you@example.com"
           autocomplete="email" required aria-describedby="e3-err">
    <button type="submit">Join</button>
  </div>
  <p class="err" id="e3-err" hidden>Please enter a valid email address.</p>
  <p class="done" hidden>Thanks — check your inbox to confirm.</p>
</form>

How it works

Three layouts sharing one stylesheet and one handler: inline for mid-page, boxed for a sidebar, and a dark strip for a footer.

Every field has a label, including the strip. In the compact footer version the label is visually hidden with the .sr class rather than removed, so it is still announced. A placeholder is not a label — it vanishes on focus, and it is not read as one by every screen reader.

clip-path: inset(50%) for visually hidden text. This is the modern form of the old clip: rect() hack. Note it does not use display: none or visibility: hidden, either of which would hide the label from screen readers too, defeating the point.

The error clears as it is fixed. Once a field has failed, typing a valid address removes the message immediately rather than making the person submit again to find out.

Success replaces the form, not just adds to it. Inputs are disabled after a successful subscribe so nobody submits twice and wonders whether it worked.

Accessibility notes

The email input uses type="email", which brings up the right keyboard on mobile, and autocomplete="email", which lets the browser fill it in one tap.

aria-invalid carries the error state programmatically and drives the red border, so the two cannot fall out of step.

The dark strip variant needs its contrast checked: placeholder text at #6d7e99 on #1e2a3b is deliberately above the 4.5:1 threshold. Placeholders are the most commonly failed contrast check on any site.

Making it yours

Point the submit at your list provider. Mailchimp, Buttondown, ConvertKit and EmailOctopus all accept a plain form POST; most also offer an API endpoint if you want to keep the person on the page as this does.

Double opt-in is worth the extra step. It keeps your list clean, protects your sending reputation, and in several jurisdictions it is what proves consent. The success message here says "check your inbox to confirm" for that reason.

If you are in the EU or UK, an unticked consent checkbox is required for marketing email. A pre-ticked box is not consent under GDPR.

Related templates

Check your version

Once you have edited this, the HTML validator will catch any tag you left unclosed, and the formatter will tidy the indentation. Both run in your browser.