Why Validation Matters
Validation serves two purposes:
- User experience β giving immediate feedback prevents frustration from submitting a form only to find out the email was missing the @ symbol.
- Data quality β ensuring data arrives in the format your server or database expects, reducing errors and security risks.
HTML5 provides basic validation for free. For complex rules or security-critical checks, you supplement it with JavaScript and always repeat validation server-side.
The required Attribute
Adding required to a field means the browser will refuse to submit the form if that field is empty. It works on input, textarea, and select.
<form action="/subscribe" method="POST">
<label for="sub-email">Email address *</label>
<input type="email" id="sub-email" name="email" required>
<label for="sub-name">Full name *</label>
<input type="text" id="sub-name" name="name" required>
<!-- Optional field β no required attribute -->
<label for="sub-company">Company (optional)</label>
<input type="text" id="sub-company" name="company">
<button type="submit">Subscribe</button>
</form>
minlength and maxlength
These attributes constrain the number of characters in a text-based input or textarea.
<!-- Password: at least 8, no more than 64 characters -->
<label for="pw">Password</label>
<input type="password" id="pw" name="password"
minlength="8" maxlength="64" required>
<!-- Username: 3β20 characters -->
<label for="uname">Username</label>
<input type="text" id="uname" name="username"
minlength="3" maxlength="20" required>
<!-- Textarea: bio up to 500 characters -->
<label for="bio">Bio (max 500 characters)</label>
<textarea id="bio" name="bio" rows="4" maxlength="500"></textarea>
min, max, step for Numbers and Dates
For type="number", type="range", and date/time inputs, use min, max, and step to constrain the allowed range.
<!-- Age: integer between 16 and 120 -->
<label for="age">Age</label>
<input type="number" id="age" name="age" min="16" max="120" step="1" required>
<!-- Price: dollars and cents, 0.00 to 9999.99 -->
<label for="price">Price (USD)</label>
<input type="number" id="price" name="price" min="0" max="9999.99" step="0.01">
<!-- Event date: must be in 2026 -->
<label for="event-date">Event date</label>
<input type="date" id="event-date" name="event_date"
min="2026-01-01" max="2026-12-31" required>
<!-- Working hours: 9am to 5pm, in 30-min slots -->
<label for="appt-time">Appointment time</label>
<input type="time" id="appt-time" name="time"
min="09:00" max="17:00" step="1800">
The pattern Attribute with Regex
The pattern attribute accepts a JavaScript-compatible regular expression. The entire input value must match the pattern for the form to submit. This is essential for type="tel" (which has no built-in format validation) and for custom text formats.
<!-- UK phone number: starts with 07 or +447, then 9 digits -->
<label for="uk-phone">UK mobile number</label>
<input type="tel" id="uk-phone" name="phone"
pattern="(07\d{9}|\+447\d{9})"
placeholder="07700 900000"
title="Enter a UK mobile number: 07xxxxxxxxx or +447xxxxxxxxx"
required>
<!-- UK postcode: letter(s) + digit(s) + space + digit + two letters -->
<label for="postcode">Postcode</label>
<input type="text" id="postcode" name="postcode"
pattern="[A-Z]{1,2}[0-9][0-9A-Z]?\s?[0-9][A-Z]{2}"
placeholder="SW1A 1AA"
title="Enter a valid UK postcode in uppercase"
required>
<!-- Hex colour code: # followed by exactly 6 hex digits -->
<label for="hex-color">Hex colour</label>
<input type="text" id="hex-color" name="color"
pattern="#[0-9A-Fa-f]{6}"
placeholder="#ff5733"
title="Enter a hex colour code like #ff5733">
<!-- Username: letters, numbers, underscores, 3-20 chars -->
<label for="handle">Username</label>
<input type="text" id="handle" name="username"
pattern="[a-zA-Z0-9_]{3,20}"
title="3 to 20 characters: letters, numbers, and underscores only"
required>
title attribute alongside pattern. The browser shows this text in the validation tooltip, telling users what format is expected. Without it, the error message is generic and unhelpful.
Type Validation
Input types email and url have built-in format validation β no pattern needed for basic checking.
<!-- Browser validates: must contain @ and a domain part -->
<input type="email" name="email" required>
<!-- Browser validates: must start with http:// or https:// -->
<input type="url" name="website">
<!-- Number type rejects non-numeric characters -->
<input type="number" name="quantity" min="1">
The novalidate Attribute
Adding novalidate to the <form> element disables all browser-native validation for that form. This is useful when you want to handle validation entirely with JavaScript and display your own custom error messages.
<!-- Browser validation disabled β JavaScript takes over -->
<form action="/register" method="POST" novalidate id="register-form">
<label for="reg-email">Email</label>
<input type="email" id="reg-email" name="email" required>
<div class="error-message" id="email-error" role="alert"></div>
<button type="submit">Create account</button>
</form>
document.getElementById('register-form').addEventListener('submit', function(e) {
e.preventDefault();
const emailInput = document.getElementById('reg-email');
const errorDiv = document.getElementById('email-error');
if (!emailInput.validity.valid) {
errorDiv.textContent = 'Please enter a valid email address.';
emailInput.focus();
return;
}
errorDiv.textContent = '';
// proceed with form submission
this.submit();
});
Customising Validation Messages with setCustomValidity()
The Constraint Validation API lets you set a custom error message that the browser displays in its native tooltip. Call setCustomValidity('') to clear the error (mark the field valid again).
<label for="confirm-pw">Confirm password</label>
<input type="password" id="confirm-pw" name="confirm_password" required>
const pw = document.getElementById('pw');
const confirmPw = document.getElementById('confirm-pw');
confirmPw.addEventListener('input', function() {
if (this.value !== pw.value) {
this.setCustomValidity('Passwords do not match.');
} else {
this.setCustomValidity(''); // clear error β field is valid
}
});
Client-Side vs Server-Side Validation
Browser validation is a convenience β it is never a security measure on its own.
| Client-side (HTML/JS) | Server-side | |
|---|---|---|
| Speed | Instant, no round-trip | Requires network request |
| Can be bypassed? | Yes β easily disabled in DevTools or via API calls | No β server always runs the check |
| Can access database? | No | Yes β can check uniqueness, existing records |
| Security | Never trust alone | Required for all security-sensitive rules |
| Use for | Immediate UX feedback (empty fields, format hints) | All business logic, unique email, passwords, authorisation |
Client Validation Is UX β Never Trust It for Security
HTML5 gives you free validation with plain attributes β no JavaScript needed. But remember the golden rule: client-side validation is a convenience, not a security boundary. Anyone can bypass it with dev tools or a direct API call, so every rule must be re-checked on the server.
<form>
<input type="email" required>
<input type="password" minlength="8" required>
<input type="text" pattern="[A-Za-z]+" title="Letters only">
<input type="number" min="1" max="99">
<button>Sign up</button>
</form>
| Attribute | Enforces |
|---|---|
required | field can't be empty |
type="email" | basic email shape |
minlength / maxlength | text length bounds |
pattern | custom regex |
Style validity live with the :valid and :invalid CSS pseudo-classes. Add novalidate to the form only when you're taking full control with JavaScript.
ποΈ Practical Exercise
- Make a field
required. - Add a
patternto validate a phone number. - Use
minlengthandmaxlengthon a text field. - Use
type="email"for built-in email checking. - Add
min/maxto a number input and submit an out-of-range value.
π₯ Challenge Exercise
Build a sign-up form using only built-in HTML validation: a required username with minlength, a valid email, a password with a pattern requiring letters and numbers, and an age field with min="18". Submit invalid data and read the browserβs messages. Explain why client-side validation is not a security replacement for server-side checks.
π Summary
requiredβ prevents submission if the field is empty.minlength/maxlengthβ constrains the character count for text inputs and textareas.min/max/stepβ constrains numeric and date input values.patternβ validates input against a regex; always add a descriptivetitle.type="email"andtype="url"perform automatic format validation.novalidateon the form disables native browser validation β use when writing custom JS validation.setCustomValidity()customises the browser's native validation error message.- Client-side validation is for UX; server-side validation is for security β you need both.
Interview Questions
- What is HTML5 built-in form validation?
- What does the
requiredattribute do? - How does the
patternattribute work? - What is the difference between client-side and server-side validation?
- Why should you never rely on client-side validation alone for security?
Related Topics
FAQ
Yes. On a checkbox, required means the box must be checked before the form submits. This is commonly used for "I agree to the terms" checkboxes. For a group of checkboxes where at least one must be selected, you need JavaScript β HTML cannot express that constraint natively.
The pattern attribute implicitly anchors the regex to match the entire value (like wrapping your pattern in ^...$). So a pattern of [0-9]{5} requires the entire field to be exactly 5 digits β not just contain 5 digits somewhere. If your value has extra characters, the match fails. Also check that you haven't used regex delimiters (/pattern/) β the pattern attribute only takes the pattern string, no slashes.
Yes. Add novalidate to the form, prevent default submission in a JavaScript event listener, check element.validity properties, and display your own error elements. The validity object exposes booleans like valueMissing, typeMismatch, patternMismatch, tooShort, and rangeUnderflow so you can tailor each message.
There is no browser-imposed upper limit on the maxlength value itself β you choose the number. A practical guideline: match it to your database column size to prevent truncation errors. For a VARCHAR(255) column, set maxlength="255". For long-form content stored in a TEXT column, you might set 5000 or omit maxlength entirely and rely on server-side trimming.

