A List Apart


Illustration by Kevin Cornell

Forward Thinking Form Validation

Form validation has been a finicky business since the web was born. First came the server-side validation error summary. Then we evolved to client-side validation to verify results inline. Now, we have the marching giant that is HTML5 and CSS3: HTML5’s forms chapter offers new input types and attributes that make validation constraints possible. CSS3’s basic UI module provides several pseudo-classes to help us style those validation states and change a field’s appearance based on the user’s actions. Let’s take a look at combining the two to create a CSS-based form validator that has fairly broad browser support.

Article Continues Below

The more we can guide a user on how to complete a form field in real-time, the less likely they are to make mistakes. Look at the CSS3 form validation example in a browser that supports the CSS3 UI pseudo-classes, such as Chrome 4+, Safari 5+ or Opera 9.6+. I use CSS3 UI pseudo-classes and HTML5 form attributes to do CSS-based validation. Let’s see how that works.

CSS3 UI pseudo-classes

The UI module has several pseudo-classes that help to style form fields in various states.

  • valid
  • invalid
  • required
  • optional
  • in-range
  • out-of-range
  • read-only
  • read-write

In the demo above, I use the required, invalid, and valid pseudo-classes to accomplish the CSS validation:

input:focus:required:invalid {
  background: pink url(ico_validation.png) 379px 3px no-repeat;
input:required:valid {
  background-color: #fff;
  background-position: 379px -61px;

Since we only want to denote that a field is invalid once it has focus, we use the focus pseudo-class to trigger the invalid styling. (Naturally, flagging all required fields as invalid from the start would be a poor design choice.)

Bringing focus to an invalid required field triggers the style to show the exclamation graphic, which alerts the user that something needs to be entered. Once the field’s validation constraints are satisfied, the valid pseudo-class triggers. Now, we remove the focus pseudo-class so that the green tick that indicates a correct field will remain.

All the pseudo-classes listed above are self explanatory. The in-range and out-of-range pseudo-classes should be used in conjunction with the min and max attributes, whether on a range input, a number field, or any other types that accept those attributes. For example, if a user enters a value that is out-of-range, we can use the pseudo-class to change the styling to reflect that state; likewise, we can do the same for in-range values.

Only Opera supports the range pseudo-classes at the moment. Other browsers will follow soon.

Additional types and attributes to help us

HTML5 forms also introduce new input types such as email, url, and number. For example, email only triggers the valid pseudo-class when the user enters a valid e-mail address; the same is true for number and url. Url constraint validation differs among browsers. In Opera, typing “http://” flags the url field as valid. In Chrome typing “http://w” flags it valid, while simply typing “http:” in Safari flags a url as valid.

There are also a few attributes which help validation such as placeholder, required, maxlength, pattern, min, max, and step:

<input id="postcode" name="postcode" type="number" min="1001" max="8000"
maxlength="4" required />

The postcode field uses the new number type and a few of the new attributes. In Australia, a postcode can only be four digits so we set the maxlength attribute to restrict it. We also want to restrict how high or low the postcode can be so we use min and max attributes to set boundaries. The required attribute is self explanatory.

We can use the step attribute to further restrict a field with min and max. By default, step is set to one, so any number between the min and max values incremented by at least one validates. Changing step to 100 validates between the set range if the value the user entered is an increment of 100. For example, if I set the step attribute to 100 on my postcode field, 1001 will be a valid entry, as will 1101, 1201, 1301, etc.

Find the pattern

To trigger the invalid pseudo-class on more specific conditions, such as a rudimentary phone number, we can use the pattern attribute which allows us to apply a regular expression to the field.

<input type="tel" id="tel" name="tel" pattern="d{10}" placeholder=
"Please enter a ten digit phone number" required />

The regular expression above is a simple one. It says, “I will only accept exactly ten digits and nothing else.” That way, the field will always be invalid until the regular expression requirements are met. Notice how I have used the placeholder attribute to give the user a small hint.

We can really push the power of the pattern attribute by applying a more complex regular expression as I do on the password field:

<input id="password" name="password" type="password" title="Minimum 8
characters, one number, one uppercase and one lowercase letter" required
(?=.*[a-z]).*" />

Since we have specific conditions that restrict what the user can enter, forcing them to create a more secure password, we set up a complex regular expression as shown above. The password must be at least eight characters long, contain one number, one lowercase letter, and one uppercase letter.

To help a user meet these conditions, we use the title attribute to help them understand exactly what the requirements are. We don’t use the placeholder attribute here, as it needs more explanation and placeholder should only be used for short hints.

Adding helpful hints

If the user never hovers over the field, and instead tabs through them, they may never notice the extra instructions in the title attribute. You may notice that on the phone, postcode, and password fields, a helpful hint appears when a field needs extra instructions.

<input id="password" type="password"  /><p class="validation01">
  <span class="invalid">Minimum 8 characters, one number, one uppercase 
letter and one lowercase letter</span>
  <span class="valid">Your password meets our requirements, thank you.

The markup above has an extra container with both the invalid and valid hint boxes. This way, when the field is invalid, it will contain the extra information to help the user. When they get it right, our message and the green tick reassures them that they have filled it out correctly.

.validation01 {
  background: red;
  color: #fff;
  display: none;
  font-size: 12px;
  padding: 3px;
  position: absolute;
  right: -110px;
  text-align: center;
  top: 0;
  width: 100px;
input:focus + .validation01 {
  display: block;
input:focus:required:valid + .validation01 {
  background: green;
input:focus:required:valid + .validation01 .invalid {
  display: none;
input:focus:required:invalid + .validation01 .valid {
  display: none;

To show or hide the helpful hint, depending on what state the field is in, we can target the field by chaining the pseudo-classes, using the adjacent sibling combinator to target the correct hint. Once the field has been filled out correctly, the background changes to green and the valid message displays.

UX concerns with the current approach

There is one main gripe with how the invalid pseudo-class currently works when a field is required and has additional conditions that must be satisfied—for example, when a field is required and its type is email. Because the field is always invalid until its conditions are met, it will pick up the invalid styles. In this case, the field would be instantly invalid, and marked red with errors even before the user has entered anything. That᾿s why we use the focus pseudo-class to show the invalid styles only when a field is in focus. This isn’t optimal: if a user moves away from the field without meeting its validation requirements, the field will not indicate that something is wrong until the user brings focus back to it.

A proposed solution to this would be to add the indeterminate pseudo-class available on radio and checkbox inputs. Technically, a field that has more conditions than being required when it’s empty is neither valid nor invalid but rather indeterminate. This idea would fix the instant invalid issue and allows us to optimally style the field depending on its validation state.

Additionally, we can accomplish some pretty comprehensive functionality without JavaScript. We can tell what state a field is in, if it’s required, tell it to conform to a certain pattern with regular expressions, specify minimum and maximum values, and much more. But what if that’s not enough? What if we want to take it further? Well we’re in luck as the HTML5 forms chapter also specifies the constraint validation API.

Constraint validation API

Alongside all the new attributes, input types, and CSS3 pseudo-classes, the HTML5 forms chapter also specifies a simple JavaScript API that allows us to extend form validation capabilities further with some handy built-in methods, attributes, and events. Take a look at the updated demo, which hooks into the constraints validation API.

Each form field has a new attribute called validity. The validity attribute returns a ValidityState object which represents an element’s current validity state(s). The ValidityState object contains several Boolean attributes which identify which validity state the current element is in. Basically, they’re a series of true/false answers that tell a developer exactly what is wrong with the field:

  •   valueMissing
      This attribute returns true if a required element is empty.
  •   typeMismatch
      This value applies to all the new type attributes. For example, if an email value is incorrect, this attribute returns true.
  •   patternMismatch
      When an element contains the pattern attribute and doesn’t conform to the regular expression conditions, this attribute will return true.
  •   tooLong
      When any element surpasses its maxlength property this attribute will return true.
  •   rangeUnderflow and rangeOverflow
      If an element’s min or max attributes are above or below the specified values, this attribute will return true.
  •   stepMismatch
      When an element with the step attribute doesn’t conform to the required step value, this attribute returns true.
  •   valid
      If any of the values listed above return true, this attribute returns false to indicate the field is invalid. Otherwise, if all the conditions are met, it will return true.

And there’s more

The invalid event is another handy feature. It will be invoked by the field when it is still invalid. So we can attach behaviour to it and, in our case, change the field(s) styling to reflect their current state.

Additionally, the checkValidity() method can be executed on either an individual field or the form as a whole, and returns true or false. Executing the method will also programmatically fire the invalid event for all invalid fields, or, if executed on a single field, only for that element.

Take me to the demo

Let’s take our previous demo and enhance it with the constraint validation API. Taking what we’ve learned from Luke Wroblewski’s Inline Validation in Web Forms and our own findings, we can apply these ideas to our form to create the optimal inline validation experience.

The first thing we can fix is the instant error styling of an invalid field. Rather than instantly styling the field to indicate the user hasn’t met the requirements, we wait until they move away from the field to show any issues.

If they meet the requirements while the field is still in focus, we let our user know instantly that the field is correct. We do this by attaching the input event to check to see if the field is valid. When it is, we update the styles to reflect it straight away.

If a field has incorrect values, and the user moves to the next field, the blur event will check the field’s validity and then apply the error styles to let the user know something is wrong. It will retain the error styling until the requirements are met.

What about older browsers?

All the topics discussed are fairly new and browser support, while good, wouldn’t cut it in a real-world production environment where we must support older browsers. That’s where the script I wrote comes in handy.

For browsers that don’t support the HTML5 forms chapter and the constraint validation API, the script emulates that functionality. For browsers that support these features, the script detects support and hooks into the native functionality. Let’s take a look at the further updated demo with the new script added in. Try it in IE or Firefox to see it work like the native supporting browsers do.

Browser support

This script has been tested and works in the following browsers:

  • IE6+
  • Firefox 1+—FF4 will have native support soon.
  • Chrome 4+—Native support.
  • Safari 3.2+—Safari 5 has native support.
  • Opera 9.6+—Native support.

The following features are emulated in the script:

  • Each field has the validity object which is live and will let you know the field’s current state.
  • The checkValidity() method is available and indicates whether the form or a specific element is invalid.
  • The pattern, placeholder, required, min, max, and step input attributes are supported.
  • The placeholder and required attributes are supported for textareas.
  • The required attribute is supported for select inputs.
  • The email and url input types will check against a built-in regular expression and will be invalid until they conform.

Validation aplenty!

Browser support for HTML5 forms and the CSS3 UI module is starting to improve. Opera 9 lead the way by implementing Web Forms 2.0 before it merged into the HTML5 forms chapter, but it has only supported the CSS3 UI module since version 9.6. Chrome has had support since version 4, Safari recently shipped it in version 5, Firefox is due to add support in a forthcoming beta of version 4, and IE9, if they continue their progress, should also have support in one of their preview builds.

We can do some amazing things with the new modules and chapters from CSS3 and HTML5 respectively. As browser support improves, these sorts of techniques become a viable option that can cater to the simple and the complex nature of form validation.

About the Author

37 Reader Comments

Load Comments