HTML5 Forms: JavaScript and the Constraint Validation API

Share this article

Key Takeaways

  • HTML5 allows for client-side form validation without JavaScript coding, but for more sophisticated forms, JavaScript and the Constraint Validation API can be used to enhance native validation. This is due to limitations such as not all browsers supporting all HTML5 input types and CSS selectors, and the difficulty of styling error message bubbles.
  • The Constraint Validation API provides methods and properties such as .willValidate, .checkValidity(), .validity, and .setCustomValidity(). These can be used to check if a field will be validated, validate a field, check the validity of a field, and set custom validity messages respectively. However, not all properties are supported in all browsers.
  • A simple, generic cross-browser form validation system can be created using JavaScript and the Constraint Validation API. This system includes disabling native validation, looping through all fields to check if native validation is available and if the input type is supported, checking the validity of fields, and setting custom validity messages. This system can be adapted to support older browsers and different input types.
For the final article in this three-part series about HTML5 web forms we’ll discuss JavaScript integration and the Constraint Validation API. If you’ve not done so already, please read The Markup and CSS articles to ensure you’re familiar with the concepts. HTML5 allows us to implement client-side form validation without any JavaScript coding. However, we need to enhance native validation when implementing more sophisticated forms because:
  • not all browsers support all HTML5 input types and CSS selectors
  • error message bubbles use generic text (‘please fill out this field’) and are difficult to style
  • :invalid and :required styles are applied on page load before the user interacts with the form.
A sprinkling of JavaScript magic and the Constraint Validation API can improve the user experience. Be aware that this can get a little messy if you want to support a wide range of browsers and input types which we’ll endeavor to do.

Intercepting Form Submissions

Before HTML5, client-side validation involved attaching a submit handler to the form which would validate the fields, display errors and prevent the submit event. In HTML5, the browser will perform its own validation first — the submit event will not fire until the form is valid. Therefore, if you want to do something sophisticated such as displaying your own errors, comparing or auto-filling fields, you must switch off native validation by setting the form’s noValidate property to true:
var form = document.getElementById("myform");
form.noValidate = true;

// set handler to validate the form
// onsubmit used for easier cross-browser compatibility
form.onsubmit = validateForm;
Of course, this means you must check for field errors in code, but it is still possible to leverage native browser validation as we’ll see shortly.

The Field .willValidate Property

Every input field has a .willValidate property. This returns:
  • true when the browser will natively validate the field
  • false when the browser will not validate the field, or
  • undefined when the browser does not support native HTML5 validation, e.g. IE8.
