pseudo-class, pseudo-element, pseudo-CSS

This is a work in progress.

Table of content

Pseudo-elements and pseudo-classes

W3C CSS 2.1 specification
CSS 2.1 selectors, Part 3: Pseudo-classes and pseudo-elements

This article is intended to summarize bugs and inconsistencies regarding pseudo-elements and pseudo-classes in IE6/Win and IE7 beta 3. Note that the author can't discuss the typographical aspects of these selectors.

Dean Edward's IE7 { css2: auto; } is a general workaround which adresses these issues in IE6.

The :first-letter pseudo-element



IE6: There must be a blank between the selector and the declaration block (IE 6 pseudo elements bug) – this is a inconsistency to the normal parsing behavior in standards mode (fixed in IE7b2).

IE6, IE7b2: And there is another problem: "no space before comma", see below.

  1. p:first-letter{property: value} /*does not work*/
  2. p:first-letter {property: value} /*fix*/
  4. p:first-letter, .extraneous {property: value} /*does not work*/
  5. p:first-letter , .extraneous {property: value} /*fix with space before comma*/


IE6: The pseudo-element matches in other's #id rules, in grotesque violation of the specs. In the following example, even if there is no such element with id=nonexistent at the page, all the block-level links of #content get a green first-letter on hover (fixed in IE7b2).

[ testcase ]

  1. #nonexistent a:first-letter {}
  2. #content a {display: block;}
  3. #content a:hover {color: green; }

When at inline-level, the links don't get any green on hover at all in this example.

IE6, 7b3: A similar, bizarre behavior can be seen here: IE renders a 2em first letter, but the link is blue and oblique.

[ testcase ]

  1. a:link, a:visited {
  2. color: blue;
  3. display: block;
  4. }
  5. #myID a:first-letter { font-size: 2em; }
  6. #myID a:link, #myID a:visited {
  7. color: red;
  8. font-style: italic;
  9. }

The presence of the pseudo-element manipulates the specificity – debugger's horror. In general, a workaround by using !important just to bring IE6 back to order cannot be recommended, as !important itself is buggy in IE6.


IE6, 7b3: A pseudo-element which sets its own em font-size 'inherits' the em-margin of it's undimensioned (non-layout) element. In the following example, the first-letter shows a 3em margin-left.

[ testcase ]

  1. p {margin-left: 1em;}
  2. p:first-letter {font-size: 3em;}

A workaround is to let the p gain "layout".

Application instability: another element adjacent to the pseudo-element

IE6, 7b3: Things go worse and may burn your Win XP SP2 PC / Virtual PC when you let the pseudo-element be a part of or be immediately followed by another element. Consider this example, where the typography emphasizes the initial cap and the adjacent sentence:

[ testcase ]

  1. .ipsum {
  2. margin: 0 0 0 3em;
  3. padding: 0.5em;
  4. background: url(bg.gif) top left no-repeat;
  5. border: 1px solid red;
  6. }
  7. .ipsum:first-letter { font-size: 2em; }
  8. .ipsum span { text-transform: uppercase; }
  10. <p class="ipsum">L<span>orem ipsum dolor sit amet</span>, consectetuer adipiscing elit. Aliquam ... </p>
A screenshot showing the misplacement of a :first-letter element adjacent a span.

The first-letter wrongly 'inherits' the left margin. The span inherits it too, resulting in a (font-size:) 2 x (margin-left:) 3em gap. In addition, 3em from the start of the span, there is an area which has 'inherited' the margin, the padding, the red border and the background-image!

Now, when such a .ipsum construct gains "layout" by a subset of layout properties (position: absolute; / float: any value; / display: inline-block; / zoom: 1;), what will happen? Right. Some will have to cancel the frozen process, others will have to reboot, data gets lost. Don't do it. (No, I don't link to a testcase here. IE7b3 is still crashing after scrolling/resizing. Depending on the memory already burned, you might have difficulties to stop the process...)

