Learning To Use The :before And :after Pseudo-Elements In CSS
If you’ve been keeping tabs on various Web design blogs, you’ve probably noticed that the :before and :after pseudo-elements have been getting quite a bit of attention in the front-end development scene — and for good reason. In particular, the experiments of one blogger — namely, London-based developer Nicolas Gallagher — have given pseudo-elements quite a bit of exposure of late.
![]()
Nicolas Gallagher used pseudo-elements to create 84 GUI icons created from semantic HTML.
To complement this exposure (and take advantage of a growing trend), I’ve put together what I hope is a fairly comprehensive run-down of pseudo-elements. This article is aimed primarily at those of you who have seen some of the cool things done with pseudo-elements but want to know what this CSS technique is all about before trying it yourself.
Although the CSS specification contains other pseudo-elements, I’ll focus on :before and :after. So, for brevity, I’ll say “pseudo-elements” to refer generally to these particular two.
What Does A Pseudo-Element Do?
A pseudo-element does exactly what the word implies. It creates a phoney element and inserts it before or after the content of the element that you’ve targeted.
The word “pseudo” is a transliteration of a Greek word that basically means “lying, deceitful, false.” So, calling them pseudo-elements is appropriate, because they don’t actually change anything in the document. Rather, they insert ghost-like elements that are visible to the user and that are style-able in the CSS.
Basic Syntax
The :before and :after pseudo-elements are very easy to code (as are most CSS properties that don’t require a ton of vendor prefixes). Here is a simple example:
#example:before {
content: "#";
}
#example:after {
content: ".";
}
There are two things to note about this example. First, we’re targeting the same element using #example:before and #example:after. Strictly speaking, they are the pseudo-elements in the code.
Secondly, without the content property, which is part of the generated content module in the specification, pseudo-elements are useless. So, while the pseudo-element selector itself is needed to target the element, you won’t be able to insert anything without adding the content property.
In this example, the element with the id example will have a hash symbol placed “before” its content, and a period (or full stop) placed “after” its content.
Some Notes On The Syntax
You could leave the content property empty and just treat the pseudo-element like a content-less box, like this:
#example:before {
content: "";
display: block;
width: 100px;
height: 100px;
}
However, you can’t remove the content property altogether. If you did, the pseudo-element wouldn’t work. At the very least, the content property needs empty quotes as its value.
You may have noticed that you can also code pseudo-elements using the double-colon syntax (::before and ::after), which I’ve discussed before. The short explanation is that there is no difference between the two syntaxes; it’s just a way to differentiate pseudo-elements (double colon) from pseudo-classes (single colon) in CSS3.
One final point regarding the syntax. Technically, you could implement a pseudo-element universally, without targeting any element, like this:
:before {
content: "#";
}
While the above is valid, it’s pretty useless. The code will insert a hash symbol before the content in each element in the DOM. Even if you removed the <body> tag and all of its content, you’d still see two hash symbols on the page: one in the <html> element, and one in the <body> tag, which the browser automatically constructs.
Characteristics Of Inserted Content
As mentioned, the content that is inserted is not visible in the page’s source. It’s visible only in the CSS.
Also, the inserted element is by default an inline element (or, in HTML5 terms, in the category of text-level semantics). So, to give the inserted element a height, padding, margins and so forth, you’ll usually have to define it explicitly as a block-level element.
This leads well into a brief description of how to style pseudo-elements. Look at this graphic from my text editor:

