How To Use Structural Pseudo Classes and Pseudo Element Selectors

The last couple of weeks we’ve been looking at different types of css selectors. First we looked at the simple and attribute selectors and then we looked at combinators and some pseudo class selectors.

This week we’ll finish up with structural pseudo-classes and pseudo elements. I think you’ll find both have some practical applications for styling web pages.

Note: Once again a reminder that the numbers in parenthesis represent which level css the selector was introduced in.

Structure, Saint Jean Cap Ferrat, France

Structural Pseudo-Classes

Structural pseudo-classes were introduced as a way to target html elements based on information in the document tree that aren’t easily represented by simple selectors or combinators. Most were introduced in css3.

You may already be using a few of these. If you aren’t you likely find yourself wanting to target the elements they select. Without them we generally have to add a class or id to the element we’re targeting.

E:root (3) — matches an element E that’s the root of the document. In html this is always the html element.

{code type=css}
:root {background: blue;}
html {background: blue;}

The lines of css above are functionally equivalent. Perhaps not the most useful structural pseudo-class, but the easiest to explain.

E:nth-child(n) (3) — matches an element E that’s the nth child of its parent.

Assuming we have a table with a large number of rows.

{code type=css}
tr:nth-child(4) {background: #ccc;}

The css above says to find the 4th row and give it a background color that’s a light gray.

A common use of the nth-child pseudo-class is coloring alternate rows of a table.

{code type=css}
tr:nth-child(2n+1) {background: #ccc;}
tr:nth-child(2n) {background: #eee;}

2n+1 represents odd numbered rows and 2n represents even numbered rows. Special odd and even values can also be used.

{code type=css}
tr:nth-child(odd) {background: #ccc;}
tr:nth-child(even) {background: #eee;}

You aren’t limited to 2 though. Any number can be used.

{code type=css}
tr:nth-child(10n+1) {background: #ccc;}

Here the 11th, 21st, 31st, etc rows would be given a light gray background.

E:nth-last-child(n) (3) — matches an element E that’s the nth child of its parent, counting from the last child.

nth-last child works similarly to nth-child, except that the count starts from the last element in the list as opposed to the first. Again assuming a table with a large number of rows.

{code type=css}
tr:nth-last-child(1) {background: #ccc;}

Here the last row is the one to get the background color.

{code type=css}
tr:nth-last-child(4) {background: #ccc;}

Here the 4th last row gets the background color.

All the same values can be used

{code type=css}
tr:nth-last-child(2n+1) {background: #ccc;}
tr:nth-last-child(2n) {background: #eee;}

{code type=css}
tr:nth-last-child(odd) {background: #ccc;}
tr:nth-last-child(even) {background: #eee;}

Both of the above would result in all odd and even rows getting different background colors. While you probably wouldn’t use nth-last-child for these, you could.

More typical use of nth-last-child is to target a specific or few specific items at the end of the list.


:nth-of-type and :nth-last-of-type

The rest of the structural pseudo-classes are plays on the above two so if you understand how the above two work all you’ll need to understand the rest is how they differ.

E:nth-of-type(n) (3) — matches an element E that’s the nth sibling of its type

E:nth-last-of-type(n) (3) — matches an element E that’s the nth sibling of its type, counting from the last one

nth-of-type() and nth-last-of-type() work the same way nth-child() and nth-last-child() work, the difference being that what we’re targeting has to be a specific type of element as opposed to any child element.

You’d use the of-type classes when the parent of the element you’re targeting has different types of elements as children.

{code type=html}

< img src="" alt="" / >


{code type=css}
p:nth-of-type(2) {font-size: 1.2em;}
p:nth-last-of-type(1) {font-size: 1.2em;}

I’m sure you can figure out which paragraphs are targeted by the css above Again we’re using the of-type pseudo-classes because the elements inside the div are of mixed type.

:first-child and :last-child

These two pseudo-classes are shortcuts for nth-child(1) and nth-last-child(1). Like the two nth classes these require all the children to be of the same type.

E:first-child (2) — matches an element E that’s the first child of its parent

E:last-child (3) — matches an element E that’s the last child of its parent

A common use is in navigation bars when you want to style either the first, last or both menu items in the list differently than those in the middle of the bar.

Often I’ll add a border between menu items in navigation bars by setting either a left or right border. However that necessitates either adding a border to one end or removing it from the other. li:last-child and li:first-child come in handy for this.

:first-of-type and :last-of-type

These two elements are the functional equivalent of nth-of-type(1) and nth-last-of-type(1). They work the same as first-child and last-child except that they only target an element of a specific type.

E:first-of-type (3) — matches an element E that’s first sibling of its type.

E:last-of-type (3) — matches an element E that’s last sibling of its type.

Styling the first of last paragraph inside a div, article, or section might be a common use for the first and last of-type classes.

only-child and only-of-type

These next two elements are again similar except that they only target elements with a single child.

E:only-child (3) — matches an element E that’s the only child of its parent.

E:only-of-type (3) — matches an element E that’s the only sibling of its type.

With only-child you’d have only the one element inside the parent. With only-of-type you could have multiple children, but only the one type of element you’re targeting.


E:empty (3) — matches an element E that has no children (including text nodes)

:empty is a way to target empty tags. For example it wouldn’t match

{code type=html}

Some text


It would match

{code type=html}


Ideally you won’t leave any empty tags in your html so I’m not sure how useful this one is. Perhaps as a way to find and remove any empty tags you’ve accidentally left behind.

Pseudo Elements

There are some things we want to style in practice, but have no way at all to select based on information in the document tree.

Normally to select these items we’d have to add an element like a span and also give it some kind of class. Pseudo elements are a way to target these items.

We have 4 pseudo elements we can target

::first-line pseudo element

E::first-line (1) — matches the first formatted line of an element E.

{code type=css}
p:first-line {text-transform: uppercase;}

The above changes the first line in all paragraphs to uppercase. Of course the first line of text can be somewhat dynamic if you’ve created a flexible layout and the browser width is varied.

::first-letter pseudo element

E::first-letter (1) — matches the first formatted letter of an element E.

Similar to first-line, first-letter styles only the first letter differently. An obvious use is to create drop caps.

{code type=html}

This is the first paragraph


{code type=css}
p.intro::first-letter {font-size:4em; font-weight:bold; color: #f00; float:left; }

::before and ::after pseudo elements

E::before (2) — matches generated content before an element E.

E::after (2) — matches generated content after an element E.

The ::before and ::after pseudo elements are ways to add content before or after an element. Common uses are to add a quote image before and after blockquotes or to use your own special bullet in front of a list.

{code type=html}


{code type=css}
blockquote::before {content: “2018”;}
blockquote::after {content: “2019;”;}

The above adds a single left quotation mark before the blockquote and a single right quotation mark after it.

Update: Originally I had html entities for the value of the quotes above, but as Michael pointed out in the comments below, css won’t parse the html entity. You need to use escaped unicode values instead. Alternatively you could set the character encoding of the page as @charset “UTF-8”

While there will be times when you don’t specifically want to add any content you still need to include the content property with ::before and ::after. Leaving it blank is allowed, but it must be included.

{code type=css}
blockquote::before {content: “”;}

You can do quite a lot with these two pseudo elements as seen in the video above or this demo for building a css solar system.

Double or Single Colon?

One note about all 4 pseudo elements is their use of a double colon out front.

This is technically the correct syntax however browsers that accept pseudo elements also allow a single colon and the double colon doesn’t work in all browsers.

For practical purposes you’re better off using the single colon for now so:

  • :first-letter
  • :last-letter
  • :before
  • :after

Browser Support

Generally browser support for pseudo elements is better than the structural pseudo classes, though it varies some. The elements will work as far back as IE8 and with first-letter and first-line support goes back to IE5.5. Yep, you read that right.

Most of the structural pseudo classes don’t start working until IE9, though support for first-child goes back to IE6.


Like the other selectors we’ve covered you may have already used some of these in practice, while others may be completely new to you.

You’ll find more support for the pseudo elements than the structural pseudo classes, but you can likely find practical uses for most everything mentioned here.

I hope this walk through of css selectors has been useful and brought to light a few selectors you may not use or be familiar with. I know that’s been the case for me and hopefully these posts will serve as a reminder for both of our development practice.

« »

Download a free sample from my book, Design Fundamentals.


  1. Definitely helps make sense of pseudo elements. I have, however, spotted a little typo:

    p:first-line {tex-transform: uppercase;}

    Should be

    p:first-line {text-transform: uppercase;}

  2. Hi Steve –

    Great article. Very informative and helpful.

    Quick question:

    This code in my stylesheet isn’t working:

    q:before {content: ““”}
    q:after {content: “””}

    It’s simply displaying the characters between the quotes on the web page; entities are not being recognized.

    Any idea what the problem may be?

    Thanks. MB

  3. Found the answer, in case you’re interested:

    CSS doesn’t parse HTML. So HTML entities as values for the CSS content property are rendered as plain text.

    In CSS, escaped characters must start with a back slash and be followed by the hex Unicode value. Thus, your example above:

    blockquote::before {content: “&lsquo;”}
    blockquote::after {content: “&rsquo;”}

    will not work. It will render “&lsquo;” and “&rsquo;” (as I found out when trying to set-up the quote property in my stylesheet).

    The correct code would be:

    blockquote::before {content: “2018”}
    blockquote::after {content: “2019”}

    Thanks again for a useful article (and a great site!)


    • Thanks Michael. Sorry I didn’t have time to look into this yesterday, but I’m glad you figured it out. I hadn’t realized the issue with html entities using the content property. Good to know.

      I’ll update the post. Thanks.

  4. Call me persnickety, but I didn’t let this issue ride peacefully into the sunset…

    As I don’t think I had ever used CSS entities after 4 years of studying and using the language, I figured there must be a more popular solution.

    There is.

    Declare the character encoding in CSS files. In other words, make the first line of your external stylesheet read: @charset “UTF-8”;. We’ve all seen this before.

    You should be good to go with HTML entities in the content property going forward.

    I’m think I’m ready to move on now 😉

  5. Thank for such an educative article and it really helped me understand these concepts. Keep on producing such great articles.

Leave a Reply

Your email address will not be published. Required fields are marked *