Since we disabled native validation above, every field will return false. Let’s create our validateForm handler which loops through all fields and checks whether native validation is available:
function validateForm(event) {

	// fetch cross-browser event object and form node
	event = (event ? event : window.event);
	var
		form = (event.target ? event.target : event.srcElement),
		f, field, formvalid = true;

	// loop all fields
	for (f = 0; f < form.elements; f++) {

		// get field
		field = form.elements[f];

		// ignore buttons, fieldsets, etc.
		if (field.nodeName !== "INPUT" && field.nodeName !== "TEXTAREA" && field.nodeName !== "SELECT") continue;
The loop iterates through all fields in the form’s elements collection and checks they’re inputs rather than other types such as buttons and fieldsets. The next line is important…
// is native browser validation available?
		if (typeof field.willValidate !== "undefined") {

			// native validation available

		}
		else {

			// native validation not available

		}
Both false and undefined are falsey values so you cannot check just field.willValidate! We now know code inside the first block will evaluate when native validation can be used. However…

Does the Browser Support the Input Type?

If you read part one, you’ll recall that unsupported input types fall back to text. For example:
<input type="date" name="dob" />
is not natively supported in Firefox 29 or IE11. Those browsers will (effectively) render:
<input type="text" name="dob" />
BUT both browsers support validation for text types so field.willValidate will NOT return undefined! We must therefore check that our type attribute matches the object’s .type
property — if they don’t match, we need to implement legacy fall-back validation, e.g.
// native validation available
			if (field.nodeName === "INPUT" && field.type !== field.getAttribute("type")) {

				// input type not supported! Use legacy JavaScript validation

			}

The Field .checkValidity() Method

If native validation is available, the .checkValidity() method can be executed to validate the field. The method returns true if there are no issues or false otherwise. There is a similar .reportValidity() method which returns the current state without re-checking although this is less useful and not supported in all browsers. Both methods will also:
  1. set the field’s .validity object so errors can be inspected in more detail and
  2. fire an invalid event on the field when validation fails. This could be used to show errors, change colors etc. Note there is no corresponding valid event so remember to reset error styles and messages if necessary.

The Field .validity Object

The .validity object has the following properties: .valid – returns true if the field has no errors or false otherwise. .valueMissing – returns true if the field is required and but value has been entered. .typeMismatch – returns true if the value is not the correct syntax, e.g. a badly-formatted email address. .patternMismatch – returns true if the value does not match the pattern attribute’s regular expression. .tooLong – returns true if the value is longer than the permitted maxlength. .tooShort – returns true if the value is shorter than the permitted minlength. .rangeUnderFlow – returns true if the value is lower than min. .rangeOverflow – returns true
if the value is higher than max. .stepMismatch – returns true if the value does not match the step. .badInput – returns true if the entry cannot be converted to a value. .customError – returns true if the field has a custom error set. Not all properties are supported in all browsers so be wary about making too many assumptions. In most cases, .valid or the result of .checkValidity() should be enough to show or hide error messages.

Supporting .validity in Older Browsers

You can manually emulate the .validity object in legacy browsers, e.g.
// native validation not available
			field.validity = field.validity || {};

			// set to result of validation function
			field.validity.valid = LegacyValidation(field);
This ensures .validity.valid can be tested in all browsers.

The Field .setCustomValidity() Method

The .setCustomValidity() method can either be passed:
  • an empty string. This sets the field as valid so .checkValidity() and .validity.valid will return true, or
  • a string containing an error message which will be shown in the message bubble (if used). The message also flags the field as failing so .checkValidity() and .validity.valid will return false and the invalid event will fire.
Note that you can also check the current message using the field’s .validationMessage property.

Putting it all Together

We now have the basis of a simple, generic cross-browser form validation system:
var form = document.getElementById("myform");
form.noValidate = true;

// set handler to validate the form
// onsubmit used for easier cross-browser compatibility
form.onsubmit = validateForm;

function validateForm(event) {

	// fetch cross-browser event object and form node
	event = (event ? event : window.event);
	var
		form = (event.target ? event.target : event.srcElement),
		f, field, formvalid = true;

	// loop all fields
	for (f = 0; f < form.elements; f++) {

		// get field
		field = form.elements[f];

		// ignore buttons, fieldsets, etc.
		if (field.nodeName !== "INPUT" && field.nodeName !== "TEXTAREA" && field.nodeName !== "SELECT") continue;

		// is native browser validation available?
		if (typeof field.willValidate !== "undefined") {

			// native validation available
			if (field.nodeName === "INPUT" && field.type !== field.getAttribute("type")) {

				// input type not supported! Use legacy JavaScript validation
				field.setCustomValidity(LegacyValidation(field) ? "" : "error");

			}

			// native browser check
			field.checkValidity();

		}
		else {

			// native validation not available
			field.validity = field.validity || {};

			// set to result of validation function
			field.validity.valid = LegacyValidation(field);

			// if "invalid" events are required, trigger it here

		}

		if (field.validity.valid) {

			// remove error styles and messages

		}
		else {

			// style field, show error, etc.

			// form is invalid
			formvalid = false;
		}

	}

	// cancel form submit if validation fails
	if (!formvalid) {
		if (event.preventDefault) event.preventDefault();
	}
	return formvalid;
}


// basic legacy validation checking
function LegacyValidation(field) {

	var
		valid = true,
		val = field.value,
		type = field.getAttribute("type"),
		chkbox = (type === "checkbox" || type === "radio"),
		required = field.getAttribute("required"),
		minlength = field.getAttribute("minlength"),
		maxlength = field.getAttribute("maxlength"),
		pattern = field.getAttribute("pattern");

	// disabled fields should not be validated
	if (field.disabled) return valid;

    // value required?
	valid = valid && (!required ||
		(chkbox && field.checked) ||
		(!chkbox && val !== "")
	);

	// minlength or maxlength set?
	valid = valid && (chkbox || (
		(!minlength || val.length >= minlength) &&
		(!maxlength || val.length <= maxlength)
	));

	// test pattern
	if (valid && pattern) {
		pattern = new RegExp(pattern);
		valid = pattern.test(val);
	}

	return valid;
}
The LegacyValidation is purposely left simple; it checks required, minlength, maxlength and pattern regular expressions, but you’ll need additional code to check for emails, URLs, dates, numbers, ranges, etc. Which leads to the question: if you’re writing field validation code for legacy browsers, why bother using the native browser APIs? A very good point! The code above is only necessary if you wanted to support all browsers from IE6 up and offer a similar user experience. That wouldn’t always be necessary…
  • You may not require any JavaScript code for simple forms. Those using legacy browsers could fall back to server-side validation — which should always be implemented.
  • If you require more sophisticated forms but only need to support the latest browsers (IE10+), you can remove all the legacy validation code. You would only require additional JavaScript if your forms used dates which aren’t currently supported in Firefox and IE.
  • Even if you do require code to check for fields such as emails, numbers etc. in IE9 and below, keep it simple and remove it once you cease to support those browsers. It’s a little messy now, but the situation will improve.
But remember to always use the correct HTML5 field type. The browsers will provide native input controls and enforce faster client-side validation even when JavaScript is disabled.

Frequently Asked Questions (FAQs) about HTML5 Forms and JavaScript Constraint Validation API

What is the JavaScript Constraint Validation API and how does it work with HTML5 forms?

The JavaScript Constraint Validation API is a set of methods and properties provided by the browser to validate HTML5 forms. It works by checking whether the data entered by the user meets certain criteria specified in the form’s attributes. For example, if a form field is marked as ‘required’, the API will check if the user has entered a value before the form can be submitted. If the validation fails, the browser will display an error message and prevent the form from being submitted.

How can I use the checkValidity() method in the Constraint Validation API?

The checkValidity() method is used to check if all the form fields meet the validation criteria. It returns a boolean value – true if all fields are valid, and false if at least one field is invalid. Here’s an example of how to use it:

var form = document.getElementById('myForm');
if (form.checkValidity()) {
// form is valid, proceed with submission
} else {
// form is invalid, display error message
}

What is the difference between the checkValidity() and reportValidity() methods?

While both methods are used for form validation, they behave slightly differently. The checkValidity() method simply checks if the form is valid and returns a boolean value. On the other hand, the reportValidity() method not only checks the form’s validity, but also displays the browser’s built-in error messages if the form is invalid.

How can I customize the error messages displayed by the Constraint Validation API?

You can customize the error messages by using the setCustomValidity() method. This method takes a string as an argument, which will be the custom error message displayed when the form is invalid. Here’s an example:

var emailField = document.getElementById('email');
emailField.setCustomValidity('Please enter a valid email address.');

What are the different properties provided by the ValidityState object in the Constraint Validation API?

The ValidityState object provides several properties that indicate different validation states. These include valueMissing (true if the field is required and empty), typeMismatch (true if the value doesn’t match the field’s type), patternMismatch (true if the value doesn’t match the field’s pattern attribute), and several others.

How can I use the Constraint Validation API with different types of form fields?

The Constraint Validation API can be used with various types of form fields, including text fields, email fields, number fields, and more. The validation criteria can be specified using attributes like ‘required’, ‘pattern’, ‘min’, ‘max’, etc. The API will then check if the user’s input meets these criteria.

Can I use the Constraint Validation API with forms that use AJAX?

Yes, the Constraint Validation API can be used with AJAX forms. You can call the checkValidity() or reportValidity() method before sending the AJAX request to ensure the form is valid.

How can I handle form validation for multiple forms on the same page?

You can handle validation for multiple forms by assigning a unique ID to each form and using the getElementById() method to access each form. You can then call the checkValidity() or reportValidity() method on each form separately.

Can I use the Constraint Validation API with older browsers that don’t support HTML5?

The Constraint Validation API is part of HTML5 and may not be fully supported by older browsers. However, you can use JavaScript libraries like Modernizr to detect if the browser supports the API and provide a fallback if it doesn’t.

How can I test if my form validation is working correctly?

You can test your form validation by entering different types of data into the form fields and checking if the correct error messages are displayed. You can also use the console.log() method to log the result of the checkValidity() or reportValidity() method and check if it matches the expected result.

Craig BucklerCraig Buckler
View Author

Craig is a freelance UK web consultant who built his first page for IE2.0 in 1995. Since that time he's been advocating standards, accessibility, and best-practice HTML5 techniques. He's created enterprise specifications, websites and online applications for companies and organisations including the UK Parliament, the European Parliament, the Department of Energy & Climate Change, Microsoft, and more. He's written more than 1,000 articles for SitePoint and you can find him @craigbuckler.

Formshtml5 apisHTML5 Tutorials & Articles
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week