By Craig Buckler

HTML5 Forms: CSS

By Craig Buckler

In the second article of this three-part series about HTML5 forms, we’re going to look at styling or — more specifically — the pseudo-class selectors you can use to target input fields in various states. If you haven’t read it already, please refer to part one to ensure you understand the basic markup concepts.

Remove Default Styling

You’ve probably noticed browsers applying default formatting. For example, most browsers apply rounded corners to search boxes and add subtle background gradients that can look misplaced on your flat design.

To remove default styling, you can use the appearance: none; property, which requires prefixes. However, use with caution since it can remove essential styles — checkboxes and radio buttons disappear in Chrome! To be on the safe side, only apply the property when it’s required and test in as many browsers as possible, e.g.

textarea {
  -webkit-appearance: none;
  -moz-appearance: none;
  -ms-appearance: none;
  appearance: none;
  outline: 0;
  box-shadow: none;

Note I have also reset the outline and box-shadow to remove the default ugly blue box-shadow focus and error styling in all browsers.

The appearance property is documented on CSS-Tricks but it’s in a state of flux.


:focus has been supported since CSS2.1 and sets styles for the field currently being used, e.g.

select:focus {
  background-color: #eef;


:checked styles are applied to checked checkboxes or radio buttons, e.g.

<input type="checkbox" name="test" />
<label for="test">check me</label>
input:checked ~ label {
    font-weight: bold;

There is no corresponding ‘:unchecked’ selector but you shouldn’t need one: simply create a default style then apply changes when :checked is activated. Alternatively, you can use :not(:checked).


:indeterminate is technically not yet in the spec, though it is mentioned. According to the spec, it represents a checkbox or radio button that is “neither checked nor unchecked.”

It is unusual in that it only applies styles when you set a checkbox’s .indeterminate property via JavaScript, i.e.

document.getElementById("mycheckbox").indeterminate = true;

It has no effect on the .checked property, which can only be true or false.

There are few a situations when :indeterminate could be useful. If you had a list checkboxes, you could provide a ‘check all’ checkbox that checked or unchecked every item when clicked. However, if you check some of the items, the ‘check all’ checkbox could go into an indeterminate state.


:required applies styles to any input that has a required attribute and must be entered prior to submit.


:optional applies styles to any input that does not have a required attribute. I’m not sure why it’s been added since :not(:required) would do the same?!


:valid applies styles to any input that currently holds valid data.


Similarly, :invalid (or :not(:valid)) applies styles to any input that currently holds invalid data, e.g.

input:invalid {
    border-color: #900;

:in-range (number and range inputs)

Numbers and ranges holding a valid value between the min and max attributes that adhere to the step value can be selected using :in-range. Obviously, it’s a little difficult for a slider to be out of range, but…

:out-of-range (number and range inputs)

:out-of-range targets invalid number values for range inputs.


Inputs with a disabled attribute can be targeted with the :disabled pseudo-class, e.g.

input:disabled {
    color: #ccc;
    background-color: #eee;

Remember that disabled fields will not be validated or have their data posted to the server. However, styles for pseudo-classes such as :required and :invalid will still be applied.


Similarly, non-disabled fields can be selected with :enabled (or :not(:disabled)). In practice, you’re unlikely to require this selector since it’s the default input style.


Inputs with a readonly attribute can be targeted with the :read-only pseudo-class. Remember that read-only inputs will still be validated and posted to the server but the user cannot change the values.


Standard read-write fields can be selected with :read-write (or :not(:read-only)). Again, it’s not a selector you’ll need often.

:default (submit buttons or inputs only)

Finally, we have the :default selector, which applies styles to the default submit button.

Placeholder Text Style

The placeholder attribute text can be styled using the ::placeholder pseudo-element with vendor-prefixes (in separate rules), e.g.

input::-webkit-input-placeholder { color: #ccc; }
input::-moz-placeholder { color: #ccc; }
input:-ms-input-placeholder { color: #ccc; }
input::placeholder { color: #ccc; }

CSS Specificity

The selectors above have the same specificity so some care is necessary when defining two or more styles that apply to the same input. Consider:

input:invalid { color: red; }
input:enabled { color: black; }

Here we require all invalid fields to use red text but it’ll never happen because we’ve defined all enabled fields to have black text later in the stylesheet.

Keep selectors simple and use the minimum amount of code. For example, an empty :required field will be :invalid so it’s rarely necessary to style the former.

Validation Bubble

On submit, the first invalid value is highlighted with an error bubble:

error bubble

The bubble design will vary across devices and browser. Only the Webkit/Blink browsers permit a level of non-standard CSS customization:

::-webkit-validation-bubble { ... }
::-webkit-validation-bubble-arrow { ... }
::-webkit-validation-bubble-message { ... }
::-webkit-validation-bubble-arrow-clipper { ... }

My recommendation: don’t bother trying. If you require custom error formatting you’ll almost certainly want to use custom messages. For that, you’ll require JavaScript.

Browser Support

In general, the important styles and selectors work in all modern browsers from IE10+. Some of the less useful ones, such as in-range are Webkit/Blink only for now. Older browsers will support :focus but, for anything more sophisticated, you’ll need to provide JavaScript fall-backs.

Creating Usable Forms

The styles above are applied immediately. For example:

input:invalid {
    border-color: #900;

applies a red border to any invalid field. Unfortunately, when the page first loads every field could be invalid and the user is confronted with a daunting set of red boxes.

Personally, I prefer errors to appear on submit or perhaps when changing focus from a field that’s invalid. Browsers offer no way to do that natively. You guessed it — you need JavaScript. Fortunately, the HTML5 constraint validation API provides the tools to:

  • halt validation until a form is used
  • use custom error messages
  • polyfill unsupported input types
  • provide fall-back styling and validation for older browsers, and
  • create more usable forms

We’ll take a closer look at these in the last part of this series.

  • Vo Quoc Dat

    Good article.

    Where can I see demo with this article ?


    • Craig Buckler

      There isn’t a demo – mainly because they’re applicable to any form and it depends what you want to do.

  • Jingqi Xie

    The pseudo class :-ms-input-placeholder means the whole input with the placeholder, rather than the pseudo elements in other browsers which refer to the placeholder itself.

  • Ajili Ines

    Heplful article.

    But in the paragraph (:enabled)
    : Similarly, non-disabled fields can be selected with :enabled (or :not(:enabled)). the :not(:enabled), I think that it must be :not(:disabled)?

  • Stephen Cunliffe

    The “ugly blue box-shadow focus” thing you talk about wanting to remove is a
    usability feature. If the color/exact styling offends you by all means
    change it but being able to identify the currently focused field is “a
    good thing” and something you certainly do not want to lose!

    • vsync

      well, you can say “fuck users, I want my website to be beautiful” and one can think of his website as creation of art, which many are, and in these situation, usability means absolutely nothing.

    • LouisLazaris

      Yeah, I think Craig’s intention was to say that you should change it, not remove it. I was considering editing that section when I reviewed this, so I’ll see about making an edit to make that more clear. Thanks.

    • Craig Buckler

      Yeah – sorry – there wasn’t any intention to remove focus effects but replace them with something better which matches your styles.

  • callmenick1

    Great collection @craigbuckler:disqus , very useful!

  • LouisLazaris

    You’re right, nice catch. We got bit by the double negative confusion. :) Fixed now.

  • :checked doesn’t seem to work very well for radio buttons, since (in some cases) they all have the same “name” value.

  • Roman Blöth

    Thank you so much for this 3-parts-series. I thought I knew everything about HMTL5 forms, but tought me otherwise. Very concise and easy-to-ready & understand!

  • Great article. Gonna take that with me for my argan oil business…

  • Smokva

    Great article! You addressed quite a few issues that I have not found anywhere else and that I’ve been scratching my head over for a few days now. So thank you.
    Just a couple of quick points/questions for clarification.
    1. In your ‘Remove Default Styling’ section, should you not also list the ‘input,’ element alone in addition to ‘input[type=”text”] ? Since type=”text” is the default, it is not always specified in the code. Or are you assuming the input element has already been stripped in the basic reset?
    2. I found your ‘placeholder text style’ section particularly helpful so thank you. But did you make a typo on input:-ms-input-placeholder { color: #ccc; } ? There is only one colon – shouldn’t there be two?
    3. So to be crystal clear: Are you saying that currently there isn’t any way to create a customized validation error message using only CSS3? One is strictly at the mercy of the browsers validation messages. Is that correct? (I don’t like using any script – except the shiv -mainly because I use noScript and have Javascript disabled, so I figure others are paranoid like me! :) )

  • Try my form library on JQuery

Get the latest in Front-end, once a week, for free.