A screenshot showing incriesing memory and CPU usage by the modiefied testcase

A workaround is the use of one of the following rules, maybe because this changes the specificity again so the span doesn't try to inherit it.

  1. p.ipsum {
  2. _height: 0;
  3. property: value;
  4. ...
  5. }
  1. p.ipsum {
  2. property: value;
  3. ...
  4. layout_subset: trigger_value;
  5. }

The bug is already tracked by Quirksmode's Bug Report and was filed to MS once more in Feb. 2006.

Ordered and unordered lists

IE6, 7b3: In ordered lists, li:first-letter affects both the first letter of the list item and the list marker. Same in unordered lists (font-properties seems to be not affected here, though).

[ testcase ]

A screenshot showing the list-marker is affected by :first-letter.


IE6, IE7: Only float:left seems to be supported on a first-letter pseudo-element. And it cannot be overwritten by float: right or reset by float: none later on.

  1. p:first-letter { float: left; font-size: 2em; }
  2. p:first-letter { float: none; } /* or float: right */

[ testcase ]

This results in a left floating first letter.

The :first-line pseudo-element



IE6: There must be a blank between the selector and the declaration block (fixed in IE7b2).

IE6, 7b2: There must be a blank before the comma, analog to :first-letter.


IE6: Analog to the situation in :first-letter, the pseudo-element matches in other's #id rules and changes the specificity. This becomes noticeable e.g. on multi-line-links, where only the first line reacts on hover (fixed in IE7b2).

[ testcase ]

  1. #nonexistent a:first-line {}
  2. #content a {display: block;}
  3. #content a:hover {color: fuchsia; }


IE6, IE7: In ol-lists, li:first-line affects the list marker too.

[ testcase ]


IE6, IE7: The :first-line pseudo-element fails to apply a background to the contents of the first line of an element when this element has layout.

[ testcase ]

The :before and :after pseudo-elements

selector specification
generated content specification

IE6, IE7: Not implemented.

[ testcase ]

The :first-child pseudo-class


IE6: Not implemented.

IE7b2: Implemented.

[ testcase ]

  1. li:first-child a {
  2. color: yellow;
  3. }
  4. li:first-child a:hover {
  5. color: teal;
  6. }

A human bug

Human language understanding often leads to a bug that is hard to find (it is not related to any browser), every time one reads it, it sounds ok, like "We declare a rule for all links and then a rule for the visited ones. But why is the visited one oblique, but not gray?"

  1. a:link {color: gray;}
  2. a:visited { font-style: italic;}

The :link pseudo-class applies for links that have not yet been visited. The :visited pseudo-class applies once the link has been visited by the user. ... The two states are mutually exclusive

(CSS 2.1 5.11.2).

:visited but not visited

IE6: When we refer to "#" in <a href="#">link</a> while designing a page, IE declares these links as visited once the page itself is loaded (fixed in IE7b3). This may result in some surprising differences once the references are included.

[ testcase ]

  1. a:link {
  2. color: white;
  3. text-decoration: underline;
  4. }
  5. a:visited {
  6. color: teal;
  7. }
  8. a:hover, a:active {
  9. color: yellow;
  10. }
  12. <a href="#">one</a>

The :active and :focus pseudo-classes


IE6, 7b3: :active is implemented.

IE6, 7b3: :focus is not implemented.

[ testcase ]

As :focus is not implemented, one workaround is the use of the :active pseudo-class:

  1. a:focus, a:active {property: value;}

A link remains :active when the user comes back via the back-button

IE6, 7b3: This can be desired, but is not compatible to other browsers. It can be particulary annoying, though.

[ testcase ]

The :hover pseudo-class


:hover is limited to links in IE6

IE6: Well tested workarounds to achieve whatever:hover are and IE7 { css2: auto; }.

IE7b3: Implemented, see [ testcase ].

Descendant selectors: a child doesn't get the :hover transition

