CSS3 has been a big leap forward for forms. Not only are new style possibilities available, but new and powerful pseudo-selectors can also now be used to modify the appearance of our page, depending on the state of the form or of its fields. In this chapter, we will use a party registration form as a test case to show how this component can be enhanced by the new CSS specifications. We will also pay attention to how we can retain the right behavior for older browsers. We're going to cover the following topics:
HTML structure
The form
Basic styling
Marking required fields
The checked radio buttons trick
Counting invalid fields
Balloon styling
Let's start with some HTML5 code to shape the structure of our project's web page. To do so, create a file, named index.html
, in a new folder, named
no_signup_no_party
, containing the following markup:
<!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <title>No signup? No party!</title> <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.7.3/build/cssreset/cssreset- min.css"> <link rel='stylesheet' type='text/css' href='http://fonts.googleapis.com/css?family=Port+Lligat+Sans'> <link rel='stylesheet' type='text/css' href='css/application.css'> <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"> </script> </head> <body> <article> <header> <h1>No signup? No party!</h1> <p> Would you like to join the most amazing party of the planet? Fill out this form with your info but.. hurry up! only a few tickets are still available! </p> </header> <form name="subscription"> <!-- FORM FIELDS --> <input type="submit" value="Yep! Count me in!"> </form> <footer> Party will be held at Nottingham Arena next sunday, for info call 555-192-132 or drop us a line at info@nottinghamparties.fun </footer> </article> </body> </html>
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased through your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
As you can see from the markup, we are taking advantage of the new structure offered by HTML5. Tags such as <article>
, <header>
, and <footer>
enrich the page by adding semantic meaning to the content. These tags are rendered exactly as <div>
but are, semantically speaking, better because they explain something about their content.
Note
For more information, I suggest you look at the following article: http://html5doctor.com/lets-talk-about-semantics
Flavor text aside, the only section that needs detailed explanation is the <head>
section. Within this tag, we ask the browser to include some external assets that will help us along the way.
First, there is a Reset stylesheet, which is particularly useful for ensuring that all the CSS properties that browsers apply by default to HTML elements get removed. In this project, we use the one offered freely by Yahoo!, which basically sets all the properties to none
or something equivalent.
Next, we ask for another stylesheet. This one is from a
Google service called Google Web Fonts (www.google.com/webfonts), which distributes fonts that can be embedded and used within a web page. Custom web fonts are defined with a special
@font-face
property that contains the link to the font file the browser has to implement.
@font-face{ font-family: YourFontName; src: url('yourfonturl.eot'); }
Unfortunately, to reach the maximum possible compatibility between browsers, more font file formats are required, and so a more complex statement is necessary. The following statements help achieve such compatibility:
@font-face{ font-family: YourFontName; src: url('yourfonturl.eot'); src: url('yourfonturl.woff') format('woff'), url('yourfonturl.ttf') format('truetype'), url('yourfonturl.svg') format('svg'); font-weight: normal; font-style: normal; }
Google Web Fonts provides us with a stylesheet containing these statements for the fonts we choose, saving us all the trouble related to font conversion.
Next, let's create an empty file for our stylesheet under a css
folder within the project.
Last but not least, we need to ensure that even older Internet Explorer browsers will be able to handle the new HTML5 tags correctly.
html5shiv
(html5shiv.googlecode.com) is a small JavaScript file
that accomplishes exactly this task.
Now let's write the HTML code for the form by adding the following
code below the <!--FORM FIELDS-->
mark:
<fieldset> <legend> Some info about you: </legend> <input type="text" name="name" id="name" placeholder="e.g. Sandro" title="Your name, required" required> <label class="label" for="name"> Name: </label> <input type="text" name="surname" id="surname" placeholder="e.g. Paganotti" title="Your surname, required" required> <label class="label" for="surname"> Surname: </label> <input type="email" name="email" id="email" placeholder="e.g. sandro.paganotti@gmail.com" title="Your email address, a valid email is required" required> <label class="label" for="email"> E-mail: </label> <input type="text" name="twitter" id="twitter" placeholder="e.g. @sandropaganotti" title="Your twitter username, starting with @" pattern="@[a-zA-Z0-9]+"> <label class="label" for="twitter"> Twitter:</label> <footer></footer> </fieldset>
HTML5 offers some new attributes that we will explore briefly, as follows:
placeholder
: This is used to specify some help text that is displayed within the field when empty.required
: This is used to mark the field as required. It's a Boolean attribute that tells the browser to ensure that the field is not empty before submitting the form. This attribute is part of the new form validation features, which basically offer a way to specify some input constraints on the client side. Unfortunately, each browser handles the display of the error messages contained in thetitle
attribute in a different way, but we'll check this later in the chapter.pattern
: This is a powerful and sometimes complex way of specifying a validation pattern. It needs a regular expression as a value. This expression is then checked against the data inserted by the user. In case of failure, the message contained in thetitle
attribute is displayed.In the given example, the pattern value is
@[a-zA-Z0-9]+
, which means "one or more occurrences (the+
sign) of glyphs from the rangesa-z
(all lowercase letters),A-Z
(all uppercase letters), and0-9
(all digits)".Note
More ready-to-use patterns can be found at http://html5pattern.com/.
Like most of the features introduced by HTML5, even new form attributes such as the ones we saw in the code earlier suffer in terms of complete browser compatibility.
Note
To get a glimpse of the current browser support for these attributes and many other HTML5 and CSS3 features, I suggest going to http://caniuse.com/.
There's another oddity in this code: labels are placed after the fields they're linked to. This markup, although uncommon, is still valid and gives us some new interesting options to intercept user interaction with the form elements. This may sound mysterious, but we're going to analyze the technique in detail in a few pages from now.
Let's add another
fieldset
element below the one we just wrote:
<fieldset class="preferences"> <legend> Your party preferences: </legend> <input type="radio" name="beers" id="4_beers" value="4"> <label class="beers" for="4_beers">4 beers</label> <input type="radio" name="beers" id="3_beers" value="3"> <label class="beers" for="3_beers">3 beers</label> <input type="radio" name="beers" id="2_beers" value="2"> <label class="beers" for="2_beers">2 beers</label> <input type="radio" name="beers" id="1_beers" value="1"> <label class="beers" for="1_beers">1 beers</label> <input type="radio" name="beers" id="0_beers" value="0" required> <label class="beers" for="0_beers">0 beers</label> <span class="label"> How many beers?: </span> <input type="radio" name="chips" id="4_chips" value="4"> <label class="chips" for="4_chips">4 chips</label> <input type="radio" name="chips" id="3_chips" value="3"> <label class="chips" for="3_chips">3 chips</label> <input type="radio" name="chips" id="2_chips" value="2"> <label class="chips" for="2_chips">2 chips</label> <input type="radio" name="chips" id="1_chips" value="1"> <label class="chips" for="1_chips">1 chips</label> <input type="radio" name="chips" id="0_chips" value="0" required> <label class="chips" for="0_chips">0 chips</label> <span class="label"> How many chips?: </span> <footer></footer> </fieldset>
Nothing to highlight here; we've just added two radio button groups. Now, if we try to run what we've done up to now in a browser, we are going to face some disappointment because the default browser's styles have been removed by the Reset stylesheet.

