13 min read

CSS Structural Pseudo-Classes: nth-child, first-child, and last-child

13 min read
289 words
26 sections21 code blocks

What Are Structural Pseudo-Classes?

Structural pseudo-classes target elements based on their position in the DOM tree — their relationship to parents and siblings. They let you style elements by their order without adding extra classes to your HTML.

:first-child and :last-child

:first-child

Targets an element that is the first child of its parent:

CSS
li:first-child {
  font-weight: bold;
}

p:first-child {
  margin-top: 0;
}

.card:first-child {
  margin-left: 0;
}

:last-child

Targets an element that is the last child of its parent:

CSS
li:last-child {
  border-bottom: none;
}

p:last-child {
  margin-bottom: 0;
}

.card:last-child {
  margin-right: 0;
}

Common Pattern: Remove Border from Last Item

CSS
.list-item {
  border-bottom: 1px solid #eee;
  padding: 1rem 0;
}

.list-item:last-child {
  border-bottom: none;
}

:nth-child()

The most powerful structural pseudo-class. It targets elements based on their position number among siblings:

Basic Numeric Targeting

CSS
/* Select the 3rd child */
li:nth-child(3) { color: red; }

/* Select the 1st child */
li:nth-child(1) { font-weight: bold; }

Even and Odd

CSS
/* Every even row */
tr:nth-child(even) { background: #f8f9fa; }

/* Every odd row */
tr:nth-child(odd) { background: white; }

This is the standard way to create zebra-striped tables and lists.

The an+b Formula

The real power of :nth-child() comes from the an+b formula, where:

  • a = cycle size (every ath element)
  • n = counter starting from 0
  • b = offset (start from bth element)
CSS
/* Every 3rd element: 3, 6, 9, 12... */
li:nth-child(3n) { color: blue; }

/* Every 3rd element starting from 1st: 1, 4, 7, 10... */
li:nth-child(3n+1) { color: red; }

/* Every 4th element starting from 2nd: 2, 6, 10, 14... */
li:nth-child(4n+2) { color: green; }

Selecting First N Elements

CSS
/* First 3 elements */
li:nth-child(-n+3) {
  font-weight: bold;
}
/* n=0: -0+3=3 (3rd), n=1: -1+3=2 (2nd), n=2: -2+3=1 (1st), n=3: 0 (stops) */

/* First 5 elements */
li:nth-child(-n+5) {
  background: #e3f2fd;
}

Selecting from Nth Element Onward

CSS
/* Starting from 4th element */
li:nth-child(n+4) {
  opacity: 0.7;
}

Selecting a Range

CSS
/* Elements 3 through 7 */
li:nth-child(n+3):nth-child(-n+7) {
  background: #fff3cd;
}

:nth-last-child()

Same as :nth-child() but counts from the end:

CSS
/* Last element */
li:nth-last-child(1) { } /* Same as :last-child */

/* Second to last */
li:nth-last-child(2) { border-style: dashed; }

/* Last 3 elements */
li:nth-last-child(-n+3) { color: #999; }

:nth-of-type() and :nth-last-of-type()

These work like :nth-child() but only count elements of the same type:

CSS
/* Every odd paragraph (ignores other elements like headings) */
p:nth-of-type(odd) {
  background: #f5f5f5;
}

/* First paragraph */
p:nth-of-type(1) {
  font-size: 1.2em;
}

/* Every 2nd image */
img:nth-of-type(2n) {
  float: right;
}

:nth-child vs :nth-of-type

HTML
<div>
  <h2>Title</h2>
  <p>First paragraph</p>
  <p>Second paragraph</p>
</div>
CSS
/* p:nth-child(2) = First <p> (it's the 2nd child overall) */
/* p:nth-of-type(2) = Second <p> (2nd paragraph specifically) */

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

CSS
/* First paragraph in a container */
p:first-of-type {
  font-weight: bold;
}

/* Last image */
img:last-of-type {
  margin-bottom: 0;
}

:only-child and :only-of-type

:only-child

Targets an element that is the only child of its parent:

CSS
/* Style differently when there's only one item */
li:only-child {
  list-style: none;
  text-align: center;
}

:only-of-type

Targets an element that is the only one of its type among siblings:

CSS
p:only-of-type {
  font-size: 1.2em;
}

:empty

Targets elements that have no children (no text, no elements, no whitespace):

CSS
/* Hide empty paragraphs */
p:empty {
  display: none;
}

/* Style empty cells in a table */
td:empty {
  background: #f5f5f5;
}

/* Error message container (only show when has content) */
.error:empty {
  display: none;
}

Practical Patterns

Grid with Variable Columns

CSS
/* Remove right margin on every 3rd card */
.card:nth-child(3n) {
  margin-right: 0;
}

Staggered Animation

CSS
.item {
  opacity: 0;
  animation: fadeIn 0.5s ease forwards;
}

.item:nth-child(1) { animation-delay: 0.1s; }
.item:nth-child(2) { animation-delay: 0.2s; }
.item:nth-child(3) { animation-delay: 0.3s; }
.item:nth-child(4) { animation-delay: 0.4s; }
.item:nth-child(5) { animation-delay: 0.5s; }

Alternating Layout

CSS
.feature:nth-child(even) {
  flex-direction: row-reverse;
}

Quantity-Aware Styling

CSS
/* If there are 3 or fewer items, make them full width */
li:first-child:nth-last-child(-n+3),
li:first-child:nth-last-child(-n+3) ~ li {
  width: 100%;
}

Summary

Structural pseudo-classes target elements by their position in the DOM. Use :first-child and :last-child for edge cases, :nth-child(an+b) for pattern-based selection, :nth-of-type when you need to count specific elements, and :empty for conditional display. These selectors eliminate the need for extra CSS classes and make your stylesheets more dynamic and maintainable.