Working with Forms & User InputEntry 7 of 10
Built-in Browser Validation
HTML validation attributes let the browser check input and block submission, without JavaScript.
Notes
| Attribute | Checks |
|---|---|
required | Field isn't empty |
minLength / maxLength | Text length |
min / max / step | Number and date ranges |
type | Format: email, url, number, date … |
pattern | Matches a regular expression |
- The browser shows its own message and
onSubmitnever fires while a field is invalid. - Style invalid fields with the
:invalid/:user-invalidCSS pseudo-classes.:user-invalidonly applies after the user has interacted, avoiding red fields on page load. - In JSX, attributes are camelCase:
minLength,maxLength. - Built-in messages can't be fully styled and differ between browsers. For custom messages, use the Constraint Validation API (
setCustomValidity()) or custom validation (Entry 8). - Browser validation is a convenience, not security. Anyone can remove the attributes in DevTools. The backend must validate again.
What / Why / When
- What
Declarative validation built into HTML.
- Why
Zero JavaScript for common rules.
- When
Simple rules where the browser's default messages are acceptable.
- Use case
A required email and a password of at least 6 characters.
Code compare
<form onSubmit={handleSubmit}> <label htmlFor="email">Email</label> <input id="email" type="email" name="email" required /> <label htmlFor="password">Password</label> <input id="password" type="password" name="password" required minLength={6} /> <label htmlFor="age">Age</label> <input id="age" type="number" name="age" min={18} max={120} /> <label htmlFor="zip">ZIP code</label> <input id="zip" name="zip" pattern="\d{5}" title="Five digits" /> <button type="submit">Sign up</button></form>Angular adds novalidate to every form automatically, turning browser validation off. Validation comes from Angular validators instead:
form = new FormGroup({ email: new FormControl("", { nonNullable: true, validators: [Validators.required, Validators.email], }), password: new FormControl("", { nonNullable: true, validators: [Validators.required, Validators.minLength(6)], }), age: new FormControl<number | null>(null, [Validators.min(18), Validators.max(120)]), zip: new FormControl("", { nonNullable: true, validators: [Validators.pattern(/^\d{5}$/)] }),});To use the browser's native popups instead, add ngNativeValidate to the <form>.
Differences and gotchas
- In Angular,
requiredin the template works as a validator too (it's also a directive), but with Reactive Forms keep validators in the class so there's one source of truth. Validators.emailis looser than some backends expect; validate on the server as well.
Quick reference
| HTML / React | Angular validator |
|---|---|
required | Validators.required |
minLength={6} | Validators.minLength(6) |
min={18} / max | Validators.min(18) / Validators.max() |
type="email" | Validators.email |
pattern | Validators.pattern() |
:user-invalid | .ng-invalid.ng-touched |