CSS3 Pseudo-Classes and HTML5 Forms

by .

Hello! I’m Doctor Peter and I’m here to treat you with a dose of complementary CSS3. Don’t worry, this won’t hurt a bit.

Contrary to what HTML5 Please and the W3C would have you believe, CSS3 is not part of HTML5. “But this is HTML5 Doctor,” I imagine you saying. “Why are you talking about CSS3 here?” Well, I want to talk about a very specific part of CSS3, one that works in perfect tandem with HTML5, specifically with the new form functions that are available.

One of the killer features that HTML5 introduces is client-side form validation without using JavaScript. Now, I know the other doctors haven’t discussed this yet, and as I’m just a locum, you might feel as if I’m putting the cart before the horse (#mixedmetaphors), but I’m hoping to explain just enough to let you know how it works, and then your regular doctors can provide the details at a later date.

A Recap of Pseudo-Classes

A pseudo-class is information about an element that’s in the document tree but not available through any specified attributes. For example, the :first-child pseudo-class applies to elements which are the first child of their parents. You can get that information from the DOM tree, but there’s no first-child attribute.

In CSS2.1, there were a handful of pseudo-classes available, notably the link states (:link, :visited) and those of user actions (:active, :hover). In CSS3, that handful becomes a basketful, with pseudo-classes of structure (:nth-child, :nth-of-type), UI element (:enabled, :checked), and the ones we’ll look at now, of form validation.

Form Validation without JavaScript

So as I mentioned at the beginning, HTML5 introduces client-side form validation without the need for JavaScript. When you attempt to submit a form, your browser will validate all of the fields and return an error if any of the fields is incorrect. This will happen mostly automagically — provided your browser has the capability — and will look something like what’s shown in Figure 1:

Form with an invalid field and associated error message
Figure 1: A form validation error message

Those error messages are browser and OS specific, and hard to modify (which I documented on my blog), but you can change the way the errors appear on the elements themselves with the new validation pseudo-classes, which are part of the CSS3 Basic UI module.

I’ll cover current browser support as we go through the article, but in a nutshell:

  • Firefox and IE10 support some of it.
  • Opera and Chrome support all of it.
  • Safari supports all of it too, but form validation is disabled by default.

That being the case, I’d recommend using Chrome, Opera, or Safari to view the examples I’ll be showing you.

If you want to keep up to date with browser support for form validation, I suggest keeping an eye on When can I use.

So with all of the boring technical guff out of the way, let’s get on with the interesting technical guff and take a look at some examples of form validation.

Required and Optional Elements

One of the most common patterns of validation is that of mandatory (that is, required) values — the fields that the user must complete in order to progress. To mark a form element as mandatory, you need only add the required attribute to it:

<input type="text" required>

If you want to apply styles to this in order to show that requirement, you can use the new :required pseudo-class, which applies rules to any matched element which has the required attribute. For example, you may want to put an asterisk after the text of the label:

input:required + label::after { content: "*"; }

The exact rules you use depends on how you’ve marked up your document. The rule in this example is based on the markup having this structure:

<input type="text" required id="foo">
<label for="foo">Foo</label>

The opposite of required is, of course, optional. And wouldn’t you know it, we have a pseudo-class for that too, logically named :optional. It affects any form element that doesn’t have a required attribute. For example, you may want to make those have a lighter border:

input:optional { border-color: silver; }

You can see this in action in Figure 2, or take a look at a live example.

Required and optional form fields
Figure 2: Input fields styled with :required (top) and :optional (bottom)

Valid and Invalid Elements

There are other types of validation besides simply “required” or “optional”. You can use pattern matching, such as email address validation:

<input type="email">

If the user enters anything into the field, it has to match the pattern of an email address, or else it will be marked as invalid. (Of course, if it’s not required, then it can be left blank.)

You can style valid or invalid form elements using pseudo-classes called — wait for it — :valid and :invalid. Maybe you want add a symbol depending on the validation status:

input:invalid + label::after { content: ' ⨉'; }
input:valid + label::after { content: ' ✓'; }

Note that these will take effect as soon as the page loads, so you’ll probably want to use JavaScript to apply them only after submission, perhaps by adding a class to the form with JavaScript:

document.forms[].addEventListener('submit', function(e) {
  // Your fallback form validation function

The exact script you use will vary, but however you do it, you can use that class to style the form elements, like so:

.submitted input:invalid + label::after { content: ' ⨉'; }

Figure 3 shows the output of this, and you can also see the live example.

Valid and invalid form fields
Figure 3: Showing the use of the :valid (top) and :invalid (bottom) pseudo-classes

Two extra things to note about this example: first, in order to use my own JavaScript, I’ve prevented the form from automatically validating by using the novalidate attribute on the submit button; and second, Firefox puts a coloured glow around elements depending on their validation state, which I’ve over-ruled by using their proprietary :-moz-ui-invalid selector. View the source of the demo to see how these are used.

Number Ranges

Some of the new input types, such as number, allow a range of values using the min and max attributes, like so:

<input type="number" max="10" min="1">

Although form controls will usually prevent the user from entering a value outside of this range, there may be occasions (such as when JavaScript is used to provide values) where the value provided to the input does exceed the range:

<input type="number" max="10" min="1" value="11">

When this happens, you can use the :out-of-range pseudo-class for your styling:

input[type='number']:out-of-range { border-color: red; }

And, as you’d expect, there’s a counterpart known by the name of :in-range:

input[type='number']:in-range { border-color: green; }

In Figure 4 you can see these two pseudo-classes at work, and once more there’s also a live example to view.

Out-of-range and in-range form fields
Figure 4: The :out-of-range (top) and :in-range (bottom) pseudo-classes applied to a range input

Reading and Writing

You may have a form field that’s pre-filled with a value that you don’t want the user to be able to edit, such as terms and conditions in a textarea. If that’s the case, you can add the readonly attribute:

<textarea readonly>Lorem ipsum</textarea>

You can style elements which have the readonly attribute applied, using…can you guess? You are correct, imagined reader: the :read-only pseudo-class. You could combine it with the user-select CSS property to prevent the user from being able to select the text (in the example, I’ve also changed the border style to make it more obvious):

textarea:read-only { user-select: none; }

And if you should need it, there is of course a :read-write pseudo-class for elements which don’t have the attribute applied:

textarea:read-write { user-select: text; }

You can see the results in Figure 5, and as before there’s a live example too.

Read-only and read-write form fields
Figure 5: The textarea on the left is styled using :read-write, and the one on the right, with :read-only

Firefox and IE10 have actually implemented the readonly attribute, but don’t yet have support for these pseudo-classes.

Browser Support

This table lists support for HTML5 Form Validation in browsers at the time of writing:

Browser support for HTML5 Form Validation
Browser/Attribute Chrome Firefox IE Opera Safari
:optional / :required 10.0 4.0 10.0 10.0 5.0
:invalid / :valid 10.0 4.0 n/a 10.0 5.0
:in-range / :out-of-range 10.0 n/a n/a 10.0 5.0
:read-only / :read-write 10.0 n/a n/a 10.0 5.0

That’s All from Me

I hope I’ve given you enough to whet your appetite for finding out more about HTML5 Form Validation. There’s plenty more to it, including a full JavaScript API (you can read about this in the HTML5 spec, but ).

Thanks for your time, and thanks to the Doctors for this opportunity.

38 Responses on the article “CSS3 Pseudo-Classes and HTML5 Forms”

  • Stuart Malone says:

    You say “Safari supports all of it too, but form validation is disabled by default.” That kind of implies that Safari’s form validation can be turned on, but searching the net I wasn’t able to come up with a technique for turning form validation on in Safari. If you know how to do this, I’d love for you to share.

  • Safari has no native feedback mechanism for constraint validation, but it supports pretty much the entire API; so while you can’t ‘turn on’ validation, you can write your own script pretty easily.

  • SO I see what you mean, sorry; when I said ‘disabled by default’ I should have made it clearer that it’s actually non-existent.

  • Andy Walpole says:

    I’m not really sure why anybody would use the :read-only pseduo-class. After all, forms are there for the user to fill in and submit – not to look at.

    But :out-of-range and :in-range will probably come in handy in the future

  • Robson Sobral says:

    I think should be good to remember that some pseudo selectors can be emulated using attribute selectors. For example, input[required] and input[readonly].

  • Jason Neel says:

    What would be the use case for using the readonly attribute as opposed to the disabled attribute, and vice versa, on form fields?

  • Robson Sobral says:

    @jason, disabled fields aren’t submitted, but readonly does.

  • Jason Neel says:

    @Robson Ahh, gotcha. Thanks. That makes sense, and I can see where that would be very helpful in certain circumstances. Cool

  • Robson Sobral says:

    @jason, just never, ever forget to validate even the readonly data on the server side.

  • Alejandro Moreno says:

    Thanks for the article. But why do you have double colons in all your instances of label::after?

  • Andy Walpole says:


    In CSS3 the before and before and after pseudo-elements have a double colon instead of a single so as to differentiate them from pseudo-classes. Pseudo-elements with double colons will not working in IE versions 8 and below

  • Robson Sobral says:

    To differentiate pseudo-elements and pseudo-selectores, the new specification says pseudo-elements should use two colons. But the browsers still support the old syntax for compatibility.

  • Alejandro Moreno says:

    Re: double colons: Thanks!

  • Stomme poes says:

    We used them aregularly. Users filled in a very long form for our insurance. When they submitted, they were presented with a final page: first, showing what they had filled in, to check that it was correct and give them the opportunity to change anything… then, a textarea with some legally required legal mumbo (emulating any desktop software users typically install, so something they already have experience with) inside a form with a checkbox (“I agree”). That form is actually their final submission. Using a scrolling div outside the form pretty much would require multiple forms on a single page with ultimately a single submit, which would require Javascript finagling we don’t want. (The ability to link form inputs who are outside a form, as HTML5 allows, was then not yet available… and today still not cross-browser enough to be safe for corporate users.)

    @Jason another reason readonly rather than disabled: so far as I understand, disabled inputs may be ignored by AT such as screen readers. Read only does imply it should be read, and usually can be.
    Disabled implies “this label-input pair is currently not relevant” and is to be completely ignored.

    Re form validation: wish there weren’t so many issues with language in those error messages. I’ve had a case where I avoided HTML5 input types because the languages were always incompatible with the form due to differences in chosen browser language (or inherited from the OS settings) or back-end region settings. That and some of them (default error messages) are rather unhelpful (esp with pattern attribute). This makes Javascript/client-side validation still necessary but now needs extra code to override the defaults.
    Very off-topic, wish auto-complete was not “on” by default. If I understand correctly, I have to manually add it as “off” on all relevant inputs every time. Arg.

  • Jon says:

    “Contrary to what HTML5 Please and the W3C would have you believe, CSS3 is not part of HTML5.”

    Maybe we educate people without discrediting our peers? Just a thought. Also if you’re going to explicit name call like that the least you can do is explain what you’re talking to about to your readers so they can understand what you’re talking about.

    To me that read something like, ‘The established sites are wrong about A. Today, I’m going talk about B which is sort of related.’

  • Jon says:

    Looks like I repeated myself in that second block there.

  • Jon, I wasn’t really being critical of either of the two bodies I mentioned, just poking a bit of light-hearted fun; I’d hoped that was clear from the tone of the rest of my introduction, but maybe it wasn’t.

    I will cop to the fact that the publishing process meant that the reference is less topical than it was when I wrote it.

  • Grawl says:

    Good article, it’s goot to know the edge of technologies.
    But I think that there’s an error: http://cl.ly/EiB9

  • Martin says:

    How well does this CSS work with Internet Explorer 6?

  • Robson Sobral says:

    @grawl, the HTML specification uses “readonly”, while CSS3 uses “read-only”. But, I have to mention that still is a working draft: http://www.w3.org/TR/css3-ui/#pseudo-ro-rw

    @Martin, it doesn’t. If you use one of this selectors, IE 6 is gonna ignore the entire rule.

  • LouieGeetoo says:

    So, what about the smartphone browsers (Android, iOS Safari, Opera Mini, etc.)? As far as I know they don’t support HTML5 validation, but it’d be good to at least give them a mention.

    Thanks for the article!

  • Grawl says:

    @Robson, yes, I’m about specifications.

  • michelangelo says:

    I would argue that required field markers are very much a part of the page’s content and they do not belong in the CSS.

  • Edson says:

    Outstanding article! There are some pseudo classes combined with siblings (+) that I’ve never thought to use! And here a little add for this excelent article. For those like me don’t want those small up and down arrows on input type=”number” just use this:

    input[type=number]::-webkit-outer-spin-button {

  • Nicolas says:

    The article (examples) depend on not following w3c recommendation regarding the positioning of labels by putting labels after the non-radio / non-checkbox inputs.
    I think it should at least be mentioned so everybody can make an educated decision if it is worth it to make use of a convenience provided by css while not following best practice in html.

    ‘Check that there is a label element that identifies the purpose of the control before the input, textarea, or select element’
    ‘Check that there is a label element that identifies the purpose of the control after the input element [For all input elements of type checkbox or radio]’

  • Aankhen says:

    Edson: Why would you remove the very useful buttons…? If you don’t want a spinner, don’t use a spinner. If you use a spinner, don’t deactivate half of its functionality!

  • Paul says:

    I didn’t find the email validation particularly useful. It is successful after x@x.

    @LouieGeetoo actually Opera seems to support some of these. I did my own set of experiments on required fields and email addresses and it worked well. Android’s default browser was terrible and inconsistent. You can find a link to what I did here:

  • @type=”email” is still wrong specified in HTML5 and wrong implemented in browsers as it allows only ASCII characters in E-mail addresses. IDN addresses like ivan@россия.рф would be rejected but should not.

  • @Gunnar — you’re partially right in that some browsers don’t take Internationalized Domain Name (IDN) values into account for validation. Because of this I think it’s best to use type="email" only to trigger an email-specific keyboard layout on mobile devices. For HTML5 email validation you can use pattern="[^ @]*@[^ @]*", but if using both pattern will only restrict type="email", not broaden its scope. Because of this if you’re using type="email" you also want to use formnovalidate. On the bright side this is a browser bug, so eventually Peter’s swish CSS3 will work as expected for everyone.

    As for the spec, IDN support is a UI issue as international email addresses use punycode behind the scenes, so it’s not something the spec dictates. Given that, note the spec says:

    User agents may transform the value for display and editing; in particular, user agents should convert punycode in the value to IDN in the display and vice versa.

    More info: HTML5 spec bug 15489 for background, WebKit bug #40761.

    PS isn’t i18n fun!? :|

  • @Oli: Thanks for pointing that out.

    As for the spec […]

    Hm, what spec? The WHATWG and the W3C specs differ: there’s no such paragraph neither in the current WD nor in the current editor’s draft.

    PS isn’t i18n fun!? :|

    To quote Addison Phillips: “Internationalization is not a feature. It is an architecture.”

    The whole mess comes from IT being developed for a great part by native English speakers blind for the needs of other languages.

  • George Dina says:

    Nice, I will :out-of-range and :in-range for a back end project I am on now.
    I assume there will not be any problems with browser compatibility along my colleagues.

  • Rex says:

    I used small concept of required attribute and designed it, its here


    do you have any idea on how to design the alerts?

  • Do you have any sources to support your statement that HTML5 Please and the W3C would have us believe CSS3 was a part of HTML5?
    Otherwise I tend to think: Contrary to what you would have us believe, neither HTML5 Please nor the W3C would do that.

  • JeffNunn says:

    The distinction between HTML5 and CSS3 is pedantic and isn’t very meaningful. They are highly related and the difference between the two is only important to people working directly with the specs. Practically, it boils down to the exact same thing — new features for a more interactive web that reduce the amount of JS you need with varying degrees of browser support.

  • JeffNunn says:

    Obviously it matters for browser developers, people on the workgroup etc. but for the average developer who implements these features, it really doesn’t

  • gopi says:

    this is used to know about html5.i understand how to create a website using html5.

  • Hello. How can I do that with other markup (laberl before input)?. Thanks!

  • anon says:

    Damn, I wish there was a way to stop my browser honouring these validations. I don’t really care if I give a server data that they do not want, or if I do not provide data they do want, or provide them with false data.

    There aren’t legitimate reasons to use this, the server still has to validate the data (or you are trusting the client, and on a public network that is a stupid thing to do).

    My main gripe currently with this feature is how it annoyingly implements what businesses will want: as much accurate data as they can get hold of, and the user’s privacy desires take 2nd fiddle. The user is in less of a position of power and control over what they disclose. Features like this form validation in the browser just play into the hands of the anti-social.

    This very form implements the irritations, you make out that an email address isn’t actually required when you say it will not be published, then say it is required, then back that up with HTML 5 BS.

    Ahhh, the FF element inspector thing manually lets me override your form’s wishes, and then your server has to step in and do the job you tried to tell my browser to do:

    ERROR: please fill the required fields (name, email).

    As ever I have provided an email address where if it leads to spam you will get the spam, postmaster@html5doctor.com . If you don’t bother validating email addresses then people will not have to put in valid ones, and if it is their email address with a risk of spam, or yours, then the choice is obvious.

  • Leave a Reply to michelangelo

    Some HTML is ok

    You can use these tags:
    <a href="" title="">
    <abbr title="">
    <blockquote cite="">
    <del datetime="">
    <q cite="">

    You can also use <code>, and remember to use &lt; and &gt; for brackets.