In this example, I’ve highlighted the styles that will be applied to the elements inserted before and after the targeted element’s content. Pseudo-elements are somewhat unique in this way, because you insert the content and the styles in the same declaration block.
Also note that typical CSS inheritance rules apply to the inserted elements. If you had, for example, a font stack of Helvetica, Arial, sans-serif applied to the <body> element of the document, then the pseudo-element would inherit that font stack the same as any other element would.
Likewise, pseudo-elements don’t inherit styles that aren’t naturally inherited from parent elements (such as padding and margins).
Before Or After What?
Your hunch on seeing the :before and :after pseudo-elements might be that the inserted content will be injected before and after the targeted element. But, as alluded to above, that’s not the case.
The content that’s injected will be child content in relation to the targeted element, but it will be placed “before” or “after” any other content in that element.
To demonstrate this, look at the following code. First, the HTML:
<p class="box">Other content.</p>
And here’s the CSS that inserts a pseudo-element:
p.box {
width: 300px;
border: solid 1px white;
padding: 20px;
}
p.box:before {
content: "#";
border: solid 1px white;
padding: 2px;
margin: 0 10px 0 0;
}
In the HTML, all you would see is a paragraph with a class of box, with the words “Other content” inside it (the same as what you would see if you viewed the source on the live page). In the CSS, the paragraph is given a set width, along with some padding and a visible border.
Then we have the pseudo-element. In this case, it’s a hash symbol inserted “before” the paragraph’s content. The subsequent CSS gives it a border, along with some padding and margins.
Here’s the result viewed in the browser:

The outer box is the paragraph. The border around the hash symbol denotes the boundary of the pseudo-element. So, instead of being inserted “before” the paragraph, the pseudo-element is placed before the “Other content” in the paragraph.
Inserting Non-Text Content
I mentioned briefly that you can leave the content property’s value as an empty string or insert text content. You basically have two additional options of what to include as the value of the content property.
First, you can include a URL that points to an image, just as you would do when including a background image in the CSS:
p:before {
content: url(image.jpg);
}
Notice that the quotes are missing. If you wrapped the URL reference in quotes, then it would become a literal string and insert the text “url(image.jpg)” as the content, instead of inserting the image itself.
Naturally, you could include a Data URI in place of the image reference, just as you can with a CSS background.
You also have the option to include a function in the form of attr(X). This function, according to the spec, “returns as a string the value of attribute X for the subject of the selector.”
Here’s an example:
a:after {
content: attr(href);
}
What does the attr() function do? It takes the value of the specified attribute and places it as text content to be inserted as a pseudo-element.
The code above would cause the href value of every <a> element on the page to be placed immediately after each respective <a> element. This could be used in a print style sheet to include full URLs next to all links when a document is printed.
You could also use this function to grab the value of an element’s title attribute, or even microdata values. Of course, not all of these examples would be practical in and of themselves; but depending on the situation, a specific attribute value could be practical as a pseudo-element.
While being able to grab the title or alt text of an image and display it on the page as a pseudo-element would be practical, this isn’t possible. Remember that the pseudo-element must be a child of the element to which it is being applied. Images, which are void (or empty) elements, don’t have child elements, so it wouldn’t work in this case. The same would apply to other void elements, such as <input>.
Dreaded Browser Support
As with any front-end technology that is gaining momentum, one of the first concerns is browser support. In this case, that’s not as much of a problem.
Browser support for :before and :after pseudo-elements stacks up like this:
- Chrome 2+,
- Firefox 3.5+ (3.0 had partial support),
- Safari 1.3+,
- Opera 9.2+,
- IE8+ (with some minor bugs),
- Pretty much all mobile browsers.
The only real problem (no surprise) is IE6 and IE7, which have no support. So, if your audience is in the Web development niche (or another market that has low IE numbers), you can probably go ahead and use pseudo-elements freely.
Pseudo-Elements Aren’t Critical
Fortunately, a lack of pseudo-elements will not cause huge usability issues. For the most part, pseudo-elements are generally decorative (or helper-like) content that will not cause problems in unsupported browsers. So, even if your audience has high IE numbers, you can still use them to some degree.
A Couple Of Reminders
As mentioned, pseudo-element content does not appear in the DOM. These elements are not real elements. As such, they are not accessible to most assistive devices. So, never use pseudo-elements to generate content that is critical to the usability or accessibility of your pages.
Another thing to keep in mind is that developer tools such as Firebug do not show the content generated by pseudo-elements. So, if overused, pseudo-elements could cause maintainability headaches and make debugging a much slower process.
(Update: As mentioned in the comments, you can use Chrome’s developer tools to view the styles associated with a pseudo-element, but the element will not appear in the DOM. Also, Firebug is adding pseudo-element support in version 1.8.)
That covers all of the concepts you need in order to create something practical with this technique. In the meantime, for further reading on CSS pseudo-elements, be sure to check out some of the articles that we’ve linked to in this piece.
(kw)