Time to add some basic styling!
What we need to do is center the form, give the right size to the texts, choose a background, and adjust the displacement of labels and fields.
Let's start with the background. What we want to achieve is to place an image as big as possible to fit the page while keeping its proportions. This simple task in the "CSS2 era" would involve some use of JavaScript, such as the well-known Redux jQuery plugin (http://bavotasan.com/2011/full-sizebackground-image-jquery-plugin/). With CSS3 it's just a matter of a few statements:
html{ height: 100%; background: black; background-image: url('../img/background.jpg'); background-repeat: no-repeat; background-size: cover; background-position: top left; font-family: sans-serif; color: #051a00; }

What does the trick here is the
background-size
property, which accepts the following values:
length
: Using this value, we can express the size of the background using any units of measurement, for examplebackground-size: 10px 10px;
.percentage
: Using this value, we can specify a background size that varies with the size of the element, for examplebackground-size: 10% 10%;
.cover
: This value scales the image (without stretching it) to cover the whole area of the element. This means that part of the image may not be visible because it could get bigger than the container.contain
: This value scales the image (without stretching it) to the maximum size available while keeping the whole image within the container. This, obviously, could leave some area of the element uncovered.
So, by using cover
, we ensure that the whole page will be covered by our image, but we can do more! If we run all that we've done up to here in a browser, we will see that the pixels of our background image become visible if we enlarge the window too much. To avoid this, what we can do is to use another background image on top of this one. We can use small black dots to hide the pixels of the underlying image and achieve a better result.
The good news is that we can do this without using another element, as CSS3 allows more than one background on the same element. We can use commas (,
) to separate the backgrounds, keeping in mind that what we declare first will lay over the others. So, let's change the preceding code a bit:
html{ height: 100%; background: black; background-image: url('../img/dots.png'), url('../img/background.jpg'); background-repeat: repeat, no-repeat; background-size: auto, cover; background-position: center center, top left; font-family: sans-serif; color: #051a00; }

Also, all the other background-related properties act in the same way. If we omit one of the values, the previous one is used, so writing background-repeat: repeat
is the same as writing background-repeat: repeat, repeat
if two background images are declared.
Let's move on and define the rest of the required properties to complete the first phase of the project:
/* the main container */ article{ width: 600px; margin: 0 auto; background: #6cbf00; border: 10px solid white; margin-top: 80px; position: relative; padding: 30px; border-radius: 20px; } /* move the title over the main container */ article h1{ width: 600px; text-align: center; position: absolute; top: -62px; /* using the custom font family provided by google */ font-family: 'Port Lligat Sans', cursive; color: white; font-size: 60px; text-transform: uppercase; } /* the small text paragraphs */ article p, article > footer{ padding-bottom: 1em; line-height: 1.4em; } /* the fieldsets' legends */ article legend{ font-family: 'Port Lligat Sans', cursive; display: block; color: white; font-size: 25px; padding-bottom: 10px; } .label{ display: block; float: left; clear: left; } /* positioning the submit button */ input[type=submit]{ display:block; width: 200px; margin: 20px auto; } /* align texts input on the right */ input[type=text], input[type=email]{ float: right; clear: right; width: 350px; border: none; padding-left: 5px; } input[type=text], input[type=email], .label{ margin: 2px 0px 2px 20px; line-height: 30px; height: 30px; } span + input[type=radio], legend + input[type=radio]{ clear: right } /* size of the small labels linked to each radio */ .preferences label.chips, .preferences label.beers{ width: 60px; background-image: none; } input[type="radio"]{ padding-right: 4px; } input[type="radio"], .preferences label{ float: right; line-height: 30px; height: 30px; }
There are just a few things to underline here. First of all, by using some floats, we've moved all the fields to the right and the labels to the left. Next, we've defined some distance between the elements. Maybe the most cryptic statement is the following one:
span + input[type=radio], legend + input[type=radio]{ clear: right }
Due to the floating that we just talked about, the first element of each group of radio buttons became the rightmost. So, we identify this element by using the selector1 + selector2
selector, which indicates that the specified elements must be siblings. This is called an
adjacent sibling selector, and selects all the elements matching the selector2
selector that directly follows an element matching the selector1
selector. Finally, using clear:right
we simply state that there must be no other floating elements to the right of these radio buttons.
Let's reload the project in the browser to appreciate the result of our work:

Let's look at an easy trick to automatically display an asterisk (*) near the labels of required fields. The HTML5 form validation model introduces some new and very interesting pseudo-selectors:
In our case, we need to match all the labels that follow a field that has the required
attribute. Now the HTML5 structure we implemented earlier comes in handy because we can take advantage of the +
selector to accomplish this.
input:required + .label:after, input:required + * + .label:after{ content: '*'; }
We added a small variation (input:required + * + .label:after
) in order to intercept the structure of the radio buttons as well.
Let's analyze the sentence a bit before moving on. We used the
:after
pseudo-selector to get access to the location just after the element with a label
class. Then, with the content
property, we injected the asterisk within that location.
If we reload the page we can verify that, now, all the labels that belong to fields with a required
flag end with an asterisk. Someone may point out that screen readers do not recognize this technique. To find a way around this, we can take advantage of the
aria-required
property, part of the WAI-ARIA specification (http://www.w3.org/TR/WCAG20-TECHS/ARIA2).

Now we can concentrate on the radio buttons, but how can we render them in a better way? There is a cool technique for this; it takes advantage of the fact that you can check a radio button even by clicking on its linked label. What we can do is hide the input elements and style the corresponding labels, maybe using icons that represent chips and beers.
Let's begin by removing the text from the radio button labels and changing the cursor appearance when it's hovering over them:
.preferences label{ float: right; text-indent: -100px; width: 40px !important; line-height: normal; height: 30px; overflow: hidden; cursor: pointer; }
Well done! Now we have to hide the radio buttons. We can achieve this by placing a patch with the same color as the background over the radio button. Let's do that:
input[type=radio]{ position: absolute; right: 30px; margin-top: 10px; } input[type=radio][name=chips]{ margin-top: 35px; } span + input[type=radio] + label, legend + input[type=radio] + label{ clear: right; margin-right: 80px; counter-reset: checkbox; } .preferences input[type="radio"]:required + label:after{ content: ''; position: absolute; right: 25px; min-height: 10px; margin-top: -22px; text-align: right; background: #6cbf00; padding: 10px 10px; display: block; }
If we now try to submit the form either using WebKit-based browsers or Firefox, we can appreciate that the validation balloons related to radio buttons are displayed correctly on both of them.
Let's move on and work on the radio button labels that, at the moment, are completely empty because we moved the text away using the text-indent
property. What we are going to do now is to put a tiny placeholder image within each label, and by taking advantage of the CSS3 ~
selector, create a pseudo-star rating system with a nice mouse-over effect.
Due to the fact that we have to work with different images (for beers and chips), we have to duplicate some statements. Let's start with the .beers
labels:
.preferences label.beers{ background: transparent url('../img/beer_not_selected.png') no-repeat center center; } .preferences label.beers:hover ~ label.beers, .preferences label.beers:hover, .preferences input[type=radio][name=beers]:checked ~ label.beers{ background-image: url('../img/beer.png'); counter-increment: checkbox; }
The elem1 ~ elem2
selector applies to all the elem2
labels that are siblings of the elem1
label and that follow it (the elem2
labels don't have to be adjacent, though). This way, we can target all the labels that follow a label that is in the hover state (when the mouse is over the element) with the selector .preferences label.beers:hover ~ label.beers
.
Using the CSS3 :checked
pseudo-class selector, we can identify the radio button that has been checked, and by applying the same trick that we just discussed, we can target all the labels that follow a checked radio button by using .preferences input[type=radio][name=beers]:checked ~ label.beers
. By putting together these two selectors and a classic .preferences label.beers:hover
selector, we are now able to change the placeholder image reflecting the user interaction with the radio buttons. Now let's add a final cool feature. We have used the
counter-increment
property to keep track of the number of selected labels, so we can take advantage of this counter and display it.
.preferences input[type=radio][name=beers]:required + label.beers:after{ content: counter(checkbox) " beers!"; }
Let's try the result in a browser:

Now, we have to duplicate the same statements
for the .chips
labels too:
.preferences label.chips{ background: transparent url('../img/frenchfries_not_selected.png') no-repeat center center; } .preferences label.chips:hover ~ label.chips, .preferences label.chips:hover, .preferences input[type=radio][name=chips]:checked ~ label.chips { background-image: url('../img/frenchfries.png'); counter-increment: checkbox; } .preferences input[type=radio][name=chips]:required + label.chips:after { content: counter(checkbox) " chips!"; }
All of the styling we did in this chapter has one big problem; if the browser doesn't support CSS3, it successfully hides both radio buttons and text labels but fails to add their image replacements, making everything unusable. There are a few ways to prevent this. The one introduced here is to use media queries.
Media queries, which will be covered in detail in a later project, basically consist of a statement that describes some conditions required to apply some styles. Let's consider the following example:
@media all and (max-width: 1000px){ body{ background: red; } }
In this example, the body background is turned into red only if the size of the browser window doesn't exceed 1000px
. Media queries are really useful to apply specific styles only to target devices (smartphones, tablets, and so on), but they have another interesting property; if a browser supports them, it also supports the CSS3 rules we used, so we can place all of the CSS written in this and in the previous sections within a media query statement:
@media all and (min-device-width: 1024px){ /* --- all of this and previous sections' statements --- */ }
With this trick, we solved another subtle problem. Trying the project on an iPad without this media query statement would have resulted in some problems with clicking on the radio buttons. This is because labels do not respond to clicks on iOS. By implementing this media query, we force iOS devices to fall back to regular radio buttons.
In the previous section, we used some properties without explaining them; they are counter-reset
and counter-increment
. Plus, we used a function-like command called
counter()
. In this section, we'll explain these properties by creating a mechanism to display the number of invalid fields. A counter is basically a variable we can name and whose value can be incremented using counter-increment
. Next, this counter can be displayed by using the
counter(variable name)
declaration within a
content
property.
Let's see a small example:
<ul> <li>element</li> <li>element</li> <li>element</li> </ul> <p></p> <style> ul{ counter-reset: elements; } li{ counter-increment: elements; } p:after{ content: counter(elements) ' elements'; } </style>
Trying this small bit of code results in a p
element containing the sentence 3 elements:

We can combine these powerful properties with the new form pseudo-selector to obtain a way to display valid and invalid fields.
Let's start by creating two counters, invalid
and fields
, and resetting them at each fieldset
element because we want to display the invalid fields for each fieldset
element. Then, we increment both counters when we find an invalid field and only the
fields
counter when we find a valid field.
fieldset{ counter-reset: invalid fields; } input:not([type=submit]):not([type=radio]):invalid, input[type=radio]:required:invalid{ counter-increment: invalid fields; border-left: 5px solid #ff4900; } input:not([type=submit]):not([type=radio]):valid, input[type=radio]:required{ counter-increment: fields; border-left: 5px solid #116300; }
The :not
pseudo-selector is pretty straightforward. It subtracts the elements matching the selector within the parentheses from the elements matching the leftmost selector. If this seems a bit confusing, let's try to read the last selector: match all the input
elements, whose type
value is not submit
and not radio
, that respond to the :valid
pseudo-selector.
Almost there! Now that we have the counters, let's display them using the
footer
element we have:
fieldset footer{ clear: both; position: relative; } fieldset:not([fake]) footer:after{ content: 'yay, section completed, move on!'; text-align: right; display: block; font-size: 13px; padding-top: 10px; } /* the value of the content property must be on one single line */ fieldset > input:invalid ~ footer:after{ content: counter(invalid) '/' counter(fields) " fields with problems; move the mouse over the fields with red marks to see details.\a Fields with * are required."; white-space: pre; }
The :not([fake])
selector is used like the media query shown earlier. We just want to ensure that only the browsers that support the :valid
and :invalid
pseudo-selectors can interpret this selector.
This last addition has some drawbacks, though; mixing presentation with content is generally something to avoid.
Each browser actually displays form errors in its own way, and we can't do very much to affect this visualization. The only exceptions are WebKit-based browsers, which let us change the appearance of such messages. The following code shows how an error balloon is constructed in these browsers:
<div>::-webkit-validation-bubble <div>::-webkit-validation-bubble-arrow-clipper <div>::-webkit-validation-bubble-arrow </div> </div>::-webkit-validation-bubble-message <div> <b>Browser validation message</b> element's title attribute </div> </div>
We can access all the elements that compose an error message by using the special pseudo-classes listed in the preceding code. So, let's begin!
::-webkit-validation-bubble{ margin-left: 380px; margin-top: -50px; width: 200px; } input[type=radio]::-webkit-validation-bubble{ margin-left: 50px; margin-top: -50px; } ::-webkit-validation-bubble-arrow-clipper{ -webkit-transform: rotate(270deg) translateY(-104px) translateX(40px); } ::-webkit-validation-bubble-arrow{ background: #000; border: none; box-shadow: 0px 0px 10px rgba(33,33,33,0.8); } ::-webkit-validation-bubble-message{ border: 5px solid black; background-image: none; box-shadow: 0px 0px 10px rgba(33,33,33,0.8); }
With -webkit-transform
, we're applying some transformation to the matched elements. In this case, we're moving the arrow, which usually lies on the bottom of the balloon, to the left side of it.
The following is a glimpse of how our completed project looks:

As we might expect, this project is not fully supported on all browsers because it implements HTML5 and CSS3 features that, of course, aren't included in old browsers. Many techniques exist to find a way around this issue; the one we'll look at now is called graceful degradation. It basically focuses on making the core functionalities of the project as widely supported as possible while accepting that everything else might be unsupported and thus not displayed.
Our project is a good example of graceful degradation: when a browser does not support a specific property, its effects are simply ignored without affecting the basic functionality of the form.
To prove this, let's try the project on IE8, which basically has no CSS3 support:

To achieve the best possible browser support, we may also want to hide footer elements and radio buttons on IE9 because, otherwise, they'll be displayed but they won't behave as expected. To do so, we need to add a conditional comment in our index.html
file, just before the end of the head
section. We'll see in the later chapters how conditional comments work, but for now let's say that they allow us to specify some markup that needs to be interpreted only by chosen browsers.
<!--[if IE 9]> <style> footer, input[name=beers], input[name=chips]{ display: none; } </style> <![endif]-->
In this first project, we've explored how CSS3 can enhance our forms with useful information derived from the markup and the status of the fields. In the next chapter, we'll focus our attention on buttons and how we can mimic real-world shapes and behavior without using images by taking full advantage of gradients and other CSS3 properties.