Form validation is the process of checking whether the data entered by the user is acceptable before the form is submitted.
For example:
User enters:
Email → anubhav
↓
Is this a valid email?
↓
❌
↓
Show validation errorHTML provides a powerful built-in validation system, so you can perform many common validations without JavaScript.
Imagine a registration form:
Name:
[ ]
Email:
[ ]
Age:
[ ]
Password:
[ ]A user could enter:
Name: ""
Email: hello
Age: -50
Password: ""Obviously, your application probably shouldn't accept all of these values.
Validation helps enforce rules such as:
Name → required
Email → valid email format
Age → between 18 and 100
Password → minimum 8 characters
Username → maximum 20 charactersThere are two major layers:
FORM VALIDATION
│
┌─────────┴─────────┐
│ │
▼ ▼
Client-side Server-side
validation validationRuns in the browser.
Examples:
required
minlength
maxlength
pattern
type="email"
min
maxRuns on your backend.
For example:
Browser
↓
Next.js API
↓
Validate data
↓
DatabaseNever assume:
"The browser validated it, therefore the data is safe."
A malicious user can bypass browser validation.
For example, someone can send an HTTP request directly to your backend without using your HTML form.
Therefore:
HTML validation
+
Server validation
+
Authorization
+
Business rulesFor security-sensitive applications, server-side validation is mandatory.
required Attribute#The simplest validation rule is:
<input
type="text"
name="username"
required
>This means:
The user must provide a value.
Without:
requiredthe field can normally be empty.
<label for="email">
Email
</label>
<input
type="email"
id="email"
name="email"
required
>Now the browser checks both:
Field is not empty
+
Value matches email-type constraintsSuppose:
<input
type="email"
name="email"
required
>User clicks:
[ Submit ]without entering anything.
The browser can prevent submission and display a native validation message.
Conceptually:
Email
[ ]
↓ Submit
❌ Please fill out this field.The exact message depends on the browser and user's language.
minlength#minlength specifies the minimum number of characters.
Example:
<input
type="text"
name="username"
minlength="3"
>This means:
Minimum = 3 charactersSo:
ab ❌
abc ✅
anubhav ✅maxlength#maxlength specifies the maximum number of characters.
<input
type="text"
name="username"
maxlength="20"
>Maximum:
20 charactersThis can be useful for:
minlength and maxlength#<input
type="text"
name="username"
minlength="3"
maxlength="20"
required
>Now the field must satisfy:
Required
AND
minimum 3 characters
AND
maximum 20 charactersmin and max#For numeric controls:
<input
type="number"
name="age"
min="18"
max="100"
>Allowed range:
18 ───────────────── 100Examples:
17 ❌
18 ✅
25 ✅
100 ✅
101 ❌step#step specifies the allowed increments.
Example:
<input
type="number"
name="price"
min="0"
step="0.01"
>This supports values such as:
10
10.01
10.50
99.99For integers:
<input
type="number"
min="0"
max="100"
step="1"
>step With Range#For a range slider:
<input
type="range"
name="volume"
min="0"
max="100"
step="10"
>Possible values:
0
10
20
30
40
...
100HTML provides a specialized input type:
<input type="email">Example:
<label for="email">
Email
</label>
<input
type="email"
id="email"
name="email"
required
>The browser can detect values that don't satisfy its email-type constraint.
For example:
hello → likely invalid
hello@ → likely invalid
hello@example.com → valid format"Valid email syntax" is not the same thing as:
"This email address actually exists."
HTML cannot determine whether an email inbox exists.
Use:
<input
type="url"
name="website"
>Example:
<label for="website">
Website
</label>
<input
type="url"
id="website"
name="website"
>The browser applies URL-type constraints.
<input
type="number"
name="age"
min="18"
max="100"
required
>This gives the browser information about the expected input.
You can also constrain dates.
Example:
<input
type="date"
name="dob"
min="1950-01-01"
max="2010-12-31"
>The browser can use those constraints when validating the field.
Similarly:
<input
type="time"
name="appointment"
min="09:00"
max="18:00"
>This describes an allowed time range.
pattern Attribute#pattern allows you to specify a regular-expression-based constraint.
Example:
<input
type="text"
name="username"
pattern="[A-Za-z0-9]+"
>This says the value should contain only:
A-Z
a-z
0-9So:
anubhav123 ✅
Warrior2026 ✅
anubhav_123 ❌
anubhav@123 ❌A simple example:
<input
type="tel"
name="phone"
pattern="[0-9]{10}"
>This describes exactly 10 digits.
Examples:
9876543210 ✅
123456789 ❌
12345678901 ❌
98765abc10 ❌Phone-number formats differ internationally.
For a production application, don't blindly assume every phone number is exactly 10 digits.
Suppose your application wants:
3–15 characters
letters, numbers and underscoreYou might use:
<input
type="text"
name="username"
pattern="[A-Za-z0-9_]{3,15}"
required
>Examples:
warrior123 ✅
anubhav ✅
dev_user ✅
ab ❌
hello-world ❌pattern Detail#Modern HTML pattern matching uses the browser's regular-expression semantics, with the pattern effectively required to match the whole value, not merely find a substring.
Therefore, when designing patterns, think in terms of:
Does the entire value satisfy my rule?rather than:
Does some part of the value match?required + pattern#These are often combined:
<input
type="text"
name="username"
required
pattern="[A-Za-z0-9_]{3,15}"
>Now:
empty
→ ❌ required
abc
→ ✅
abc_123
→ ✅
ab
→ ❌ too short
hello-world
→ ❌ invalid patternYou can also require a radio group to have a selection.
Example:
<fieldset>
<legend>Choose a plan</legend>
<label>
<input
type="radio"
name="plan"
value="free"
required
>
Free
</label>
<label>
<input
type="radio"
name="plan"
value="pro"
>
Pro
</label>
</fieldset>The group needs a valid selection before submission.
A checkbox can also be required.
For example:
<label>
<input
type="checkbox"
name="terms"
value="accepted"
required
>
I agree to the Terms and Conditions
</label>The user must check it before submitting.
This is common for:
☐ I agree to Terms and ConditionsExample:
<select
name="country"
required
>
<option value="">
Select a country
</option>
<option value="india">
India
</option>
<option value="usa">
USA
</option>
</select>The initial option has:
value=""so it doesn't satisfy the required selection.
A more robust pattern is:
<select name="country" required>
<option
value=""
selected
disabled
>
Select a country
</option>
<option value="india">
India
</option>
<option value="usa">
USA
</option>
</select><textarea
name="message"
required
></textarea>The user must provide content before submitting.
Let's build a small registration form.
<form action="/register" method="post">
<div>
<label for="username">
Username
</label>
<input
type="text"
id="username"
name="username"
minlength="3"
maxlength="20"
pattern="[A-Za-z0-9_]+"
required
>
</div>
<div>
<label for="email">
Email
</label>
<input
type="email"
id="email"
name="email"
required
>
</div>
<div>
<label for="age">
Age
</label>
<input
type="number"
id="age"
name="age"
min="18"
max="100"
required
>
</div>
<div>
<label for="password">
Password
</label>
<input
type="password"
id="password"
name="password"
minlength="8"
required
>
</div>
<button type="submit">
Register
</button>
</form>Now HTML itself handles many basic constraints.
HTML validation becomes even more powerful when JavaScript is involved.
Browsers expose the:
Constraint Validation APIThis allows JavaScript to inspect and trigger validation.
Important methods include:
checkValidity()
reportValidity()
setCustomValidity()checkValidity()#Suppose:
<input
id="email"
type="email"
required
>JavaScript can do:
const email = document.querySelector("#email");
console.log(email.checkValidity());It returns:
trueif the element satisfies its constraints.
Otherwise:
falseform.checkValidity()#You can validate the entire form:
const form = document.querySelector("form");
if (form.checkValidity()) {
console.log("Form is valid");
} else {
console.log("Form is invalid");
}This is useful when building custom application logic.
reportValidity()#reportValidity() checks validity and asks the browser to report validation errors to the user.
Example:
const form = document.querySelector("form");
form.reportValidity();Conceptually:
Form
↓
Validation
↓
Invalid?
↓
Browser displays validation messagesetCustomValidity()#This allows JavaScript to define a custom validation error.
Example:
const username = document.querySelector("#username");
username.setCustomValidity(
"This username is already taken."
);Now the field is considered invalid.
To clear the custom error:
username.setCustomValidity("");This is important:
non-empty custom message
→ invalid
empty string
→ custom error clearedvalidity#Form controls also expose a:
validityproperty.
Example:
const input = document.querySelector("#email");
console.log(input.validity);It provides information about different validation states.
Common properties include:
valueMissing
typeMismatch
patternMismatch
tooLong
tooShort
rangeUnderflow
rangeOverflow
stepMismatch
badInput
customError
valid
invalidvalidity#Suppose:
<input
type="email"
id="email"
required
>If the user leaves it empty:
email.validity.valueMissingcan be:
trueIf they enter something that doesn't satisfy the email constraint:
email.validity.typeMismatchcan indicate the problem.
This lets JavaScript build more customized validation experiences.
valid and invalid CSS States#CSS provides pseudo-classes:
input:valid {
/* valid state */
}
input:invalid {
/* invalid state */
}For example:
input:invalid {
border: 2px solid red;
}
input:valid {
border: 2px solid green;
}We aren't focusing on CSS yet, but understand the concept:
HTML constraint
↓
Validity state
↓
CSS can react:user-valid and :user-invalid#Modern browsers also support user-interaction-aware validation pseudo-classes such as:
input:user-invalidand:
input:user-validThese can help avoid immediately showing every validation state before the user has interacted with the field.
Browser support and exact behavior can vary, so production applications should test their target browsers.
novalidate#You learned this in the previous chapter:
<form novalidate>It disables the browser's automatic constraint-validation step when submitting that form.
For example:
<form
action="/register"
method="post"
novalidate
>Now your application can implement a custom validation flow.
But:
novalidatedoes not mean:
"Don't validate anything."It means:
Don't use the browser's normal interactive constraint-validation behavior for form submission.
Your server should still validate.
formnovalidate#You can also disable constraint validation for a particular submit button.
<form>
<input
type="email"
name="email"
required
>
<button type="submit">
Submit
</button>
<button
type="submit"
formnovalidate
>
Save Draft
</button>
</form>This can be useful when:
Submit
→ requires complete valid form
Save Draft
→ allows incomplete formVery useful in real applications.
A useful mental model:
USER
│
▼
Enters data
│
▼
HTML constraints
│
┌───────┴────────┐
│ │
Valid Invalid
│ │
▼ ▼
Continue Show error
│
▼
Submit
│
▼
Server
│
▼
Server-side validation
│
┌───┴────┐
│ │
Valid Invalid
│ │
▼ ▼
Process RejectThis distinction is extremely important for interviews.
| Client-side | Server-side |
|---|---|
| Runs in browser | Runs on server |
| Fast feedback | Trusted validation layer |
| Better UX | Security/business rules |
| Can be bypassed | Must be enforced |
| Uses HTML/JS | Uses backend code |
For example:
HTML:
age >= 18is good UX.
But your backend should also verify:
age >= 18because the client cannot be trusted.
Suppose your HTML says:
<input
type="number"
min="18"
>That's useful.
But imagine your business rule is:
Only users aged 21+ can purchase this product.HTML alone shouldn't be treated as the authoritative business rule.
The backend must enforce:
age >= 21The browser is a user interface, not your security boundary.
Browser validation
↓
DatabaseBad architecture.
Better:
Browser validation
↓
Server validation
↓
Authorization
↓
Databasepattern#Suppose:
<input
pattern="[0-9]{10}"
>A malicious client can bypass it.
Server validation is still necessary.
type="number" for everything numeric#Phone numbers aren't really mathematical numbers.
For example:
+91 9876543210or:
0012345678can make number inappropriate.
For phone numbers:
<input type="tel">is generally more semantically appropriate.
Don't try to replace proper email handling with a giant regex unless you have a very specific requirement.
For normal browser-side validation:
<input type="email">is often the right starting point.
Then perform appropriate server-side validation.
<form action="/register" method="post">
<fieldset>
<legend>Account Information</legend>
<p>
<label for="username">
Username
</label>
<input
type="text"
id="username"
name="username"
minlength="3"
maxlength="20"
pattern="[A-Za-z0-9_]+"
autocomplete="username"
required
>
</p>
<p>
<label for="email">
Email
</label>
<input
type="email"
id="email"
name="email"
autocomplete="email"
required
>
</p>
<p>
<label for="password">
Password
</label>
<input
type="password"
id="password"
name="password"
minlength="8"
autocomplete="new-password"
required
>
</p>
</fieldset>
<fieldset>
<legend>Personal Information</legend>
<p>
<label for="age">
Age
</label>
<input
type="number"
id="age"
name="age"
min="18"
max="100"
required
>
</p>
<p>
<label for="website">
Website
</label>
<input
type="url"
id="website"
name="website"
>
</p>
</fieldset>
<label>
<input
type="checkbox"
name="terms"
value="accepted"
required
>
I agree to the Terms and Conditions
</label>
<br><br>
<button type="submit">
Create Account
</button>
</form>This form uses:
required
minlength
maxlength
pattern
min
max
type validation
autocomplete
checkbox validationwithout JavaScript.
| Attribute | Purpose |
|---|---|
required |
Value must be provided |
minlength |
Minimum text length |
maxlength |
Maximum text length |
min |
Minimum numeric/date value |
max |
Maximum numeric/date value |
step |
Allowed increments |
pattern |
Pattern constraint |
type="email" |
Email-type constraint |
type="url" |
URL-type constraint |
accept |
File-type hint |
multiple |
Multiple values/files |
novalidate |
Disable interactive constraint validation for form submission |
formnovalidate |
Disable it for a specific submit button |
It is the process of checking whether form values satisfy specified constraints before submission.
required do?#It prevents a control from satisfying the required constraint when its value is missing.
min and minlength?#min
→ minimum value
minlength
→ minimum number of charactersExample:
<input type="number" min="18">versus:
<input type="text" minlength="8">maxlength and max?#maxlength
→ maximum number of characters
max
→ maximum valuepattern?#A constraint based on a regular expression.
checkValidity()?#It checks whether a form control or form satisfies its validation constraints.
reportValidity()?#It checks validity and asks the browser to report validation problems to the user.
setCustomValidity() do?#It sets a custom validation error message.
input.setCustomValidity("Username already exists");Clear it with:
input.setCustomValidity("");No.
Client-side validation can be bypassed.
novalidate?#It tells the browser not to perform its normal interactive constraint validation when submitting that form.
Build a registration page containing:
CREATE ACCOUNT
Username
[________________________]
Email
[________________________]
Password
[________________________]
Confirm Password
[________________________]
Age
[________________________]
Phone
[________________________]
Country
[ Select Country ▼ ]
Gender
○ Male
○ Female
○ Other
Skills
☐ HTML
☐ CSS
☐ JavaScript
☐ React
☐ I agree to Terms
[ Create Account ]Apply these constraints:
Username
→ required
→ 3–20 characters
→ letters/numbers/underscore
Email
→ required
→ email type
Password
→ required
→ minimum 8 characters
Age
→ required
→ 18–100
Country
→ required
Gender
→ required
Terms
→ requiredTry doing the entire project with HTML only first.
Then later, JavaScript can enhance the experience.
HTML validation is essentially:
FORM
│
▼
Define constraints
│
┌───────┴────────┐
│ │
Valid Invalid
│ │
▼ ▼
Continue Error shown
│
▼
Submit
│
▼
Server
│
▼
Validate againThe most important principle is:
Browser validation improves user experience; server-side validation protects your application.
Next we'll move from forms into modern HTML5 semantics and learn:
<header><nav><main><section><article><aside><footer><figure><figcaption><address><div> vs <section><section> vs <article>Mark it as complete to track your mastery.