Framework Conversion Fixes

How to Reduce Form Abandonment in a Plain HTML Signup Form

TL;DR

A plain HTML form already has everything you need to cut abandonment: required, type='email', and pattern give the browser native, instant validation that never clears the form, and minlength enforces rules inline. You only need a few attributes and a setCustomValidity call for friendly messages — no framework.

A plain HTML form already has what you need to cut abandonment — no framework required. The required, type="email", pattern, and minlength attributes give the browser native, instant validation that never clears the form, and a small setCustomValidity call replaces the default messages with friendly ones. That removes the biggest abandonment cause (lost input) with a handful of attributes.

Why native validation beats a JavaScript validator

The most common abandonment trigger is a form that wipes everything when validation fails. Native HTML validation can't do that: when a constraint fails, the browser blocks the submit and leaves every value in place, then shows an inline message next to the offending field. It also works with zero JavaScript, so it can never break because a bundle failed to load. A hand-rolled JS validator has to reimplement all of that — usually worse.

The form with constraint validation

The attributes do the work:

<form action="/signup" method="post">
  <label>
    Work email
    <input
      name="email"
      type="email"          <!-- browser checks it's a valid email -->
      required              <!-- can't submit empty -->
      autocomplete="email"
    />
  </label>

  <label>
    Password
    <input
      name="password"
      type="password"
      required
      minlength="8"         <!-- inline "must be 8+ characters" -->
    />
  </label>

  <button>Create account</button>
</form>

Submit it empty and the browser stops you with "Please fill out this field" on the first input — without losing anything. type="email" rejects bob@; minlength="8" enforces the password rule inline. autocomplete="email" lets the browser fill it, which itself reduces friction.

Friendlier messages with the Constraint Validation API

The default browser wording is functional but blunt. Replace it without taking over validation:

<script>
  const email = document.querySelector('input[name="email"]')
  email.addEventListener('invalid', () => {
    if (email.validity.valueMissing) email.setCustomValidity('We need your email to create the account.')
    else if (email.validity.typeMismatch) email.setCustomValidity('That doesn’t look like a valid email.')
  })
  // Clear the custom message as soon as they start fixing it
  email.addEventListener('input', () => email.setCustomValidity(''))
</script>

This is progressive enhancement: with JS, the messages are friendlier; without it, native validation still works perfectly. You never lose the resilient baseline.

Steps to apply it

  1. Add required to every field that's genuinely required — and remove the ones that aren't.
  2. Use the right type (email, tel, url) so the browser validates format for free.
  3. Enforce rules with minlength, maxlength, or pattern instead of JavaScript.
  4. Add autocomplete attributes so browsers can fill fields.
  5. Optionally, use setCustomValidity for friendlier copy — as an enhancement, not a dependency.

Measure the abandonment you're cutting

Fire signup_started on first input and signup_completed on success, then:

SELECT
  countDistinctIf(person_id, event = 'signup_started') AS started,
  countDistinctIf(person_id, event = 'signup_completed') AS completed,
  round(
    (countDistinctIf(person_id, event = 'signup_started')
      - countDistinctIf(person_id, event = 'signup_completed'))
    / countDistinctIf(person_id, event = 'signup_started') * 100,
  1) AS abandonment_rate_pct
FROM events
WHERE timestamp > now() - INTERVAL 30 DAY

Illustrative sample output:

started completed abandonment_rate_pct
880 620 29.5

The simplest, most robust signup form is often the most native one. If you'd like the weak points found and fixed as a Pull Request each week, that's what Velyr does.

Frequently asked questions

Can I validate a signup form without JavaScript?

Yes. HTML's constraint validation — the required, type, pattern, minlength, and maxlength attributes — makes the browser validate on submit and show inline messages, with no JavaScript and no library. The form never clears, because a failed native validation simply stops the submit and keeps the values.

How do I show a friendly validation message in plain HTML?

Use the Constraint Validation API: listen for the invalid event and call setCustomValidity with your message, then reset it on input. That replaces the browser's default wording without taking over validation yourself.

Does native HTML validation keep the entered values?

Yes. When native validation fails, the browser blocks the submit and leaves every field exactly as the user typed it. That alone removes the single biggest abandonment cause — losing input on a validation error.

Velyr is an AI growth agent that ships one weekly conversion fix as a GitHub Pull Request — you approve it over Telegram, and it rolls itself back if the numbers drop.

Start the Growth Agent