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

AttributeChecks
requiredField isn't empty
minLength / maxLengthText length
min / max / stepNumber and date ranges
typeFormat: email, url, number, date …
patternMatches a regular expression
  • The browser shows its own message and onSubmit never fires while a field is invalid.
  • Style invalid fields with the :invalid / :user-invalid CSS pseudo-classes. :user-invalid only 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

React
<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:

Angular
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, required in 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.email is looser than some backends expect; validate on the server as well.

Quick reference

HTML / ReactAngular validator
requiredValidators.required
minLength={6}Validators.minLength(6)
min={18} / maxValidators.min(18) / Validators.max()
type="email"Validators.email
patternValidators.pattern()
:user-invalid.ng-invalid.ng-touched