IE6: A frequently asked question is why the following does not work in IE6, but in all other browsers (fixed in IE7b2).

[ testcase ]

  1. a:hover img {border: 1px solid fuchsia;}

And why can a bugfix like the following ever work?

  1. a:hover img {border: 1px solid fuchsia;}
  2. a:hover {background-position: 0 0;}

One hypothesis looks at the signal chain of redraw events:

  1. a_hover(redraw=true)
  2. calls
  3. img(redraw=true)

This hypothesis would say that IE doesn't 'see' the hover transition as a whole unless there is a redraw event triggered – externally by redrawing the window, internally by a repositioning of the background or due to some other subset of properties specified on a:hover. These properties would flag that there is a change on hover to be processed.

A decent irony of this 'logic' is that background-position: 0 0; flags a change on hover, even if there is no background-image at all.

A review of Eric Meyer's Pure CSS Popups shows us that he implicitly fixes IE:

  1. div#links a:hover {
  2. color: #411;
  3. background: #AAA;
  4. border-right: 5px double white;
  5. }
  6. div#links a:hover img {
  7. position: absolute;
  8. ...
  9. }

All these properties call for a redraw, therefore the positioning is processed on hover. A detailed analysis shows the long history of this bug.

Stuck on hover

IE6: Sometimes background-images get stuck on hover. Depending on the relation of the background-image's size to the element's size and other factors (non-transparency), the hover-state may "click in" when the link has "layout". When the pointer leaves the hoverable area, the background is wrongly re-drawn. And the effect vanishes after scrolling or page reload.

  1. a {
  2. display:block;
  3. width: value;
  4. /* or any other layout-trigger */
  5. }
  6. a:hover {
  7. background-image: url(image.jpg);
  8. }

Suppose there is no logic, and no meaning. Suppose bugs are fundamentally absurd

(Bocca della Verità, Rome, on CSS).

A fix would be to "un-layout" the link, but that is mostly impossible due to the layout-dilemma: the width is needed, or it's a float, and so on. When layout elements "are responsible for drawing their own content", guess who is responsible after the :hover-transition is processed?

A better fix, in analogy to the "descendant selectors"-fix above: we could force a redraw, a rearrange on hover.

  1. a:hover {
  2. background: url(image.jpg) 0 0;
  3. }

Alternatively, if background-position cannot be used, we might try to set background-repeat or background-color explicitly. Anyway, the problem of the IE6-flicker still remains, so this problem is related to different bugs.

Sticky hover state on click

IE7: A variation of the stuck-on-hover bug can be seen if a hover state does not switch back after a mouseclick. Click on a drop down menu entry in a Son of Suckerfish drop down menu:

[ Son of Suckerfish single level drop down menu ]

Again, the fix is to apply a substantial change on hover itself:

  1. #nav li:hover {background-position: 0 0;}

See an analysis of the sticky bug in IE7.

IE6 does not show this bug in these Son of Suckerfish menus because the javascript already processes the mouse event for the li.

Part of a floated element disappears on :hover

IE6: This is most probably related to the Guillotine Bug (fixed in IE7b2).

Jump on :hover as a consequence of the re-flow

IE6: It's a basic concept of CSS that a page is incrementally rendered, so the user doesn't have to wait until all elements are loaded. The very special situation in IE by allowing containers to expand in width and height ("expand-to-fit"), even when a dimension is given, forces IE to make assumptions on the real boundary of an element. Now, roughly, assumptions can be wrong. On :hover, IE re-renders the elements of a "layout" block, the elements re-flow, but at this moment, all the dimensions are known to IE (they were determined in the first round). This results in a jump on hover, especially on percentage margins and paddings (e.g. in the Holy Grail layout jump), but can be seen in other bug-related situations too. The jump usually corrects the initial misplacement, the jump often indicates that we have to search for a wrong placement of the element before the hover event is performed.

