HTML5 Form Validation

A few months ago Sandeep introduced us to the HTML Constraint API, showing how we can use the new HTML5 input types and attributes to validate our forms in the browser with minimal JavaScript. Today I’m going to walk you through validating a simple booking form by using the Constraint API, and keep an eye on how you can make sure your forms stays accessible too.

To recap—or in case you haven’t read Sandeep’s article—with the dawn of HTML5, a raft of new input types and attributes were added to <input> tags that allow the browsers themselves to perform the client-side validation for us: no JavaScript required. To start using the new input types and attributes, you don't really need to do anything other than start using the new input types and attributes.

Strictly speaking, you should make sure you're using the HTML5 DOCTYPE, otherwise you'll run into HTML validation errors. But the great thing about them is that they all degrade gracefully. So if an older browser doesn't support them, the fact that they're in the HTML won't 'break' anything, they’ll just be rendered as an <input type=”text”>.

NB While client-side form validation is great for enhancing user experience—fast, instant feedback to the user without making a round trip to the server—you will still need to validate any data submitted on the server, too.

Let's walk through an example of how we can validate a form using only the browser's built in validation. Take this simple booking form:

<form action="" method="post">
    <fieldset>
        <legend>Booking Details</legend>
        <div>
            <label for="name">Name (required):</label>
            <input type="text" id="name" name="name" value="" aria-describedby="name-format">
            <span id="name-format" class="help">Format: firstname lastname</span>
        </div>
        <div>
            <label for="email">Email (required):</label>
            <input type="text" id="email" name="email" value="">
        </div>
        <div>
            <label>Website:</label>
            <input type="text" id="website" name="website" value="">
        </div>
        <div>
            <label for="numTickets"><abbr title="Number">No.</abbr> of Tickets (required):</label>
            <input type="text" id="numTickets" name="numTickets" value="">
        </div>
        <div class="submit">
            <input type="submit" value="Submit">
        </div>
    </fieldset>
</form>    

http://jsfiddle.net/ianoxley/aZ9r8/1/

Note that each input field has an associated <label> tag. The for attribute for the label tag matches up with the id attribute of the associated input tag. This keeps our HTML semantic, with the labels helping to give meaning to the input controls. It also means that if you click the label, the associated input tag receives the focus.

They also help with accessibility, as the text in the label will be read out to screen reader users: it can therefore be useful to indicate required fields by adding ‘required’ to the label text, as I’ve done above.

I’m also using the WAI ARIA aria-describedby attribute, which helps inform users of the format in which they should enter their name.

<label for="name">Name (required):</label>
<input type="text" id="name" name="name" value="" aria-describedby="name-format">
 <span id="name-format" class="help">Format: firstname lastname</span>

This is shown when the input field it describes receives focus, providing context-sensitive help:

.help {
    display:none;
    font-size:90%;
}
input:focus + .help {
    display:inline-block;
}

To validate this form we'll need to:

  1. Make sure the required fields have been completed
  2. Make sure what's entered for 'Name' is name-like
  3. Make sure the email address looks like an email address
  4. Check that if a website URL has been provided, it looks like a URL
  5. Make sure a number has been entered in 'Number of tickets'

Required fields

Validating required fields is as easy as adding the required attribute to each <input> tag that must be completed, namely Name, Email, and Number of Tickets. For example, Name now looks like this:

<div>
        <label for="name">Name:</label>
        <input id="name" name="name" value="" aria-describedby="name-format" required>
        <span id="name-format" class="help">Format: firstname lastname</span>
    </div>

If we try to submit this form now without completing any of the required fields, the browser will notify us:

Required attribute

Did you notice how we no longer have ‘required’ in the required fields <label> tags anymore? This is because most screen readers will pick up the required attribute now, so if we leave ‘required’ in the label tag screen reader users will have ‘required’ read to them twice, which they could find rather annoying.

A word of warning though: not all browsers implement the required attribute accessibly, so it might not be picked up correctly in certain browser / screen reader combinations. As such, current best practice recommends supplementing the required attribute with the aria-required=”true” attribute:

<input id="name" name="name" value="" aria-describedby="name-format" required aria-required=”true”/>

Validating the data

Now that our users get prompted to complete required fields, we need to make sure that the data they submit is in the format we require.

We'll want the 'Name' field to be submitted in the format 'Firstname Lastname', and to only contain letters and a space (NB in real world scenarios, you might need to take account of other locales – this example has been kept simple deliberately). We can achieve this by adding a pattern attribute to the 'Name' field, setting it's value to the regular expression we want the data to be compared against:

<input id="name" name="name" value="" aria-describedby="name-format" required aria-required=”true” pattern="[A-Za-z-0-9]+\s[A-Za-z-'0-9]+">

When using the pattern attribute, the ^ and $ for the start and end of the regular expression are implied and don't need to be included.

You can help the user by including a title attribute that tells them the format you require:

<input id="name" name="name" value="" aria-describedby="name-format" required aria-required=”true” pattern="[A-Za-z-0-9]+\s[A-Za-z-'0-9]+"  title="firstname lastname">

The text in the title attribute is then appended to the built-in validation message:

Pattern attribute

Note that some screen reader / browser combinations might lead to the title attribute being read out in addition to the aria-describedby text, so watch out for this e.g. I found that using NVDA with IE10 caused the title attribute and the aria-describedby element's text to be read out, but using NVDA with Chrome and Firefox didn’t exhibit this behaviour. Only the aria-describedby text was read. Later on we’ll revisit this and show you one solution using CSS3.

