:after{awesome!} is :before{CSS}

Or, "How to replace an entire text title with CSS".

For the longest time, I thought it was a cardinal rule of CSS: You can add pictures, but you can't add text. I must have been aiming for extremely old browser support when I learned this, probably IE6, because with modern browsers it's clearly wrong. Not only can you add text to a page with CSS, I've found a way to actually replace small pieces of text like short headings.

The :before and :after pseudo-element selectors insert content on a page, in what amounts to a span that doesn't actually appear in the DOM. From now on I'll call the pseudo-element created by these selectors a "pseudo-span". From their names and usage, you might think that div:before { content:"nice"; } would turn:

<div>trick</div>

into

<pseudo-span>nice</pseudo-span>
<div>trick</div>

But it doesn't. It actually looks like this, or would if the <pseudo-span> tags existed:

<div><pseudo-span>nice</pseudo-span>trick</div>

It adds the pseudo-span as the first child of the selected element, "before" any and all other children. Likewise, :after adds a child after all the other child elements, but still within the referenced element. Among other things, this means you can't add text to elements that don't contain anything, like <img> or <input>.

This also means that any styles applied to the parent element apply to our pseudo-spans as well. Unless we modify the pseudo-spans. You can add CSS properties to a :before or :after element just like they were spans. The common "clearfix" class to push part of a document after all floated content uses an invisible "." in a highly-modified :after, for instance.

So, I was having fun adding bits of text here and there, :before this and :after that. Then I ran a problem like this:

Existing HTML: <h2 class="myheading">Bad Heading</h2>
Required HTML: <h2 class="myheading">Good Heading</h2>

Well, I thought, the "Heading" part is OK, but I've got to get rid of the "Bad". OK, maybe I could shift the heading over so "Bad" is hidden, if I get the right number of pixels for the font, which may not be the same on all browsers and OSes...Argh! Stop! There's got to be a better way!

The next thing I thought of was this common trick I'd seen to replace a text link with an image. Basically:

a.toimage {
background: url("/path/to/image.png") no-repeat;
height: 42px;
width: 142px;
display: block;
overflow: hidden;
text-indent: -9999px;
}

The important bit here is the last two lines. Text-indent shoves the text way over to the left, and overflow:hidden makes sure the text disappears. So, I wondered if I could do the same with my heading problem. It turns out it's not quite as easy as that, but this works:

h2.myheading {
position: relative;
overflow: hidden;
text-indent: -9999px;
}

h2.myheading:before {
content: "Good Heading";
position: absolute;
left: 9999px;
}

So, how's this work? Well, first, you see those same two lines at the end of h2.myheading. Position:relative means we're going to do something with absolute positioning inside the element, and in the :before you see that we do. Content is obviously the new text I want. But why left: 9999px?

OK, consider what happens when I remove both properties with "9999px". I now have:

<h2 class="myheading"><pseudo-span>Good Heading</pseudo-span>Bad Heading</h2>

But because the pseudo-span is positioned absolutely within the h2, I get both pieces of text overlapping. Next, I shove the old text to the left with text-indent, but since the pseudo-span is a child of that element, it gets shoved over there too. They still overlap, but you can't see it. Finally, I use left:9999px to shove the new text back where the old text was. VoilĂ , the text in the heading is replaced!

Obviously, this technique has limitations. It wouldn't be easy to do for large paragraphs of text. If you have easy access to the original source, changing that is always better. But when you can't, I find this to be a really nice option.