Form validation is among the most despised tasks in web development. Implementing client-side validation in a user-friendly, developer-friendly, and accessible way is shockingly difficult - especially in the context of mobile. Worse, since the need for server-side validation is not removed, any client-side validation violates the DRY principle of software development.
In this article we'll look at some mobile form validation best practices, and a few different ways to implement them. We'll specifically look at the following four approaches:
Let's get started by looking at the form we'll build.
Disclaimer: I'm on the jQuery team, employed by Telerik (who makes Kendo UI), and speak / write about constraint validation; therefore I have biases all over the place. I'll do my best to keep this comparison objective.
To keep things simple, we're going to use a form that only collects the user's name and email address.
<form>
<div>
<label for="name">Name:</label>
<input type="text" name="name" id="name">
</div>
<div>
<label for="email">Email:</label>
<input type="email" name="email" id="email">
</div>
<div>
<button>Submit</button>
</div>
</form>
Add in a little CSS to make the form mobile-friendly, and we have the display shown below.
As is, this form has no constraints (other than [type="email"]
, but we'll get to that later). Each of our solutions will need to make Name and Email required, and ensure Email is a valid email address. We'll use the following three error messages:
Let's start with one of the more popular solutions: the jQuery validation plugin.
The jQuery validation plugin is a jQuery plugin that dates back to jQuery's origins - way back in 2006. It is maintained by its author: Jörn Zaefferer, a member of the jQuery team, and the lead developer on the jQuery UI project.
Before looking at the code behind it, let's see the validation plugin in action.
To look at how this is done, let's start with the HTML. The validation plugin reads HTML5 validation attributes from the form elements' markup. Therefore we'll make both <input>
s required by adding the required
attribute. Because the Email's type
is set to "email"
, the validation plugin enforces a valid email address as well.
<input type="text" name="name" id="name" required>
<input type="email" name="email" id="email" required>
Now let's switch over to JavaScript. To setup the validation plugin, select the appropriate <form>
with jQuery and invoke validate()
. The validate()
method accepts a number of options; we set two of them: messages
, and focusInvalid
.
$( "form" ).validate({
messages: {
name: "Name is required.",
email: {
required: "Email is required.",
email: "You must provide a valid email address."
}
},
focusInvalid: false
});
The messages
option lets us configure custom messages the plugin should use. The key of the object is the name
attribute of the field and the value is a message to use. You can also provide a value that is another object - with different error messages for different types of validation issues. We use this for Email to provide different messages for blank and invalid input.
Note: If you only have one message for a field, you can also place the message in the form element's title
attribute instead of using the messages
option.
The last option we set is focusInvalid
. The default, true
, automatically focuses the first invalid field in the form. This is done for accessibility purposes, as shifting focus announces the first error message on a screen reader; otherwise, screen reader users would have no idea that an error occurred.
On desktop browsers this functionality is helpful to all users, but it can be jarring on mobile for two reasons:
<input>
elements brings up the device's keyboard. For users, it's a bit strange to submit a form then suddenly be editing a textbox.Therefore, unfortunately the accessibility and UX best practices for form validation are at odds. There is no good answer here, but I prefer setting focusInvalid
to false
to avoid the jarring experience for mobile users.
Finally, let's look at how to control the actual display of the form. While this article will not cover design specifics, any good validation mechanism gives sufficient CSS hooks to let savvy designers do their thing.
The CSS used in the validation plugin example is shown below.
input.error {
background: red;
color: white;
}
label.error {
color: red;
}
The validation plugin automatically adds an "error"
class name to erred form elements, as well as their associated error messages (which are <label>
elements). We use those CSS hooks to provide a simple red and white display that draws the user's attention. A more thorough implementation would use an error icon of some sorts to account for color blind users.
And that's it. While this discussion was a bit lengthy, implementing our validation with validate()
took very little actual code. There's much more to the validation plugin than we're able to cover in this post; for more information refer to the validation plugin's documentation.
In most respects, the Kendo UI Validator provides a form validation solution that is very similar to the validation plugin. Both offer robust, highly configurable client-side validation implementations built on top of HTML5.
Let's jump straight in to the implementation.
Since Kendo UI's Validator is built on top of HTML5, it looks for the appropriate attributes on form elements. Therefore, we'll again add the required
attribute to each <input>
and make sure our email address has a type
of "email"
.
<input type="text" name="name" id="name" required>
<input type="email" name="email" id="email" required>
Here's the JavaScript we'll use to setup our validation.
$( "form" ).kendoValidator({
messages: {
required: function( input ) {
var element = input[ 0 ],
label = $( element.form ).find( "[for=" + element.id + "]" );
return label.text().replace( ":", "" ) + " is required.";
},
email: "Please provide a valid email address"
}
});
Kendo UI's Validator gives you several options for specifying error messages. Here we're using the messages
option, which lets you provide static strings, or implement a function to determine the error message to use. For required fields we're building a string that includes the <label>
's text; for email we're using a static string.
You can also specify the error messages in your markup directly, as shown on the following <input>
s.
<input type="text" name="name" id="name" required data-required-msg="Name is required.">
<input type="email" name="email" id="email" required data-required-msg="Email is required."
data-email-msg="Please provide a valid email address.">
With the declarative approach, our JavaScript is now as simple as running $( "form" ).kendoValidator()
. Whether you prefer to provide error messages in markup or in JavaScript is up to you, but Kendo UI gives you the flexibility to do either.
While there are plenty of other configuration options, we don't need any of them to get this basic form up and running. Kendo UI's Validator does not do the focus handling that the jQuery validate plugin does.
Like the jQuery validation plugin, Kendo UI's Validator applies a series of class names to all involved elements. Specifically, the Validator places k-invalid
on invalid form elements and k-tooltip-validation
on the error messages themselves. We'll use those to add our color scheme.
.k-invalid {
background: red;
color: white;
}
.k-tooltip-validation {
color: red;
}
And that's all there is to it! Since this example is simple, we are only scratching the surface of what the Validator can do. For more, see its API or getting started documentation.
The previous two solutions were both built on top of HTML5, so now let's try using HTML5 directly. HTML5 introduced a concept known as constraint validation, which refers to an algorithm the browser runs to determine whether a form should be allowed to submit.
Browser support for constraint validation is actually quite good, but there are three big holdouts: Safari, iOS Safari, and the default Android browser. Since we're focusing on mobile in this article, no iOS or default Android browser support is a showstopper... right?
It actually isn't. The crazy thing is - all of the constraint validation APIs are implemented in WebKit; therefore you can use all of the attributes, JavaScript, and CSS hooks - Safari, iOS Safari, and the stock Android browser will recognize them.
So why are these browsers listed as "Not supported"? While these WebKit-based browsers recognize these features, they do not prevent forms with invalid data from being submitted. Therefore this form will submit regardless of whether you type anything in the <input>
.
<form>
<input required>
<button>Submit</button>
</form>
As we'll see though, we can workaround this. All of this craziness is far easier to explain with a concrete example. Our HTML5 based example is shown below.
Note: The default Android browser is no longer shipped as of Android 4.4, but it'll be with us for a long time as Android < 4.4 distributions make up 98.9% of Android market share. And just in case you need another complication, even though form validation is turned on in Chrome for Android, it is turned off in Android 4.4's Chrome-based WebView. It should be turned on in Chrome 33.
The HTML5 specification includes no mechanism to declaratively provide validation messages. You can set messages in JavaScript using setCustomValidity()
, but that has its own set of issues. Therefore we'll store our messages using custom data-* attributes.
<input type="text" name="name" id="name" required
data-required-message="Name is required.">
<input type="email" name="email" id="email" required
data-required-message="Email is required."
data-type-message="You must provide a valid email address.">
Note: The ability to add a validation message as an attribute was proposed to the spec and was rejected. Firefox supports a vendor prefixed x-moz-errormessage
attribute you can play with, but keep in mind it only works in Firefox.
Because we're not using any framework, we need quite a bit of JavaScript to pull off the same implementation. Let's look at the whole thing first then we'll break down each part.
function validate() {
var errorNode = this.parentNode.querySelector( ".error" ),
span = document.createElement( "span" )
this.classList.remove( "invalid" );
if ( errorNode ) {
errorNode.parentNode.removeChild( errorNode );
}
if ( !this.validity.valid ) {
this.classList.add( "invalid" );
this.parentNode.appendChild( span );
span.classList.add( "error" );
span.innerHTML = this.getAttribute(
this.validity.valueMissing ? "data-required-message" : "data-type-message" );
}
};
var form = document.querySelector( "form" ),
inputs = form.querySelectorAll( "input" );
for ( var i = 0; i < inputs.length; i++ ) {
inputs[ i ].addEventListener( "blur", validate );
inputs[ i ].addEventListener( "invalid", validate );
};
// Turn off the bubbles
form.addEventListener( "invalid", function( event ) {
event.preventDefault();
}, true );
// Support: Safari, iOS Safari, default Android browser
document.querySelector( "form" ).addEventListener( "submit", function( event ) {
if ( !this.checkValidity() ) {
event.preventDefault();
}
});
To start, the code below adds event handlers to make validate()
run every time a blur
or invalid
event occurs on the <input>
s in the form.
for ( var i = 0; i < inputs.length; i++ ) {
inputs[ i ].addEventListener( "blur", validate );
inputs[ i ].addEventListener( "invalid", validate );
};
Within validate()
, we manage the error message itself - which is a <span>
placed directly after each erred <input>
. One nice thing the constraint validation API provides is the validity
property, which contains information about state of the form element. We use validity.valid
to determine whether the field is valid.
But wait, what about Safari, iOS Safari, and the stock Android browser? We're ok here, because the validity
property is one of those APIs that they support and populate correctly. So this code works fine in all modern browsers.
Note: The only major browsers this code does not work well in is IE < 10; it supports neither the constraint validation API, nor classList
.
That covers the error message management, so let's move on to the next bit of code.
// Turn off the bubbles
form.addEventListener( "invalid", function( event ) {
event.preventDefault();
}, true );
If you haven't seen HTML5's form validation before, you might not know which bubbles this code refers to. The image below shows a handful of them in action.
While it would be great to make use of the native display, the WebKit-based browsers do not have an implementation. (Notice that Safari, iOS Safari, and the stock Android browser are missing on the image above.)
Therefore, since we want to build a solution that works on all mobile devices, we have to avoid the bubbles for now. And because we're showing error messages in an alternative location, we need to turn the bubbles off in browsers that do support them. Let's go back to the code.
// Turn off the bubbles
form.addEventListener( "invalid", function( event ) {
event.preventDefault();
}, true );
What this does is listen for invalid
events on the <form>
during the DOM event model's capture phase. The function prevents the default action of the event - which is showing the bubble. If you don't like the capture phase approach, you could alternatively attach an invalid
event listener to each form element and prevent the default action there.
Note: This function runs during the capture phase because the last argument passed to addEventListener()
is set to true
. For more information on the capture phase, see MDN's documentation on addEventListener()
.
Still with me? Don't worry; we're almost at the end. There's only one more thing to do, but - believe it or not - this is the craziest of the code to explain.
// Support: Safari, iOS Safari, default Android browser
document.querySelector( "form" ).addEventListener( "submit", function( event ) {
if ( !this.checkValidity() ) {
event.preventDefault();
}
});
To start, per the spec, a submit
event should not be triggered on a <form>
until it contains valid data. Therefore in fully supporting browsers - IE, Firefox, Chrome, Opera, and Chrome for Android - this submit
handler is not invoked until all data in the <form>
is valid.
That brings us back to our friends Safari, iOS Safari, and the default Android browser. Since these WebKit-based browsers do nothing to prevent invalid submissions, it is up to us prevent them ourselves - which is what this code does.
Luckily for us, because these browsers have all of constraint validation's APIs, we can easily determine the validity of the <form>
using its checkValidity()
method. checkValidity()
does two things for us:
invalid
events on all <input>
s that are invalid. This makes the rest of our logic that listens for invalid
events work in these browsers.<form>
is valid. We use that to determine whether to prevent the default action of the event - which is completing the submission of data to the server.Phew. That's finally it for our form's JavaScript. As you can see, it requires some fun workarounds to get things running in all browsers. Let's move on to our CSS.
If you didn't notice in our JavaScript, the validate()
method adds an invalid
class name to invalid <input>
s and an error
class name to the error messages. In our example we use those class names to apply the same red display.
.invalid {
background: red;
color: white;
}
.error {
color: red;
}
If you've heard of the :invalid
pseudo-selector, you might be wondering why we're not using it here. It's because there's one serious issue with :invalid
: it applies immediately, before a user interacts with the form element. For example this <input>
has a red background
on page load, before the user does anything with it.
<style>
:invalid { background: red; }
</style>
<input required>
Highlighting fields before users interact with them is a known UX bad practice. Both the jQuery validation plugin and Kendo UI's Validator adhere to the UX best practice; :invalid
does not.
You can workaround this behavior, but it's easier to just avoid :invalid
. The CSS selectors level 4 specification has a :user-error
pseudo-class that does what we want, but it has yet to be implemented anywhere yet.
And with that, our HTML5 implementation is finally done. While you have to write quite a bit more code, and work around some crazy browser differences, you can perform form validation using HTML5 without any external dependencies. Have a few drinks nearby though, just in case.
While we have looked at three complete JavaScript solutions, we need to remember that client-side validation solutions never remove the need to validate data on the server.
Because of this, and because client-side validation is always duplicate effort, we can always choose to avoid doing it. For instance, the example below uses neither a JavaScript validation framework, nor HTML5's constraint validation features.
Without going into too many details, the idea here is - as long as your server returns user-friendly error messages, you just build your own mechanism of displaying them to the user.
Of course, there are UX benefits of using inline, client-side inline validation, but implementing a server-side mechanism as a first step makes sense since you need one anyways.
You can build on top of the server-side solution later as a feature.
In this article we looked at four means of implementing client-side validation in mobile applications: the jQuery validation plugin, Kendo UI's Validator, HTML5, and server-side validation alone.
Which should you use? As my colleague Jim Cowart likes to say, the wise answer to any development question is always - it depends. Nevertheless, I'll try to make a few recommendations.
Both the jQuery validation plugin and Kendo UI Validator are fully-featured validation libraries with robust APIs. While these libraries are great for any project, they really shine on non-trivial forms with complex requirements. Because their approaches are so similar, which to use is largely a question of style. If you have a big or complex app, checkout both and see which you prefer.
HTML5's constraint validation APIs brings client-side validation to the browser without any dependencies. However, it also brings a variety of limitations and browser discrepancies. It's great for trivial forms, but if you're using it for something even mildly complex, be prepared to fight with the browser for each customization.
Finally, if you need to get things done quickly, or are concerned about maintenance - don't do client-side validation at all. You give up some UX benefits by doing so, but you can always add client-side validation later on. Since you have to validate data on the server anyways, this makes for an excellent first step when building any web form.
Of course, there are plenty of alternative approaches to validating forms in mobile apps. Do you have a library or approach you prefer? Let us know in the comments.