Form Validation Using JavaScript's Constraint Validation API

Form Validation Using JavaScript's Constraint Validation API

by author 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> field:

typedescription
buttonbutton with no default behavior
checkboxcheckbox
colorcolor picker
datedate picker for the year, month, and day
datetime-localdate and time picker
emailemail entry
filefile picker
hiddenhidden
imagebutton which displays an image defined in the src attribute
monthmonth and year picker
numbernumber entry
passwordpassword entry (obscured text)
radioradio button
rangeslider control
resetresets all form inputs to default values (please avoid)
searchsearch entry
submitform submit button
teltelephone number
texttext entry
timetime picker (no time zone)
urlURL entry
weekweek 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:

attributedescription
acceptfile upload type
altalt attribute for the image
autocompleteauto-completion hint
autofocusfocus field
capturemedia capture method
checkedcheck checkbox or radio
disableddisable field - it's not validated or submitted
formassociate with a form using this id
formactionURL for submission (submit and image buttons)
inputmodedata type hint (none, text, tel, url, email, numeric, decimal, search)
listid of <datalist>
maxmaximum value
maxlengthmaximum string length
minminimum value
minlengthminimum string length
namename of control (submitted to server)
patternvalidation pattern regular expression, e.g. [1-3]+ one or more digits from 1 to 3
placeholderplaceholder text for empty fields
readonlyfield cannot be edited - it's validated and submitted
requiredthe field is required
sizethe size of the control usually overridden in CSS
spellcheckset true or false spell-checking
srcimage URL
stepincremental values in numbers and ranges
valuethe 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:

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

<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

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

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

CSS Validation Styles

The following pseudo-classes allow you to style inputs according to their state:

selectordescription
:focusthe field with focus
:focus-withinan element contains a field with focus
:focus-visiblean element has focus owing to keyboard navigation
:requireda field with a required attribute
:optionala field without a required attribute
:valida field that has passed validation
:invalida field that has not passed validation
:user-valida field that has passed validation after a user interaction (Firefox only)
:user-invalida field that has not passed validation after a user interaction (Firefox only)
:in-rangethe value is within range on a number or range input
:out-of-rangethe value is out of range on a number or range input
:disableda field with a disabled attribute
:enableda field without a disabled attribute
:read-onlya field with a read-only attribute
:read-write:a field without a read-only attribute
:checkeda checked checkbox or radio button
:indeterminatean indeterminate checkbox or radio state
:defaultthe default submit button or image

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

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:

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

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.

// 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

  2. 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 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:

// 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 with a valid property set true or false. Invalid fields have one or more of the following properties set true:

ValidityStatedescription
.badInputthe browser cannot understand the input
.customErrora custom validity message has been set
.patternMismatchthe value does not match the specified pattern attribute
.rangeOverflowthe value is greater than the max attribute
.rangeUnderflowthe value is less than the min attribute
.stepMismatchthe value does not fit step attribute rules
.tooLongthe string length is greater than the maxlength attribute
.tooShortthe string length is less than the minlength attribute
.typeMismatchthe value is not a valid email or URL
.valueMissinga 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:

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:

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

.help { display: none; }

.invalid .help { display: block; }

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

invalid email error

4. Add Custom Field Validation

This CodePen demonstration 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

The validateForm() function below:

  1. resets each field's validity message and parent error styling
  2. passes a non-empty string to the email and telephone field .setCustomValidity() method when both are not defined, and
  3. applies the invalid class to the parent element of all invalid fields.
// 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 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

Happy debugging, for modern frontend teams - Start monitoring your web app for free.

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!