IE7: Like in IE6, it's a rule of thumb that a relatively positioned container needs haslayout, otherwise strange things like disappearance or jumping of the descendants might occur. A :hover event might be the trigger. Check for the haslayout-state of parent elements in these cases.

Unrelated to this, sometimes the 'trivial' cause of a jump on hover is the declaration of a border at the hover-state only, and a fix is to apply a invisible background-colored similar border on the link.

The sensitive area in which a block level link reacts on hover

The clickable/hoverable area of a block level element is affected by various factors: Block anchors and hasLayout.

The :lang pseudo-class


IE6, 7b3: Not implemented.

[ testcase ]

The chaining of pseudo-classes

IE6: This valid concatenation does not work in IE6/Win. In the example, all hovered links become red (fixed in IE7b2).

[ testcase ]

  1. a:link:hover {color: aqua;}
  2. a:visited:hover {color: red;}

The chaining of pseudo-class and pseudo-element

IE6: CSS allows a construction which does not work in IE6. The first letter is aqua, but there is no reaction on hover (fixed in IE7b2).

[ testcase ]

  1. a {display: block;}
  2. a:first-letter {color: aqua; font-size: 2em}
  3. a:hover {background-color: red;}
  4. a:hover:first-letter {color: yellow;}

A workaround is to change the specificity:

  1. a {display: block; width:3em;}
  2. .cap:first-letter {color: aqua;}
  3. .cap:first-line {color: fuchsia;}
  4. a:hover {background-color: teal;}
  6. <a class="cap" href="#">Lorem ipsum dolor</a>

And by adding more declarations, and by raising !important for IE6, there is a (tortuous) way to style multi-line links and pseudo-elements, including a :hover:first-letter extravaganza:

Lorem ipsum dolor
  1. a {
  2. display: block;
  3. width: 3em;
  4. padding: 0.5em 0.5em 0.5em 1.5em;
  5. border: 1px solid blue;
  6. text-decoration: none;
  7. color: gray;
  8. text-align: right;
  9. text-indent: -1em;
  10. background: black;
  11. }
  12. .cap:first-line {
  13. color: fuchsia !important;
  14. }
  15. .cap:first-letter {
  16. color: aqua !important;
  17. vertical-align: bottom; /*sigh*/
  18. font: 2em georgia, serif;
  19. }
  20. .cap:hover:first-letter {
  21. color: black !important;
  22. }
  23. a:hover {
  24. color: white;
  25. background: teal;
  26. }

This is of course not recommended for several reasons, but it demonstrates that there might be a workaround in IE6 (in case the demand of a combined solution should arise and the budget encloses this torment).

This article was created on July 13, 2005 and last changed on March 26, 2007

Ingo Chao
Special thanks for contributing to:
Philippe Wittenbergh, Georg Sørtun, Bruno Fassino, Cecil Ward and Leif H. Silli
Copyright notice:
This work is published under a Creative Commons license. Photo © Ingo Chao.

Table of content

  1. Pseudo-elements and pseudo-classes
  2. The :first-letter pseudo-element
  3. Syntax
  4. Specificity
  5. Inheritance
  6. Application instability
  7. Ordered and unordered lists
  8. Float
  9. The :first-line pseudo-element
  10. Syntax
  11. Specificity
  12. Lists
  13. Background
  14. The :before and :after pseudo-elements
  15. The :first-child pseudo-class
  16. The :link and :visited pseudo-classes
  17. A human bug
  18. :visited but not visited
  19. The :active and :focus pseudo-classes
  20. A link remains :active
  21. The :hover pseudo-class
  22. :hover is limited to links in IE
  23. Descendant selectors
  24. Stuck on :hover
  25. Sticky hover state on click
  26. Disappearing element on :hover
  27. Jump on :hover
  28. The sensitive area
  29. The :lang pseudo-class
  30. The chaining of pseudo-classes
  31. The chaining of pseudo-class and pseudo-element