Learn how to create great form validations using JavaScript’s native API
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:
button
button with no default behaviorcheckbox
checkboxcolor
color pickerdate
date picker for the year, month, and daydatetime-local
date and time pickeremail
email entryfile
file pickerhidden
hiddenimage
button which displays an image defined in the src
attributemonth
month and year pickernumber
number entrypassword
password entry (obscured text)radio
radio buttonrange
slider controlreset
resets all form inputs to default values (please avoid)search
search entrysubmit
form submit buttontel
telephone numbertext
text entrytime
time picker (no time zone)url
URL entryweek
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:
accept
file upload typealt
alt attribute for the image
imageautocomplete
auto-completion hintautofocus
focus fieldcapture
media capture methodchecked
check checkbox or radiodisabled
disable field - it’s not validated or submittedform
associate with a form using this idformaction
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 valuemaxlength
maximum string lengthmin
minimum valueminlength
minimum string lengthname
name of control (submitted to server)pattern
validation pattern regular expression, e.g. [1-3]+
one or more digits from 1 to 3placeholder
placeholder text for empty fieldsreadonly
field cannot be edited - it’s validated and submittedrequired
the field is requiredsize
the size of the control usually overridden in CSSspellcheck
set true
or false
for spell-checkingsrc
image URLstep
incremental values in numbers and rangesvalue
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:
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:
Attempting to submit an empty, invalid, or out-of-range value stops form submission and shows a generic error message:
You can stop the browser’s default validation by adding:
- a
novalidate
attribute to the<form>
, or - 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:
: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:
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:
checkValidity()
- returnstrue
if all child controls are subject to constraint validation and satisfy the constraintsreportValidity()
- returnstrue
if a child control is not subject to constraint validation or it satisfies the constraints.
Note that both methods trigger an [invalid](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/invalid_event)
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
— Returnstrue
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 avalid
property settrue
orfalse
. Invalid fields have one or more of the following properties settrue
:.badInput
the browser cannot understand the input.customError
a custom validity message has been set.patternMismatch
the value does not match the specifiedpattern
attribute.rangeOverflow
the value is greater than themax
attribute.rangeUnderflow
the value is less than themin
attribute.stepMismatch
the value does not fitstep
attribute rules.tooLong
the string length is greater than themaxlength
attribute.tooShort
the string length is less than theminlength
attribute.typeMismatch
the value is not a valid email or URL.valueMissing
arequired
value is emptysetCustomValidity(message)
— Sets an error message for an invalid field. Pass an empty string when the field is valid or it remains invalid forever.checkValidity()
— Returnstrue
when the input is valid. This is similar to checking thevalitity.valid
property but also triggers aninvalid
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:
Please enter a valid email address.
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;
}
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:
The validateForm()
function below:
- resets each field’s validity message and parent error styling
- passes a non-empty string to the email and telephone field
.setCustomValidity()
method when both are not defined, and - 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.
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!
Originally published at blog.openreplay.com on January 10, 2022.