How to Reduce Form Abandonment in a Plain HTML Signup Form
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
- Add
requiredto every field that's genuinely required — and remove the ones that aren't. - Use the right
type(email,tel,url) so the browser validates format for free. - Enforce rules with
minlength,maxlength, orpatterninstead of JavaScript. - Add
autocompleteattributes so browsers can fill fields. - Optionally, use
setCustomValidityfor 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