Wrox Home  
Search
Professional CSS Cascading Style Sheets for Web Design
by Christopher Schmitt, Mark Trammell, Ethan Marcotte, Dunstan Orchard, Todd Dominey
July 2005, Paperback


Excerpt from Professional CSS Cascading Style Sheets for Web Design

Bending Our Own Rules with the :before and :after Pseudo-Elements

Hurrah for CSS, and hurrah for its surprising ability to create and insert content into a document.

The :before and :after pseudo-elements are two spankingly gorgeous (and fairly advanced) bits of CSS and they are exactly what we're looking for. They allow us to insert content (in this case, two images) into the box's content area, and then style it as we wish. In this way, we're not relying on content generated by the user to style our box. We're inserting some of our own.

Once they've been inserted, we can treat these pseudo-elements like spare divs, adding in content (via the content property), giving them padding, borders, margins, background colors — whatever we want, and all through a few additional lines in our style sheets.

Now, you may be thinking, "Ahem, didn't we just agree not to use an element's contents to style the element itself?" You're right, but that's why this section is titled "Bending Our Own Rules . . ." If we insert the content, and we do so using CSS, then we skip around all the problems mentioned in the previous section. It is our (virtually) perfect solution.

But enough sidetracking; let's get back to making these curves work. If we once again look at our XHTML, we can see where our CSS is going to insert the :before pseudo-element:


<div id="sidebar">
<div class="box">
OUR :BEFORE PSEUDO-ELEMENT IS INSERTED HERE
<h2>Users say...</h2>

<p><q>I love Blogger - it has been a great addition to 
my life online!</q> - Roseanne</p>

<p><q>You guys do a wonderful job... Thanks for the exemplary service 
in pioneering mass, user-friendly, web logs on the internet. 
Seriously... I, along with thousands of other users, 
definitely appreciate the service you provide.</q> - Josh</p>

<p><q>Thanks, your system is perhaps the easiest content 
management application I've ever seen... 
It simply amazes me how easy it is, and I've been working 
with computers for 20 years.</q> - Michael</p>
</div>
</div>

Figure 1 shows where the new element will appear.

Figure 1
Figure 1: Indication of where the :before pseudo-element will appear

This is a pretty powerful thing to be able to do when you think about it. We're using CSS not just to style something, but to actually generate content and insert it into the document.

Here's how we do it. First we create our pseudo-element:


.box:before {

}

Then we use the content property to insert an image into the document. You can see the new image appear in Figure 2, just above the heading "Users say . . . "

.box:before {
content: url(box-top.gif);
}
Figure 2
Figure 2: New image

Although we've managed to insert an image using the CSS content property, it's not exactly perfectly aligned. Why is this? Well, remember the 8px of padding and 2px of border that we applied to the box earlier on? Those two properties are vertically and horizontally offsetting the pseudo-element, by a total of 10px, as shown in Figure 3.

Figure 3
Figure 3: Padding and border applied to the box offset the :before pseudo-element

We must find a way to counteract this offset, and move the new image into its correct position, at the top of the box.

We start doing that by giving the pseudo-element a top-margin of -10px. This "pulls" the pseudo-element up and sits it flush with the top of the box. We also set display: block because only block-level elements can have margins applied to them, as shown in Figure 4:

.box:before {
content: url(box-top.gif);
display: block;
margin: -10px 0 0 0;
}
Figure 4
Figure 4: "Pulling" the pseudo-element up

We then add in negative values for both the left and right margins, to pull the pseudo-element into place horizontally, again counteracting the 8px of padding and 2px of border we gave the box, as shown in Figure 5:

.box:before {
content: url(box-top.gif);
display: block;
margin: -10px -10px 0 -10px;
}
Figure 5
Figure 5: Positioning the pseudo-element horizontally

It's looking pretty good now, but we have two more things left to do.

First we have to pull the text up, so the heading ("Users say . . . ") sits in the correct place. We do this by applying another negative margin to the pseudo-element, this time a bottom margin of -22px. We also apply a top margin of 0 to the h2, as shown in Figure 6:

.box:before {
content: url(box-top.gif);
display: block;
margin: -10px -10px -22px -10px;
}

h2 {
margin-top: 0;
}
Figure 6
Figure 6: Positioning the heading text

