CSS :nth-child() and Structural Pseudo-Classes Explained
The :nth-child() family selects elements by position among siblings, using a simple an+b formula. How it works and where it beats manually added classes.
:nth-child() is a CSS pseudo-class that selects elements based on their position among sibling elements, using a formula of the form an+b. It lets you style “every third row,” “the first four items,” or “everything but the last one” without touching the markup or adding a single class. It’s part of a broader family of structural pseudo-classes that select by position rather than by attribute or content.
The an+b formula
The syntax looks unfamiliar at first but follows one consistent rule: n starts at 0 and counts up, and the browser evaluates the formula for each value of n to produce a list of matching positions.
li:nth-child(3) /* only the 3rd child */
li:nth-child(2n) /* every 2nd child: 2, 4, 6... */
li:nth-child(2n+1) /* every odd child: 1, 3, 5... */
li:nth-child(n+4) /* the 4th child onward */
li:nth-child(-n+3) /* only the first 3 children */
Two keywords cover the most common cases without the formula: :nth-child(odd) and :nth-child(even) are shorthand for 2n+1 and 2n. Striping a table is just tr:nth-child(even) { background: #f4f4f4; }.
The full family
:nth-child(an+b)— matches by position among all siblings, regardless of element type.:nth-of-type(an+b)— matches by position among siblings of the same element type, ignoring other tags in between.:nth-last-child()/:nth-last-of-type()— count from the end instead of the start.:first-child/:last-child— shorthand for:nth-child(1)and:nth-last-child(1).:only-child— matches an element that is the sole child of its parent.
The :nth-child vs :nth-of-type distinction trips people up most. Given <p>, <img>, <p>, <p>, img:nth-of-type(1) matches that one image regardless of its position among all children, while img:nth-child(2) would only match if the image happened to be the second child overall.
The of S syntax
Modern :nth-child() supports an optional of <selector> clause that filters which siblings count before applying the position formula: :nth-child(2n of .featured) selects every second element among only those matching .featured, ignoring non-matching siblings entirely for counting purposes. This is useful for things like “every other visible item” when some items are conditionally hidden or excluded by a class.
Why this beats manual classes
Before these selectors were reliable across browsers, striping a list or highlighting specific positions meant adding a class like .even or .first in the markup, or doing it with JavaScript. That couples presentation to markup and breaks the moment an item is inserted or removed — the classes have to be recalculated. :nth-child() computes the position live, in CSS, so reordering or filtering the underlying list just works. It pairs well with CSS custom properties for building configurable list styles, and with :has() and :is()/:where() when you need to combine positional and relational logic in one selector.
Practical patterns
Zebra-striped tables:
tbody tr:nth-child(even) { background: var(--row-alt); }
A responsive grid where only the first 3 items get emphasis:
.card:nth-child(-n+3) { grid-column: span 2; }
Removing a border from every last item in a row of 4:
.item:nth-child(4n) { border-right: none; }
Alternating an animation delay per item:
.item:nth-child(odd) { animation-delay: 0.1s; }
.item:nth-child(even) { animation-delay: 0.2s; }
Performance and specificity notes
Structural pseudo-classes carry the same specificity as a regular class selector, so li:nth-child(2n) behaves like li.some-class for cascade purposes — see how CSS specificity works if that ordering ever surprises you. They’re also cheap: the browser already tracks sibling relationships for layout, so evaluating a position formula adds negligible overhead even in long lists.
The takeaway
:nth-child() and its relatives select elements by their position among siblings using an an+b formula, with odd/even and first-child/last-child covering the common cases as shorthand. Use :nth-of-type() when you care about position among a specific tag rather than all siblings, and reach for the of <selector> syntax when you need to filter which siblings count before positions are computed. All of it runs in CSS, so the list can reorder, grow, or shrink without any style having to be recalculated by hand.
Tagged
Keep reading
Takina · · 4 min read What Is CSS Subgrid? Nested Grid Alignment Explained
CSS subgrid lets a nested grid item inherit its parent's track sizing, so children can align to the same columns or rows across unrelated containers.
Takina · · 4 min read CSS field-sizing Property Explained
The CSS field-sizing property lets form controls like textareas grow to fit their content automatically, without JavaScript resize listeners.
Takina · · 3 min read CSS Scrollbar Styling: scrollbar-color and scrollbar-width
CSS now styles scrollbars natively with scrollbar-color and scrollbar-width, replacing years of vendor-prefixed hacks. Here's how they work.