Building HTML5 Form Validation Bubble Replacements
Join the DZone community and get the full member experience.
Join For FreeWritten by TJ VanToll
I’ve written and about HTML5 form validation over the last few years, and one of the most common questions I get is about bubbles. By bubbles I mean the UI controls browsers display validation errors in. Below you can see Chrome’s, Firefox’s, and IE’s implementations:
For whatever reason, we developers (or more likely our designer colleagues) have a deep-seated desire to style these things. But unfortunately we can’t, as zero browsers provide styling hooks that target these bubbles. Chromeused to provide a series of vendor prefixed pseudo-elements (::-webkit-validation-bubble-*
), but theywere removed in Chrome 28.
So what’s a developer to do? Well, although browsers don’t allow you to customize their bubbles, the constraint validation spec does allow you to suppress the browser’s bubble UI and build your own. The rest of the article shows you how to do just that.
Warning: Don’t go down the path of building a bubble replacement lightly. With the default bubbles you get some pretty complex functionality for free, such as positioning and accessibility. With custom bubbles you have to address those concerns yourself (or use a library that does).
Suppressing the Default Bubbles
The first step to building a custom UI is suppressing the native bubbles. You do that by listening to each form control’s invalid
event and preventing its default behavior. For example, the following form uses no validation bubbles because of the event.preventDefault()
call in the invalid
event handler.
<form> <input required> <button>Submit</button> </form> <script> document.querySelector( "input" ).addEventListener( "invalid", function( event ) { event.preventDefault(); }); </script>
The invalid
event does not bubble (no pun intended), so if you want to prevent the validation bubbles on multiple elements you must attach a capture-phase listener.
If you’re confused on the difference between the bubbling and capturing phases of DOM events, check out this MDN article for a pretty good explanation.
The following code prevents bubbles on both inputs with a single invalid
event listener on the parent <form>
element:
<form> <input required> <input required> <button>Submit</button> </form> <script> document.querySelector( "form" ) .addEventListener( "invalid", function( event ) { event.preventDefault(); }, true ); </script>
You can use this approach to remove the browser’s UI for form validation, but once you do so, you have build something custom to replace it.
Building alternative UIs
There are countless ways of displaying form validation errors, and none of them are necessarily right or wrong. (Ok, there are some wrong ways of doing this.) Let’s look at a few common approaches that you may want to take. For each, we’ll use the very simple name and email address form below. We’ll also use some simple CSS to make this form look halfway decent.
<form> <div> <label for="name">Name:</label> <input id="name" name="name" required> </div> <div> <label for="email">Email:</label> <input id="email" name="email" type="email" required> </div> <div> <button>Submit</button> </div> </form>
One important note before we get started: all of these UIs only work in Internet Explorer version 10 and up, as theconstraint validation API is not available in older versions. If you need to support old IE and still want to use HTML5 form validation, check out this slide deck in which I outline some options for doing so.
Alternative UI #1: List of messages
A common way of showing validation errors is in a box on the top of the screen, and this behavior is relatively easy to build with the HTML5 form validation APIs. Before we get into the code, here’s what it looks like in action.
function replaceValidationUI( form ) { // Suppress the default bubbles form.addEventListener( "invalid", function( event ) { event.preventDefault(); }, true ); // Support Safari, iOS Safari, and the Android browser—each of which do not prevent // form submissions by default form.addEventListener( "submit", function( event ) { if ( !this.checkValidity() ) { event.preventDefault(); } }); // Add a container to hold error messages form.insertAdjacentHTML( "afterbegin", "<ul class='error-messages'></ul>" ); var submitButton = form.querySelector( "button:not([type=button]), input[type=submit]" ); submitButton.addEventListener( "click", function( event ) { var invalidFields = form.querySelectorAll( ":invalid" ), listHtml = "", errorMessages = form.querySelector( ".error-messages" ), label; for ( var i = 0; i < invalidFields.length; i++ ) { label = form.querySelector( "label[for=" + invalidFields[ i ].id + "]" ); listHtml += "<li>" + label.innerHTML + " " + invalidFields[ i ].validationMessage + "</li>"; } // Update the list with the new error messages errorMessages.innerHTML = listHtml; // If there are errors, give focus to the first invalid field and show // the error messages container if ( invalidFields.length > 0 ) { invalidFields[ 0 ].focus(); errorMessages.style.display = "block"; } }); } // Replace the validation UI for all forms var forms = document.querySelectorAll( "form" ); for ( var i = 0; i < forms.length; i++ ) { replaceValidationUI( forms[ i ] ); }
This example assumes that each form field has a corresponding <label>
, where the id
attribute of the form field matches the for
attribute of the <label>
. You may want to tweak the code that builds the messages themselves to match your applications, but other than that this should be something can simply drop in.
Alternative UI #2: Messages under fields
Sometimes, instead of showing a list of messages on the top of the screen you want to associate a message with its corresponding field. Here’s a UI that does that.
click
event handler.
function replaceValidationUI( form ) { // Suppress the default bubbles form.addEventListener( "invalid", function( event ) { event.preventDefault(); }, true ); // Support Safari, iOS Safari, and the Android browser—each of which do not prevent // form submissions by default form.addEventListener( "submit", function( event ) { if ( !this.checkValidity() ) { event.preventDefault(); } }); var submitButton = form.querySelector( "button:not([type=button]), input[type=submit]" ); submitButton.addEventListener( "click", function( event ) { var invalidFields = form.querySelectorAll( ":invalid" ), errorMessages = form.querySelectorAll( ".error-message" ), parent; // Remove any existing messages for ( var i = 0; i < errorMessages.length; i++ ) { errorMessages[ i ].parentNode.removeChild( errorMessages[ i ] ); } for ( var i = 0; i < invalidFields.length; i++ ) { parent = invalidFields[ i ].parentNode; parent.insertAdjacentHTML( "beforeend", "<div class='error-message'>" + invalidFields[ i ].validationMessage + "</div>" ); } // If there are errors, give focus to the first invalid field if ( invalidFields.length > 0 ) { invalidFields[ 0 ].focus(); } }); } // Replace the validation UI for all forms var forms = document.querySelectorAll( "form" ); for ( var i = 0; i < forms.length; i++ ) { replaceValidationUI( forms[ i ] ); }
Alternative UI #3: Replacement bubbles
The last UI I’ll present is a way of mimicking the browser’s validation bubble with a completely custom (and styleable) bubble built with JavaScript. Here’s the implementation in action.
$( "form" ).each(function() { var form = this; // Suppress the default bubbles form.addEventListener( "invalid", function( event ) { event.preventDefault(); }, true ); // Support Safari, iOS Safari, and the Android browser—each of which do not prevent // form submissions by default $( form ).on( "submit", function( event ) { if ( !this.checkValidity() ) { event.preventDefault(); } }); $( "input, select, textarea", form ) // Destroy the tooltip on blur if the field contains valid data .on( "blur", function() { var field = $( this ); if ( field.data( "kendoTooltip" ) ) { if ( this.validity.valid ) { field.kendoTooltip( "destroy" ); } else { field.kendoTooltip( "hide" ); } } }) // Show the tooltip on focus .on( "focus", function() { var field = $( this ); if ( field.data( "kendoTooltip" ) ) { field.kendoTooltip( "show" ); } }); $( "button:not([type=button]), input[type=submit]", form ).on( "click", function( event ) { // Destroy any tooltips from previous runs $( "input, select, textarea", form ).each( function() { var field = $( this ); if ( field.data( "kendoTooltip" ) ) { field.kendoTooltip( "destroy" ); } }); // Add a tooltip to each invalid field var invalidFields = $( ":invalid", form ).each(function() { var field = $( this ).kendoTooltip({ content: function() { return field[ 0 ].validationMessage; } }); }); // If there are errors, give focus to the first invalid field invalidFields.first().trigger( "focus" ).eq( 0 ).focus(); }); });
Although replacing the validation bubbles requires an unfortunate amount of code, this does come fairly close to replicating the browser’s implementation. The difference being that the JavaScript implementation is far more customizable, as you can change it to your heart’s desire. For instance, if you need to add some pink, green, and Comic Sans to your bubbles, you totally can now:
The Kendo UI tooltip widget is one of the 25+ widgets available in Kendo UI Core, the free and open source distribution of Kendo UI. So you can use this code today without worrying about licensing restrictions — or paying money. You candownload the Kendo UI core source directly, use our CDN, or grab the library from Bower (
bower install kendo-ui-core
).
Wrapping Up
Although you cannot style the browser’s validation bubbles, you can suppress them and build whatever UI you’d like. Feel free to try and alter the approaches shown in this article to meet your needs. If you have any other approaches you’ve used in the past feel free to share them in the comments.
Published at DZone with permission of , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments