Suppose we have the following HTML:

<div class="site-main">
    <article>First article</article>
    <article>Second article</article>
    <article>Third article</article>
    <article>Fourth article</article>
    <nav class="site-nav">Previous ... Next</nav>
</div><!-- .site-main -->

We want to give the last article element a black border. So we write:

.site-main article:last-child {
    border: 5px solid black;
}

But this does not work!

While our intention is to select the last article element, the :last-child pseudo-selector will not do so in this case. Why? There is an additional nav element below all the article, and this element truly is the last-child of div.site-main. In the case above, the expression article:last-child causes the browser’s rendering engine to grab the last child, which is a nav. Since this is not the correct element type, the browser does not apply the desired styles to it.

Use last-of-type

If there was no nav element, then the last article would have been styled as intended. However, since we care to style the last child that is an article element, it is correct to use the :last-of-type selector. Now our CSS works:

.site-main article:last-of-type {
    border: 5px solid black;
}

Other X-of-type pseudo-selectors

In addition to last-of-type, there are also:

  • first-of-type – selects the first element of its parent with the matching type
  • nth-of-type() – selects the nth child of its parent with the matching type

These pseudo-selectors are extremely useful if you are trying to apply styles among a bunch of distinct elements.

But wait! There’s another caveat

Consider the following:

<div class="site-main">
    <section class="banner">First section</section>
    <section class="content">Second section</section>
    <section class="content">Third section</section>
</div>
section:first-of-type { /* First section */ }
section.banner:first-of-type { /* First section */ }
section.content:first-of-type { /* No effect, why?? */ }

To understand why the last selector fails, we must understand what type means. The type in X-of-type refers only to the type of element (e.g. a <p> or <div>), and not its attributes such as ID or classes. In the example above: The reason why section.content:first-of-type didn’t do anything is because the first section element did not have class content. The second line worked, but is overqualified, i.e. the .banner is completely redundant.

In summary, when using X-of-type selectors, don’t attempt to introduce class, ID, or other attribute selectors into the mix. Doing so will only deceive you.

Putting it all together

The example below demonstrates the use of X-of-type selectors on a snippet of HTML code. A live demo is also available at JSFiddle.

&amp;lt;div class=&amp;quot;site-main&amp;quot;&amp;gt;
    &amp;lt;article id=&amp;quot;art-1&amp;quot;&amp;gt;&amp;lt;/article&amp;gt;&amp;lt;!-- #1 --&amp;gt;
    &amp;lt;aside class=&amp;quot;tweet&amp;quot;&amp;gt;&amp;lt;/aside&amp;gt;&amp;lt;!-- #2 --&amp;gt;
    &amp;lt;article id=&amp;quot;art-2&amp;quot;&amp;gt;&amp;lt;/article&amp;gt;
    &amp;lt;aside class=&amp;quot;quote&amp;quot;&amp;gt;&amp;lt;/aside&amp;gt;&amp;lt;!-- #5 --&amp;gt;
    &amp;lt;section class=&amp;quot;ads&amp;quot;&amp;gt;&amp;lt;/section&amp;gt;
    &amp;lt;aside class=&amp;quot;quote&amp;quot;&amp;gt;&amp;lt;/aside&amp;gt;&amp;lt;!-- #4, 5 --&amp;gt;
    &amp;lt;article id=&amp;quot;art-3&amp;quot;&amp;gt;&amp;lt;/article&amp;gt;
    &amp;lt;aside class=&amp;quot;tweet&amp;quot;&amp;gt;&amp;lt;/aside&amp;gt;
    &amp;lt;aside class=&amp;quot;quote&amp;quot;&amp;gt;&amp;lt;/aside&amp;gt;&amp;lt;!-- #5 --&amp;gt;
    &amp;lt;article&amp;gt;3&amp;lt;/article&amp;gt;&amp;lt;!-- #3 --&amp;gt;
    &amp;lt;nav class=&amp;quot;site-navigation&amp;quot;&amp;gt;1 2 3 4 Next...&amp;lt;/nav&amp;gt;
&amp;lt;/div&amp;gt;
aside:first-child { /* affects nothing */ }
article:last-child { /* affects nothing */ }
article:first-of-type { /* 1 */ }
aside:first-of-type { /* 2 */ }
aside.quote:first-of-type { /* affects nothing */ }
article:last-of-type { /* 3 */ }
aside:nth-child(3) { /* affects nothing */ }
aside:nth-of-type(3) { /* 4 */ }
aside.quote:nth-of-type(odd) { /* 5 */ }

Browser compatibility

All of these pseudo-selectors were introduced with CSS3. To see the browser support for these selectors, check out this page on Can I Use.

Published by Geoffrey Liu

A software engineer by trade and a classical musician at heart. Currently a software engineer at Groupon getting into iOS mobile development. Recently graduated from the University of Washington, with a degree in Computer Science and a minor in Music. Web development has been my passion for many years. I am also greatly interested in UI/UX design, teaching, cooking, biking, and collecting posters.

One thought on “CSS Gotchas: last-child vs. last-of-type and other bizarre conundrums

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.