Dominik Porada
July 14th, 2011 4:36 amWhen you apply `float` on an element or pseudo-element, the `display:block` is redundant—it becomes `inline-block` automatically.
Louis
July 14th, 2011 5:26 amActually I believe it becomes “block” (unless there’s any evidence to suggest otherwise), but yes that’s a good point.
In that case, however, I wanted to make sure it was clear that a pseudo-element is naturally an inline element, and so it needs to be explicitly defined as a block-level element.
Dominik Porada
July 15th, 2011 5:10 amI think if you removed that `float:left`, the example would be even more comprehensive. Then, the code would clearly indicate the pseudo-element is `inline` by default, and that `display:block` is enough to make it block-level, without unnecessary properties.
Louis
July 15th, 2011 5:41 amYou’re right. I’ve corrected it. :)
Josh Humble
August 9th, 2012 5:17 amI know this is an old post, but doesn’t applying the float ALSO allow the element to contain its content? I recently had an issue with a nested span where both it and its parent span were declared block level, but for the nested span to properly contain its text, the addition of “float:left” worked. They both also had an overflow value of hidden, and widths set.
AndyW
July 14th, 2011 5:02 amThe :after selector comes in quite handy if you want to create self clearing floats (that is, as long as you don’t mind it not working too well in older browsers).
Blake
July 14th, 2011 6:21 amIf by older browsers you mean IE7 there is a bit of CSS you can use to fix some problems. Like if you did this:
.container:after {
content: “.”;
display: block;
height: 0;
clear: both;
visibility: hidden;
}
Then after you could do this:
.container {
display: block;
zoom:1;
}
to make IE7 work better.
Stephen Greig
July 14th, 2011 5:03 amNice post! Very well explained.
I’ve done a few logos in CSS3, with the help of pseudo-elements :)
http://www.tangledindesign.com/blog/tag/famous-logos-in-css3/
Kanchan Rai
July 15th, 2011 4:29 amHey there, cool logos and thanks for sharing those tutorials!!
It’s funny how IE and other older browsers render them though!!
Stephen Greig
July 20th, 2011 5:57 amYep… but just think, one day those browsers WILL die out and we will be able to use CSS3 to our hearts content!
Scott
September 25th, 2012 6:33 amAnd by that time CSS3 will be obsolete and we’ll be on CSS5 or something.
Ahmed El Gabri
July 14th, 2011 5:15 am“Another thing to keep in mind is that developer tools such as Firebug do not show the content generated by pseudo-elements. So, if overused, pseudo-elements could cause maintainability headaches and make debugging a much slower process.”
actually Firebug now supports showing pseudo-elements http://cl.ly/0L2T1p2E1X1m3j150D2D
Louis
July 15th, 2011 5:45 amYou’re partly correct, Ahmed, thanks for pointing this out. Keep in mind that the pseudo-element is still not displayed in the DOM, only in the styles pane.
Also, the current stable release of Firebug (1.7.3) does not offer this feature. The ability to view pseudo-elements in the styles pane is coming in version 1.8, which is now in Alpha. See:
http://getfirebug.com/wiki/index.php/Firebug_Release_Notes#CSS
Nonetheless, I’ll add a note to the article. Thanks again.
Alan Vitek
July 14th, 2011 5:18 amNot a huge developer in terms of coding, but I really enjoyed how this article was written. Easy enough to understand; I want to experiment with the :before and :after properties soon! Thanks Smashing and Louis :D
Overall, I feel that including a few of these things here or there would be nice, but I wouldn’t want to maintain large amounts of them.
Jan
July 14th, 2011 7:08 amI can only agree :) Well done!
Tom
July 14th, 2011 5:32 amThanks for the extra advice:)
Kt
July 14th, 2011 5:52 amThanks for this- I recently had a heck of a time trying to figure out why :after didn’t actually insert an element after the div, but rather as a child. Seems like a bit of a misnomer. Seems like it should be called IncludeAfter or something along those lines…
Louis
July 14th, 2011 6:18 amYeah, that can be a bit confusing to beginners I think, but it’s simple enough once you get it. Seems it would be more appropriate if they were called “:before-content” and “:after-content” or something like that.
Leonardo Rothe
July 14th, 2011 6:05 amGenerated content comes handy for some use cases (which I like to think I pioneered myself):
1.Using it with :empty pseudoclass to target empty elements which would be otherwise filled with dynamic data:
#result-list:empty:before { content: “There are no items to display right now”; … }
2.Using it with elements to add colons or an asterisk on mandatory items (and not having them in the HTML):
label:after { content: “:”; }
.mandatory label:before { content: “*”; color: red; }
3.Using it with recurring text, especially with elements:
.post time.published:before { content: “Published on “; }
.comment time.ago:after { content: ” ago”; }
Shane Stocks
July 14th, 2011 6:25 amI think the usability of :before and :after is rather limited, and other, more standard CSS approaches are probably more less redundant.
The only use I see of these pseudo elements are to decorate a link with an image :before it’s content, like wikipedia or something.
Other than that, I see no good use to the before and after elements. A well written article however.
Aniket Pant
July 14th, 2011 6:43 amYou need to read this article then.
http://css-tricks.com/9516-pseudo-element-roundup/
Many things can be done with pseudo elements.
Sara
July 14th, 2011 8:33 amThat article is a hot mess in IE 9…just sayin’…no solution is fool proof.
kamalendu garai
July 15th, 2011 12:12 amHey Sara.. check my sites with js polyfill…you will get it working
Pablinho
July 14th, 2011 6:48 amGreat use of pseudo elements.
But as you mentioned, the lack of support by Firebug makes debugging quite hard. Hopefully they’ll fix this in the near future.
John
July 14th, 2011 7:39 amNice and well-written article.
Google Chrome’s dev tools can actually view and edit pseudo-elements (you have to enable it in a menu somewhere IIRC). Firebug should definitely add this feature!
Louis
July 14th, 2011 8:09 amIt turns out, you can view and edit the styles applied to the pseudo-element in Chrome dev tools, but I don’t think there is any way to view the element in the DOM. I’ll add a note to the article for this. Thanks.
darwin
July 14th, 2011 7:58 amnice article :)
Paul
July 14th, 2011 8:58 amWhy not just use a REAL element?
Stefan Bergfeldt
July 14th, 2011 10:40 pmBecause you want to keep your markup as clean as possible.
Davide De Maestri
July 14th, 2011 9:54 amI think it’s still too early to use it in complex projects…
Stefan Bergfeldt
July 14th, 2011 10:41 pmThat’s the point, if you use it right, it’s not to early.
If you over-use it or depend on it, it’s too early.
Just like any new feature
Karl
July 14th, 2011 9:54 amI recently posted some examples using pseudo-elements, @font-face, unicode, and css3 animations over at http://test.learningjquery.com/unicode/
Brett Jankord
July 14th, 2011 10:42 amLouis, this is a nice write up on :before and :after pseudo elements! I also enjoyed reading your post on the difference between :before and ::before a while back.
One thing I’ve been thinking about in regards to these pseudo elements, is support on mobile devices. In my opinion, “Pretty much all mobile browsers.” seems like a very general statement when talking about their support, maybe a better statement would be, “Pretty much all MODERN mobile browsers.” I haven’t really seen any user data on their support on mobiles, so I’m just assuming based on what I’ve tested. I checked to see if they worked on my Android phone and my friends iPhone.
I think it would be great if Smashing Magazine, or anyone with a large group of users, set up some type of poll with a test page with pseudo elements so users could test on their own mobile device and report back if it works. I’d love to see that data.
Louis
July 14th, 2011 11:13 amThanks, Brett.
You’re probably right that such a statement is too general. But in this case, we’re talking about something that is not crucial to the functioning of a web page, so I felt it was fine to say that.
Nonetheless, I believe my statement was just being overly careful due to the fact that I have no way of confirming first hand where it works and where it doesn’t.
I think it works in all in-use mobile browsers — aside from maybe the odd 5-year old (or older) browser, which would be irrelevant. Especially when you factor in again that this is non-crucial, decorative content. My general statement, if I remember correctly, is based on the results in this chart:
http://caniuse.com/#search=pseudo
Brett Jankord
July 15th, 2011 5:33 amAwesome, thanks for the link. Based on that chart it looks like I just need to test these on a few Windows phones and Blackberry and should have a could set of data for their support on in-use mobiles.
Matt
July 14th, 2011 4:45 pmCompletely unrelated, but what font are you using in your text editor? It looks nice from the screen grab!
Great article, btw!
Louis
July 14th, 2011 7:33 pmMatt and I already discussed this on Twitter, but for those interested, I’m currently using Notepad++ with the Zenburn theme tweaked to my liking. I believe the font is Consolas, but it doesn’t seem to indicate the font in the theme’s setting.
Chris Coyier
July 14th, 2011 4:47 pmAwesome article Louis! Good on ya for spreading the word about pseudo elements. I hope more people get excited about them, which in turns excites browser vendors to get excited and implement more of their features (for example, the ability to animate or transition them which is currently limited to Firefox 4+).
Regarding the double-colon syntax thing, I believe it’s only IE 8 that has an issue. It does support singe-colon, but not double. That’s reason enough to use the single-colon format in all your CSS (despite the spec). I also agree with your sentiment that it’s “too late” for browsers to ever stop supporting single-colon.
One thing not mentioned that is a possible value for the content property is “counter”. This article shows it can be particularly awesome: http://www.456bereastreet.com/archive/201105/styling_ordered_list_numbers/
Prabaharan CS
July 14th, 2011 10:09 pmGreat work guys, hope I can use this technique in few months….. waiting for decrease in rate of ie7 users.
Keep it up guys…
kamalendu garai
July 15th, 2011 12:11 amAs those element does not appear in the DOM, you should use this technique to implement not to add descriptive content like text that is describing something. You should use this more logically, like in footer navigation most of us has a tendency to separate all those tabs with a pipe ( | ) sign. But, that we do to separate those visually. Those pipe sign is nothig to do with the content. even search engine dont understand those as those are illogical. So, you can set there by ” li:before{content:”|”; width:2px; display:block; float:left}”. Take a look http://www.guarderia.ch/
And be definite that below IE9 is not going to support this coding so you need to some kind of css3 – js polyfill / shim to give a support. I will advice ie9.js.
Another case could be to add a background image, some time you need to have two background image for the tag. “body:before{content:”"; background:url(images/…) no-repeat; height:100px; width:400px; display block; } with positioning could set it there. Check this site I have made like this way… http://visualpleasure.cz.cc/
Sascha
July 15th, 2011 12:37 amWow, great article. Helps me a lot. thanks
ian
July 15th, 2011 12:40 amnice one
Max
July 15th, 2011 2:15 amVery helpful article! It’s the first time, I hear about :before and :after. I’ll try that right now.
Which doctype should I use? HTML5 or can I leave XHTML 1.0 strict?
Louis
July 15th, 2011 5:50 amDoctype has no effect on this sort of thing. But it is best practice now to use an HTML5 doctype, even if you don’t plan to actually use any new HTML5 features.
Yorik W
July 15th, 2011 5:25 amInteressant article, 2 weeks ago i work on css :before and :after .
And a little tips :
#div ul li a :before
#div ul li a :hover:before
Davi Lima
December 17th, 2012 1:42 pmWhat does the first tip mean?
Ara Garabedian
July 15th, 2011 6:10 amMy favourite use for the :before and :after pseudo elements to clear collapsing margins on child elements as shown on the YUI blog (http://j.mp/bestclearfix).
This method is used in the html5 boilerplate as part of the default CSS – http://html5boilerplate.com/
Check it out if you haven’t already used this technique.
Marcin Grzywaczewski
July 16th, 2011 4:00 pmI’d like to add: Actually, the newest (CSS3 one) specification requires pseudo-elements be indicated using double colon syntax.
So, for example:
p:first-of-type { color: red; } /* :first-of-type is a pseudo-CLASS – we indicate it using one colon syntax. */
But:
p::before { content: “Paragraph states: “; } /* ::before is a pseudo-ELEMENT – so we could indicate it using double colon syntax. */
It also applies to ::after, ::first-line, ::first-letter and (currently not widely supported) ::selection pseudo-elements. I think it’s not much hassle to support CSS3 syntax right now – IE6 and IE7 doesn’t render ‘before’ and ‘after’ pseudo-elements at all. Problem is with IE8 – it doesn’t render ‘before’ pseudo-element when double colon syntax is applied.
onioneye
July 16th, 2011 5:07 pmCongratulations on a great article, Louis!
I’ve been using pseudo elements in my designs for about a half a year now, for things like double borders, multiple backgrounds, float clearing, and a bunch of other stuff, but I’ve only now discovered that css3 transitions don’t work on these types of elements. That’s quite a bummer, but I guess I can live with that. :)
BTW folks, please remember that when you use special characters in the content property, you need to convert them to a hex value. You can read more about it on Chris’ awesome blog: http://css-tricks.com/6555-css-content/
onioneye
July 16th, 2011 5:29 pmI just wanted to share one more thing. This little script: http://code.google.com/p/ie7-js/ (IE9.js) enables :after and :before pseudo elements in IE7 and CSS3 selectors in both IE8 and IE7, while fixing numerous bugs in those browsers as well. However, I’m not sure if it enables the use of a double-colon syntax.
Here’s a list of all things that it fixes/adds support for: http://ie7-js.googlecode.com/svn/test/index.html
Slob Jones
July 17th, 2011 1:25 pmThis is one of those extremely rare, clearly written tutorials. I actually understand pseudo elements now, and didn’t even have to beat my head against a wall.
Nice job, Lou.
Dan
July 18th, 2011 8:38 amGreat article, thanks.
Although I do see great uses for this functionality, I see this causing great headaches also. Especially when debugging dynamic sites (asp net etc.) Since this crosses the ideology of keeping style separate from content. I guess as long as debugging tools like the built in chrome and firebug handle this gracefully it might not be such an issue.
Deepu Balan
July 19th, 2011 2:23 amThanks Louis… Very well explained writeup…!
I have a created a pure CSS dropdown menu using :target pseudo class, you may check it out here: http://deepubalan.com/blog/2011/06/17/free-pure-css-dropdown-menu-using-target-pseudo-class/
Thanks,
-Deepu
Fatih Toprak
July 25th, 2011 8:57 amNice job. Thank you .
Hugo
August 6th, 2011 2:33 amFor the content attribute use ’020′ for elements you just want to style, it is a blank character which has no width nor height so doesn’t require you to tweak your values from the expected; here is an example from a stylesheet of mine –
https://gist.github.com/1129254
Hugo
August 6th, 2011 2:35 amImproper escaped the code, check the gist for the real code
Hamed Momeni
October 4th, 2012 11:01 amI’ve been seeing these pseudo elements more that often lately on the net. Guess they are more useful than I thought they were.
Alex
October 30th, 2012 10:39 amTwo things:
1) Why? Are pseudo elements rendered faster (assuming no data uri)
2) Why x2? At least as a PHP developer, if I want an image inserted before an element, I would never want to hardcode the URL of the image.
Graham
February 27th, 2013 1:41 amThank you for having such a thorough explanation of this. I literrally had never heard of these about half an hour ago, but now know exactly how this piece of code I found works. My mind is blown by this. It has such great potential. As for IE support. I really wish it would die a horrible death. I care not for it.
Husien Adel
March 10th, 2013 2:24 amthanks a lot for great tutorial and may be the section with attr need more clear ;)
Hadi Triyanto
May 21st, 2013 5:49 pmHello thanks for this nice info. I have a problem when I insert ‘before’ css on my hover menu. It’s taking slow down if we use chrome. But if I delete the css it’s work fine. Do you know why? Thanks Mr. Louis.
This the site sepatuonline-murah.com