Validating emails, URLs, and numbers

To make sure our user enters the right data in the email, website, and number of tickets fields, we can use some of the new input types added in HTML5:

<input type="email" id="email" name="email" required>
…
<input type="url" id="url" name="url">
…
<input type="number" id="numTickets" name="numTickets" required>

By specifying the appropriate type, our browser will validate the data for us and make sure we've got an email address in the email field, a URL in the website field, and a number in the number of tickets field.

type email

type url

Note too that the type attribute is no longer a required attribute itself. If you don't specify it the input tag will implicitly be of type="text".

Suppose we wanted to restrict the number of tickets each person could buy? We can do this by using the max and min attributes:

<input type="number" min="1" max="4" id="numTickets" name="numTickets">

If the user enters anything less than 1 or greater than 4, they'll be prompted to enter a number in the permitted range.

Max attribute

Using CSS to highlight required fields and invalid data

In tandem with the new input types and attributes provided by HTML5, CSS3 gives us some new pseudo-classes we can use to provide visual clues to the user as to which form fields are required, which are optional, and which contain validation errors.

Required fields can use the :required pseudo-class:

input:required {
    background:hsl(180, 50%, 90%);
    border:1px solid #999;
}

Optional fields can use the :optional pseudo-class:

input:optional {
    background:hsl(300, 50%, 90%);
    border:1px dotted hsl(180, 50%, 90%);
}

The success or failure of form validation can be signified to the user through the use of the :valid, :invalid, :in-range, and :out-of-range pseudo-classes:

input:valid,
input:in-range {
    background:hsl(120, 50%, 90%);
    border-color:hsl(120, 50%, 50%);
}

input:invalid,
input:out-of-range {
    border-color:hsl(0, 50%, 50%);
    background:hsl(0, 50%, 90%);
}

CSS3 pseudo-classes

Remember earlier when I mentioned certain screen reader / browser combinations can lead to the title attribute being read aloud in addition to the aria-describedby element's text? Well, one way around this is to remove the title attribute from the input element, and make use of the CSS3 :invalid pseudo class to show the aria-describedby text:

input:focus + .help,
input:invalid + .help {
    display:inline-block;
}

Now, in addition to showing the help text when the input field receives focus, we'll also show the help text when the input field's value is invalid.

After making all these changes our HTML now looks like this:

<form action="" method="post">
    <fieldset>
        <legend>Booking Details</legend>
        <div>
            <label for="name">Name:</label>
            <input id="name" name="name" value="" required  pattern="[A-Za-z-0-9]+\s[A-Za-z-'0-9]+" aria-required="true" aria-describedby="name-format">
            <span id="name-format" class="help">Format: firstname lastname</span>
        </div>
        <div>
            <label for="email">Email:</label>
            <input type="email" id="email" name="email" value="" required aria-required="true">
        </div>
        <div>
            <label for="website">Website:</label>
            <input type="url" id="website" name="website" value="">
        </div>
        <div>
            <label for="numTickets"><abbr title="Number">No.</abbr> of Tickets:</label>
            <input type="number" id="numTickets" name="numTickets" value="" required aria-required="true" min="1" max="4">
        </div>
        <div class="submit">
            <input type="submit" value="Submit">
        </div>
    </fieldset>
</form>

http://jsfiddle.net/ianoxley/VY8KU/

Disabling browser-based validation

You can disable the built-in browser validation by adding a novalidate attribute to your <form> tag:

<form novalidate>
    …
</form>

http://jsfiddle.net/ianoxley/9C2JD/

Cross-browser?

The good news is that HTML form validation is supported by all the latest desktop browsers, and most mobile browsers. The bad news is that it is only partially supported in Safari, and isn't supported at all on iOS Safari, or the default Android browser. If you need to support older versions of IE prior to IE10 you won't find any of those support form validation either.

So, what can you do if you have to support browsers that don't have support for form validation yet?

One option is to not do anything and rely on your server-side validation only. This would require no extra work on your part, but would the UX for those using unsupported browsers be satisfactory?

Another option would be to continue to use solely JavaScript for your client-side validation, and not add any of the new features discussed above.

A third approach is to use JavaScript to detect whether the browser supports form validation, use it if it does, and fall back to JavaScript-based validation if it doesn’t. Libraries such as Modernizr can help with HTML5 feature detection, but you can always write your own code if you don't want to include another JavaScript library:

// Without Moderizr
var inputElem = document.createElement('input');
if (!('required' in inputElem)) {
    // JavaScript form validation
  }

...

// With Modernizr
if (!Modernizr.input.required) {
    // JavaScript form validation
}

Conclusion

In this article we've walked through applying HTML5 form validation to a booking form client-side without using any JavaScript, and pointed out some accessibility issues to be wary of.

We then looked at how new CSS3 pseudo-classes can be used to give visual cues to the user as to which fields are required and options, and which contain valid or invalid data.

Finally we covered how to disable HTML form validation, and detect whether form validation was available or not in case we have to support browsers that are yet to support this feature.

Let me know what you think of all this in the comments.

Teach yourself HTML5 in a single weekend. SitePoint’s latest HTML5 guide, Jump Start HTML5, is free to read with a Learnable trial.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Ron Nitzsche

    Thanks a lot for this in-depth article about html5 form validation :)

  • Stefan Cuculeac

    Hello. Can you tell me how to change input text to other language?

  • http://rich.sturim.me/ rsturim

    Nice work Ian, thanks for that useful summary. Can you help describe a use-case when we’d want to apply the “novalidate” attritube?