A Pseudo Makeover

Web Review
February 2001

Although they're widely used, pseudo-elements and pseudo-classes are in many ways the least understood parts of CSS. This was especially true under CSS1, where their scope was limited and the rules on using them fairly strict. Under CSS2, the rules have been relaxed and the scope widened—in theory, anyway. Understanding the new world of pseudo-classes is important for authors, so we'll devote this article to exploring the changes and look ahead to what's coming in the future.

Pseudo-Elements

First, let's review the rules on how one can use pseudo-elements. This should serve as a refresher course for you experienced CSS authors, and a good introduction for new style masters.

Let's start with an example. Assume that we want to make the first letter of every H1 element stand out. We'll make it white text with a black background, while the rest of the text will simply be black on a white background. Here's all we need:

h1 {color: black; background: white;}
h1:first-letter {color: white; background: black;}

That's all! Now how does it work? Simply put, the user agent (such as a browser) is instructed to create "phantom markup" to represent the structure needed to style the first letter. If that sounds confusing, try examining this phantom:

<h1><first-letter>T</first-letter>his is an H1</h1>

Of course, there is no such element as first-letter—not in (X)HTML, at any rate. And if you look at the document source, there won't be any kind of first-letter markup there at all. Instead, the document looks like this:

<h1>This is an H1</h1>

This is exactly why :first-letter is known as a pseudo-element selector: because its effects can be most accurately described by invoking a nonexistent element and styling it.

There are a few other such pseudo-elements, including:

As you might guess, :first-line causes the first line of an element to be styled. :before and :after relate to generated content, which isn't really supported by any browsers as of this writing; and the other three relate to paged media and are even less supported than the generated content pseudo-elements. (In the interests of simplicity, we're going to skip a discussion of how these are supposed to work—they're subjects for entire articles in themselves.)

Here's the one thing which unites all these pseudo-elements: they may only appear at the end of a selector chain. In other words, they have to be the last thing which appears before the opening curly-brace. So these would be legal:

div p strong:first-letter {font-size: 125%;}
html body h1 + div.opener ul li:first-line {color: olive;}

This would not be legal:

p:first-line em {color: red;}     /* illegal! */

Although this may seem like a reasonable construction, it's forbidden by the CSS grammar. The other thing which is forbidden is combining pseudo-element selectors in the same selector. For example, this would not be allowed:

h1:first-line:first-letter {color: red;}   /* illegal! */

Finally, one important implication of the "last part of a selector chain" rule is that the pseudo-element selector should come after any class or ID names found in the selector. Thus:

h1.page-title:first-letter {color: green;}
p#opener:first-line {font-style: italic;}

Pseudo-Classes

By comparison, pseudo-classes are pretty freewheeling. They can appear almost anywhere in a selector chain, and you can combine more than one at a time. You can even throw them in with pseudo-elements.

First, we should address the name. Why pseudo-classes? Because they operate as if phantom classes were added to various elements. These might be based on the state of the element (in the case of anchors and form elements) or on certain language encodings. For example, let's say that we want to apply styles to hyperlinks depending on whether or not they point to resources which have been visited. You can think of the state of a link being represented like this:

<a href="http://www.w3.org/" class="visited-link">W3C</a>

Of course, nobody would ever dream of actually putting such classes in the document markup. For one thing, they'd be wrong half the time, since any unvisited link can become a visited link, and visited links eventually get expired and become unvisited links again. How would you update them?

You don't have to, of course. Just like the creation of pseudo-elements, we have the creation of pseudo-classes here. If you want to make all unvisited links green and visited links olive, here's what you need:

a:link {color: green;}
a:visited {color: olive;}

Now, these two states are mutually exclusive—a link can't be both visited and unvisited at the same time. The same can't be said for other pseudo-class selectors, which can be combined together in various ways. For example, let's say that we want to add hover styles to our links so that a background appears in the link when it's being hovered (i.e. the mouse pointer is placed within the element's box). Just to be cool, we decide to make the backgrounds different for visited links than they are for unvisited links. This will match the differing colors. So:

a:link:hover {background: yellow;}
a:visited:hover {background: cyan;}

These rules, combined with the simpler ones we used above, will yield unique color/background combinations for visited and unvisited links.

You could leave out the state-specific pseudo-classes and just have a generic hover style:

a:hover {background: magenta;}

However, this would be applied to all hyperlinks, regardless of their visited state.

The same things hold true for the other pseudo-classes, which are:

You could, for example, define a set of styles for any input element which has focus— that is, it's ready to accept user input—and is currently being hovered:

input:focus:hover {background: purple;}

Taking Pseudo-Classes a Step Further

As cool as all that was, CSS lets us do even more with pseudo-classes. For example, did you know that you can set hover styles on any element, not just hyperlinks? It's true, at least in a theoretical sense. You could make any paragraph light up just by saying:

p:hover {background: yellow;}

With those styles, all you have to do is move the mouse pointer over a paragraph and watch its background get sunny. Assuming you have a browser which supports such behavior, of course. Sadly, they only way to see it is to find an old nightly build of Mozilla which supported this behavior. No officially released browser supports hover styles on non-hyperlink elements.

On the other hand, many browsers do support the relaxed rules on the placement of pseudo-classes. In CSS2, user agents are now allowed to support the following construction:

a:link.external {font-weight: bold;}

Under CSS1, that would have been illegal, because the pseudo-classes had to follow the class name. No longer! Of course, some older browsers follow the CSS1 way, so they'd probably ignore the above example. Therefore, it might be a good idea to stick to the old way of doing things for a while.

Even more fun is combining pseudo-classes with pseudo-elements. Take, for example:

a:link:hover:first-letter {font-weight: bold;}

This would boldface the first letter of any unvisited link which is hovered with the mouse pointer. Moving the mouse pointer away will remove the conditions which match the above selector, and so the first letter will cease to be boldfaced.

Conclusion

In the transition from CSS1 to CSS2, pseudo-classes got a major upgrade. Between the ability to combine multiple pseudo-classes on a single selector, the new capabilities embodied in new pseudo-classes, and the loosening of the grammar rules governing how pseudo-classes can be placed on a selector, the ease of use went up quite a bit. Looking ahead to CSS3, a recently published working draft reveals that major work is being done to extend the range of available pseudo-classes. We also see signs that the syntax may be changed a little bit, but until the draft reaches its final form, we won't know if this will really happen or not. Once we reach that point, I'll revisit the subject in a new article. Until then, have pseudo-fun!