Form Validation Using JavaScript's Constraint Validation API
Tags: javascript, beginners, webdev
Published: 2022-01-11 04:43:36

_by author [Craig Buckler](https://blog.openreplay.com/authors/craig-buckler)_

Form validation is one of the primary reasons to use client-side JavaScript. It can prevent user entry errors before your app attempts to submit data to the server. But...

> client-side validation is not a substitute for server-side validation!

Always check incoming user data on the server. Not every request will come from a browser nor will every browser run validation code. Client-side validation is an enhancement which makes your system nicer to use.

That said, you may not require JavaScript for simpler form validation...


## HTML5 Field Validation

Modern HTML5 browsers can validate fields without JavaScript. A `type` attribute sets a constraint and can alter the UI of a standard [`<input>`](https://developer.mozilla.org/docs/Web/HTML/Element/input) field:

|`type`|description|
|-|-|
|`button`|button with no default behavior|
|`checkbox`|checkbox|
|`color`|color picker|
|`date`|date picker for the year, month, and day|
|`datetime-local`|date and time picker|
|`email`|email entry|
|`file`|file picker|
|`hidden`|hidden|
|`image`|button which displays an image defined in the `src` attribute|
|`month`|month and year picker|
|`number`|number entry|
|`password`|password entry (obscured text)|
|`radio`|radio button|
|`range`|slider control|
|`reset`|resets all form inputs to default values (please avoid)|
|`search`|search entry|
|`submit`|form submit button|
|`tel`|telephone number|
|`text`|text entry|
|`time`|time picker (no time zone)|
|`url`|URL entry|
|`week`|week number and year picker|

The browser assumes `text` when you omit or use an unsupported `type` attribute. This should allow a user to enter something in older browsers even if it cannot be automatically validated.

Other constraint control attributes include:

|attribute|description|
|-|-|
|`accept`|file upload type|
|`alt`|alt attribute for the `image`|
|`autocomplete`|[auto-completion hint](https://developer.mozilla.org/docs/Web/HTML/Attributes/autocomplete)|
|`autofocus`|focus field|
|`capture`|media capture method|
|`checked`|check checkbox or radio|
|`disabled`|disable field - it's not validated or submitted|
|`form`|associate with a form using this id|
|`formaction`|URL for submission (submit and image buttons)|
|`inputmode`|data type hint (`none`, `text`, `tel`, `url`, `email`, `numeric`, `decimal`, `search`)|
|`list`|id of `<datalist>`|
|`max`|maximum value|
|`maxlength`|maximum string length|
|`min`|minimum value|
|`minlength`|minimum string length|
|`name`|name of control (submitted to server)|
|`pattern`|validation pattern regular expression, e.g. `[1-3]+` one or more digits from 1 to 3|
|`placeholder`|placeholder text for empty fields|
|`readonly`|field cannot be edited - it's validated and submitted|
|`required`|the field is required|
|`size`|the size of the control usually overridden in CSS|
|`spellcheck`|set `true` or `false` spell-checking|
|`src`|image URL|
|`step`|incremental values in numbers and ranges|
|`value`|the initial value|


### Input Interfaces

Field types and constraint attributes can change the browser's UI behaviour. A `number` input shows a numeric keyboard on mobile devices and may also display a spinner or allow up/down cursor presses to increment and decrement values.

Use the most obvious `type` but be wary of special cases like credit cards. These are numeric but the spinner and keyboard cursor keys are not helpful. It's better to use a standard `text` input:

```html
<input
  type="text"
  name="ccnumber"
  inputmode="numeric"
  autocomplete="cc-number"
/>
```

The browser may offer features such as camera scanning or SMS confirmation when it encounters the `autocomplete` type.


### Built-In Validation

The browser ensures a value adheres to constraints defined by the `type`, `min`, `max`, `step`, `minlength`, `maxlength`, `pattern`, and `required` attributes:

```html
<input type="number" min="1" max="100" required />
```

Attempting to submit an empty, invalid, or out-of-range value stops form submission and shows a generic error message:

![input required validation message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/92kgqu75jvssg7vf2o7w.png)

You can stop the browser's default validation by adding:

1. a `novalidate` attribute to the `<form>`, or
1. a `formnovalidate` attribute to the submission button/image `<input>`.


## CSS Validation Styles

The following [pseudo-classes](https://developer.mozilla.org/docs/Web/CSS/Pseudo-classes) allow you to style inputs according to their state:

|selector|description|
|-|-|
|`:focus`|the field with focus|
|`:focus-within`|an element contains a field with focus|
|`:focus-visible`|an element has focus owing to keyboard navigation|
|`:required`|a field with a `required` attribute|
|`:optional`|a field without a `required` attribute|
|`:valid`|a field that has passed validation|
|`:invalid`|a field that has not passed validation|
|`:user-valid`|a field that has passed validation after a user interaction (Firefox only)|
|`:user-invalid`|a field that has not passed validation after a user interaction (Firefox only)|
|`:in-range`|the value is within range on a `number` or `range` input|
|`:out-of-range`|the value is out of range on a `number` or `range` input|
|`:disabled`|a field with a `disabled` attribute|
|`:enabled`|a field without a `disabled` attribute|
|`:read-only`|a field with a `read-only` attribute|
|`:read-write:`|a field without a `read-only` attribute|
|`:checked`|a checked checkbox or radio button|
|`:indeterminate`|an indeterminate checkbox or radio state|
|`:default`|the default submit button or image|

These selectors have the same specificity so order can be important. For example:

```css
input:invalid { color: red; }
input:enabled { color: blue; }
```

Invalid inputs have red text -- but this is overridden because all non-disabled inputs are blue.

You should also be wary that the browser applies validation styles on page load. The following example shows every field with red text and a red border before the user has had a chance to interact with the form:

```css
:invalid {
  color: red;
  border-color: red;
}
```

In general, it's preferable to show error messages and colors after the user has interacted with a field or attempted to submit the whole form.


## The Constraint Validation API

The [Constraint Validation API](https://developer.mozilla.org/docs/Web/API/Constraint_validation) provides customization options which can enhance or modify standard HTML5 field checking behavior. You can perform validation after user interaction, show specific error messages, or implement programmatic validation, e.g. confirm that:

* new and confirmation password fields match
* one date comes after another
* the user has entered a telephone number, an email address, or both.

The following sections describe a general approach to using the Constraint Validation API in your own code.


### 1. Disable Default Form Validation

Your code should disable default HTML5 form validation and error messages by setting the `<form>` `noValidate` property/attribute to `true`, e.g.:

```javascript
const myform = document.getElementById('myform');
myform.noValidate = true;
```


### 2. Add Form Event Handlers

You should then add event handlers for form submits or individual field changes as necessary, e.g.

```javascript
// validate form on submit
myform.addEventListener('submit', validateForm);
```

The handler function can check the whole form is valid using the form element methods:

1. `checkValidity()` -- returns `true` if **all** child controls are subject to constraint validation and satisfy the constraints

1. `reportValidity()` -- returns `true` if a child control is not subject to constraint validation or it satisfies the constraints.

Note that both methods trigger an [`invalid` event](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/invalid_event) on every invalid field. It does not bubble so handlers must be set on every field as necessary.

This simple handler function stops the form submitting when one or more fields are invalid:

```javascript
// validate form on submission
function validateForm(e) {

  const form = e.target;

  if (!form.checkValidity()) {

    // form is invalid - cancel submit
    e.preventDefault();
    e.stopImmediatePropagation();

  }

};
```


### 3. Validate Individual Fields

Individual fields have the following constraint validation properties and methods:

* `willValidate`

  Returns `true` when an element is subject to constraint validation.

* `validationMessage`

  The validation message -- an empty string when a field is valid.

* `valitity`

  A [ValidityState object](https://developer.mozilla.org/docs/Web/API/ValidityState) with a `valid` property set `true` or `false`. Invalid fields have one or more of the following properties set `true`:

|ValidityState|description|
|-|-|
|`.badInput`|the browser cannot understand the input|
|`.customError`|a custom validity message has been set|
|`.patternMismatch`|the value does not match the specified `pattern` attribute|
|`.rangeOverflow`|the value is greater than the `max` attribute|
|`.rangeUnderflow`|the value is less than the `min` attribute|
|`.stepMismatch`|the value does not fit `step` attribute rules|
|`.tooLong`|the string length is greater than the `maxlength` attribute|
|`.tooShort`|the string length is less than the `minlength` attribute|
|`.typeMismatch`|the value is not a valid email or URL|
|`.valueMissing`|a `required` value is empty|

* `setCustomValidity(message)`

  Sets an error message for an invalid field. Pass an empty string when the field is valid or it remains invalid forever.

* `checkValidity()`

  Returns `true` when the input is valid. This is similar to checking the `valitity.valid` property but also triggers an `invalid` event on the field if necessary.

The `validateForm()` handler function could loop through every field and apply an `invalid` class to its parent element:

```javascript
function validateForm(e) {

  const
    form = e.target,
    field = Array.from(form.elements);

  // apply/remove invalid class
  field.forEach(i => {

    if (i.checkValidity()) {

      // field is valid - remove class
      i.parentElement.classList.remove('invalid');

    }
    else {

      // field is invalid - add class
      i.parentElement.classList.add('invalid');

    }

  });

  if (!form.checkValidity()) {

    // form is invalid - cancel submit
    e.preventDefault();
    e.stopImmediatePropagation();

  }

};
```

If you defined a required email field in your HTML5 form:

```html
<div>
  <label for="email">email</label>
  <input type="email" id="email" name="email" required />
  <p class="help">Please enter a valid email address.</p>
</div>
```

The script applies an `invalid` class to the container `<div>` when the email is not valid. CSS can alter the styling and show or hide the error message:

```css
.help { display: none; }

.invalid .help { display: block; }

.invalid label, .invalid input, .invalid .help {
  color: red;
  border-color: red;
}
```

![invalid email error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bquhtkfu54lnunof74fb.png)


### 4. Add Custom Field Validation

[This CodePen demonstration](https://codepen.io/craigbuckler/pen/poWxoZV) shows an example contact form with name (required), email, and telephone fields. Validation occurs on submit and the form is considered valid when the name and either the email and/or the telephone have valid values:

![form errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cw7gvi0qrnll05fsmeob.png)

The `validateForm()` function below:

1. resets each field's validity message and parent error styling
1. passes a non-empty string to the email and telephone field `.setCustomValidity()` method when both are not defined, and
1. applies the `invalid` class to the parent element of all invalid fields.

```javascript
// form validation
function validateForm(e) {

  const
    form = e.target,
    field = Array.from(form.elements);

  // reset fields
  field.forEach(i => {
    i.setCustomValidity('');
    i.parentElement.classList.remove('invalid');
  });

  // email or tel set?
  const err = form.email.value || form.tel.value ? '' : 'error';
  form.email.setCustomValidity(err);
  form.tel.setCustomValidity(err);

  if (!form.checkValidity()) {

    // form is invalid - cancel submit
    e.preventDefault();
    e.stopImmediatePropagation();

    // apply invalid class
    field.forEach(i => {

      if (!i.checkValidity()) {

        // field is invalid - add class
        i.parentElement.classList.add('invalid');

      }

    });

  }

}
```
## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue.
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).



## Summary

Form validation can be mundane but it's a necessary web development activity. However, the days of writing your own email regular expressions and numeric checkers are long gone. Use the standard HTML input types then apply JavaScript enhancements with the Constraint Validation API when necessary.

Finally: *never forget to validate user data on the server!*