Every form on the web needs validation. Without it, users submit empty fields, invalid emails, passwords that are too short and phone numbers that contain letters. Your server receives garbage data and either crashes, stores bad data, or sends confusing error pages back to the user.
Good validation does two things. It prevents bad data from reaching your server. And it guides the user — clear, immediate feedback that tells them exactly what is wrong and how to fix it, right next to the field they need to correct.
This guide covers every layer of form validation: HTML5 attributes that validate automatically, CSS states that style valid and invalid fields, and JavaScript for custom rules, real-time feedback and password strength. Plus a fully working form you can copy at the end.
Why Validate — and Where
Validation happens in two places and you need both. They serve different purposes and neither one alone is enough.
Client-side validation (in the browser, using HTML and JavaScript) gives the user instant feedback. It catches mistakes before the form is submitted, saving a round trip to the server. But it is not secure — anyone can open browser DevTools and bypass client-side validation or send requests directly to your API.
Server-side validation (in your backend — Node, Flask, Django) is where security actually lives. Always validate again on the server, regardless of what the client says. Client-side validation is for user experience. Server-side validation is for security.
HTML5 Built-in Validation
HTML5 added a set of attributes that tell the browser to validate a field automatically — no JavaScript needed. The browser checks them before the form is submitted and blocks submission if any field fails. This is the simplest and fastest validation to add.
required — Field Cannot Be Empty
type — Format Validation for Free
The type attribute does more than change the keyboard on mobile. It also validates the format automatically. type="email" checks for a valid email structure. type="url" checks for a valid URL. type="number" rejects letters.
minlength and maxlength — Length Constraints
pattern — Custom Format with Regex
The pattern attribute accepts a regular expression. The field is valid only if the value matches the pattern. This is perfect for phone numbers, postcodes, usernames that must be alphanumeric, and other custom formats.
CSS Validation States — Visual Feedback
CSS has pseudo-classes that match the validation state of form fields. You can use them to add green borders for valid fields, red borders for invalid ones, and show or hide error icons — all without any JavaScript.
:valid and :invalid Pseudo-classes
:user-invalid — Only After the User Interacted
There is a problem with :invalid. It applies immediately on page load, before the user has typed anything. An empty required field shows red the moment the page appears — before the user has even had a chance to fill it in. That is frustrating and bad UX.
:user-invalid solves this. It only activates after the user has actually interacted with the field (typed in it and moved away). Use this instead of bare :invalid for a much better experience.
JavaScript Validation
HTML5 attributes handle simple cases well, but JavaScript lets you write any rule you need — checking that passwords match, limiting usernames to unique values, making one field required only when another is filled in, or showing custom error messages that are clearer than the browser defaults.
The Constraint Validation API
The browser exposes a built-in JavaScript API for working with form validation. Every input element has a validity object that tells you exactly what is wrong and a checkValidity() method that returns true or false.
Custom Error Messages
Browser default error messages like "Please fill out this field" are generic and hard to customise per language. setCustomValidity() lets you set your own message that the browser shows in the tooltip, and it marks the field as invalid so it blocks form submission.
Real-Time Validation - Feedback as You Type
Showing errors only after the user clicks Submit is the bare minimum. The best forms validate in real time - showing green ticks as fields become valid and showing error messages the moment a rule is broken, without waiting for a submit attempt.
Validating on Input - As the User Types
Validating on Blur — When the User Leaves a Field
Validating on every keystroke can be too aggressive — showing errors before the user has finished typing. A gentler approach is to validate on blur, which fires when the user clicks or tabs away from a field. This is the most common pattern in production forms.
Regex Patterns Explained
Regular expressions look intimidating but each one is just a set of rules written in a compact notation. Here are the patterns you will use most often, explained character by character.
Password Strength Meter
A password strength meter tells the user how secure their password is as they type. It checks for length, uppercase, lowercase, numbers and special characters and shows a visual bar that fills up from red to green.
Accessibility — Error Messages That Work for Everyone
Colour alone is not enough to communicate an error. Some users are colour-blind. Screen readers do not read visual styling. Accessible form validation requires text error messages, ARIA attributes and making sure error messages are announced to screen readers.
Complete Working Form
Here is a fully working registration form that brings together everything in this guide — HTML5 attributes, CSS states, real-time JavaScript validation, password strength meter and accessible error messages. All in one file you can copy and use.
Client-Side vs Server-Side — Summary
| Aspect | Client-side (HTML + JS) | Server-side (Backend) |
|---|---|---|
| Purpose | User experience — instant feedback | Security — prevent bad data |
| Can be bypassed | Yes — DevTools, curl, Postman | No — always runs server-side |
| Speed | Instant — no server trip | Requires HTTP round trip |
| Required | Optional but strongly recommended | Always mandatory |
| Error display | Inline next to the field | Usually on the next page or via API |
Quick Reference
| Technique | How to Use It | Best For |
|---|---|---|
| required | HTML attribute | Any field that must not be empty |
| type="email" | HTML attribute | Email format validation for free |
| minlength / maxlength | HTML attributes | String length constraints |
| min / max | HTML attributes on number/date | Numeric and date range constraints |
| pattern | HTML attribute + regex string | Custom format (phone, username, postcode) |
| :user-invalid | CSS pseudo-class | Show red border after user interacted |
| :valid | CSS pseudo-class | Show green border when correct |
| checkValidity() | JS method on input | Check if a field passes all HTML5 rules |
| validity object | JS property on input | Find out specifically why a field failed |
| setCustomValidity() | JS method on input | Custom browser tooltip message |
| blur event | JS addEventListener | Validate when user leaves a field |
| input event | JS addEventListener | Live feedback on every keystroke |
| aria-invalid + role="alert" | HTML attributes | Accessible errors for screen readers |
- Client-side validation is for user experience. Server-side validation is for security. You always need both. Client-side can be bypassed in seconds — never trust user input without re-validating on the server.
- HTML5 validation attributes like required, type="email", minlength, maxlength and pattern validate automatically with zero JavaScript and block form submission when rules are broken.
- Use novalidate on the form element if you want to suppress browser default error tooltips and show your own custom error messages in the UI instead.
- :user-invalid is better than :invalid for styling because it only activates after the user has actually interacted with the field. Bare :invalid shows red borders on page load before anyone has typed anything.
- The Constraint Validation API gives you checkValidity() and the validity object. The validity object tells you exactly which rule failed — valueMissing, typeMismatch, tooShort, patternMismatch and so on.
- The best UX pattern: validate on blur (when the user leaves a field) to show initial errors, and on input to clear errors as the user fixes them. Do not show errors before the user has touched a field.
- Always use text error messages alongside colours. Colour alone fails colour-blind users and screen readers. Use aria-describedby to link error messages to their field, role="alert" to announce them to screen readers, and update aria-invalid to true/false as state changes.
- A password strength meter scores against length, uppercase, lowercase, numbers and special characters. Return a percentage and colour (red/amber/green) based on the score and update a visual bar in real time on every keystroke.
- Never validate password match using the pattern attribute — you cannot reference another field's value in HTML. Do this in JavaScript: compare password.value === confirm.value on blur.
- Run all validators one final time on submit even if you have real-time validation. This catches any field the user skipped entirely.