And, finally, we must take care of a little problem caused by line-height.

Line-height is the vertical distance between two lines of text. Set a large line-height and it increases the vertical gap between the lines; set a small line-height and the lines of text can start to overlap, as shown in Figure 7.

Figure 7
Figure 7: The CSS property line-height affects the vertical spacing between lines of text.

Although we haven't specifically set a line-height on the :before pseudo-element, it's inherited a value from the default styles applied to the HTML document by the Web browser.

This vertical spacing can often cause problems when playing with pixel-perfect design, and in this case it's introducing a very small gap underneath our box-top.gif image. We don't notice this gap because the background of the pseudo-element is transparent. But, if we change the background-color to green, the gap becomes more apparent, as shown in Figure 8.

Figure 8
Figure 8: Line-height can cause unexpected vertical spacing problems (shown here as a darker line).

Even though it's not affecting our design at this moment, it's a good idea to cancel out line-height when you can. In this case, we'll do it by setting the line-height to 0.1, as shown in Figure 9:

.box:before {
content: url(box-top.gif);
display: block;
line-height: 0.1;
margin: -10px -10px -22px -10px;
}
Figure 9
Figure 9: Rounded corners on the top of the box

And now we have rounded corners and a nicely blended background, all through the power of CSS, and not an extra div or table in sight.

All that's left to do now is use the same technique to apply rounded corners to the bottom of the box as well.

Rounding the Bottom of the Box

This time we'll be using the :after pseudo-element, which is inserted after the rest of the box's content:


<div id="sidebar">
<div class="box">
<h2>Users say...</h2>

<p><q>I love Blogger - it has been a great addition to 
my life online!</q> - Roseanne</p>

<p><q>You guys do a wonderful job... Thanks for the exemplary service 
in pioneering mass, user-friendly, web logs on the internet. 
Seriously... I, along with thousands of other users, 
definitely appreciate the service you provide.</q> - Josh</p>

<p><q>Thanks, your system is perhaps the easiest content 
management application I've ever seen... 
It simply amazes me how easy it is, and I've been working 
with computers for 20 years.</q> - Michael</p>

OUR :AFTER PSEUDO-ELEMENT IS INSERTED HERE
</div>
</div>

Figure 10 shows where the new pseudo-element will appear.

Figure 10
Figure 10: Indication of where the :after pseudo-element will appear

First, let's create the :after pseudo-element:

.box:after {

}

Now let's insert the image (box-btm.gif) using the content property, and make the whole pseudo-element block-level (see Figure 11):

.box:after {
content: url(box-btm.gif);
display: block;
}
Figure 11
Figure 11: Inserting the image and making the pseudo-element block-level

Next we apply a negative bottom margin, to pull the pseudo-element down toward the bottom of the box, as shown in Figure 12. (Remember, it's being offset by the 8px of padding and 2px of border.)

.box:after {
content: url(box-btm.gif);
display: block;
margin: 0 0 -10px 0;
}
Figure 12
Figure 12: Pulling the pseudo-element toward the bottom of the box

Notice that it hasn't been pulled perfectly into place? That's line-height at work again. If we set that to 0.1, we'll see the image line up perfectly with the bottom of the box (see Figure 13):

.box:after {
content: url(box-btm.gif);
display: block;
line-height: 0.1;
margin: 0 0 -10px 0;
}
Figure 13
Figure 13: Lining up the image with the bottom of the box

We then apply negative left and right margins to pull the pseudo-element into place, horizontally, as shown in Figure 14:

.box:after {
content: url(box-btm.gif);
display: block;
line-height: 0.1;
margin: 0 -10px -10px -10px;
}
Figure 14
Figure 14: Pulling the pseudo-element into place horizontally

And finally, just to be on the safe side, we'll apply a clearing rule to the pseudo-element:

.box:after {
content: url(box-btm.gif);
clear: both;
display: block;
line-height: 0.1;
margin: 0 -10px -10px -10px;
}

That rule has no effect in this example, but it's helpful to have in there for safety's sake. If we didn't insert it, and the final elements in the box were floated, it could potentially cause a problem.

And there we have it: rounded corners, lovely clean markup, and some fancy-pants CSS (see Figure 15).

Figure 15
Figure 15: Rounded corners on the top and bottom of the box