Pattern-Based Element Selection with CSS :nth-child and :nth-of-type

:nth-child selects by position among all siblings; :nth-of-type selects by position among siblings of the same element type, using the An+B formula.

Pattern-based selection is one of the oldest tricks in the CSS playbook, and it starts with a hard fact: you do not need JavaScript to stripe a table, colour a grid, or pull the first three items out of a list. The :nth-child and :nth-of-type pseudo-classes have done that since Firefox 1 shipped in 2004, long before the phrase ‘front-end framework’ meant anything. What they are not is interchangeable. :nth-child counts every sibling under a parent, ignoring what element it is; :nth-of-type counts only siblings that share the same element type. A

and a

in the same container are both children, but only one is a ‘type’. That distinction is the whole game. Getting it wrong is the most common way this selector fails you.

The One-Number Question: What Are You Counting?

Before you write a single formula, decide which counter you need. The former uses a sibling index that includes every element node in the DOM tree under a parent; the latter uses an index that resets for each element type. Take a containing

,

,

,

. The third child is the

; :nth-child(3) selects it. The third

is the fourth child, so :nth-child(3 of p) would be needed, but that is the newer ‘of S’ syntax, shipped only in Baseline 2023. With plain :nth-of-type, the same

elements are type-indexed: the first

is type 1, the second type 2, the

is type 1 of its own kind, and the third

is type 3. So :nth-of-type(3) grabs that

directly. No extra selector needed. The counting difference is not a nuance; it is the entire specification of what matches.

Both pseudo-classes share the same specificity of (0,1,0), a single class-level weight that beats any number of element selectors but loses to a single class or ID. That specificity is a feature: you can override a pattern with one class, or let a later rule win in the cascade without a heavier hammer. For the working developer who writes CSS daily, this is the safe zone. No !important, no ID escalation. Just a predictable base that layers cleanly with the rest of your stylesheet. The An+B notation is the engine for both, and it is the same syntax you have used since CSS2, which is why this is one of the oldest durable selection techniques in the language.

Writing the An+B Pattern

When you need to select every third element in a repeating list, the formula CSS uses is An+B, where A is the period and B is the offset. The classic example is every third item: :nth-child(3n+1) selects the 1st, 4th, 7th, and so on, because 3n cycles through 0, 3, 6, and the +1 shifts the start to the first of each triplet. The odd keyword is shorthand for 2n+1, selecting the 1st, 3rd, 5th; even is 2n, the 2nd, 4th, 6th. The formula is arithmetic evaluated against a 1-based index for the B offset. That last point trips people: the index starts at 1, not 0. So :nth-child(2n) selects the 2nd, 4th, 6th children, never the 0th (which does not exist) and never the 1st. Write the formula out longhand if you need to: 2n for even, 2n+1 for odd. You will never misread a zebra stripe again.

Negative A and the First-N Trick

The real power of An+B is negative values of A. A negative-n formula like :nth-child(-n+3) selects the first three children of a parent, because -n+3 evaluates to 3, 2, 1 as n goes 0, 1, 2, and then 0, -1, -2, which match nothing. This one-liner replaces the JavaScript pattern of iterating over a NodeList and applying a class to items 0, 1, and 2. No loop, no classList.add, no cleanup. For a hero section that needs its first three cards highlighted, or a navigation where the first three items get a distinct treatment, this is the declaration you want:

/* Select the first three children of any parent */
.parent > *:nth-child(-n+3) {
  border-top: 2px solid #333;
}

The child combinator > is not required but narrows the scope to direct children, avoiding surprises from nested elements. The same technique works with :nth-of-type if your markup mixes element types: :nth-of-type(-n+3) selects the first three

elements, even if

siblings sit between them. That is the practical difference, and it is why you choose one over the other.

A Complete Runnable Example

Here is a complete, runnable example that shows both the formula and the type-counting difference in one go. Save this as an HTML file, open it in any modern browser, and watch the borders appear.

<!DOCTYPE html>
<html lang="en">
<head>
<style>
  /* Every third <li> gets a red bottom border */
  li:nth-child(3n+1) {
    border-bottom: 2px solid red;
  }
  /* The second <p> of any type gets a blue left border */
  p:nth-of-type(2) {
    border-left: 4px solid blue;
    padding-left: 8px;
  }
  /* First three <div> children of .list get a grey background */
  .list > div:nth-of-type(-n+3) {
    background: #f0f0f0;
  }
</style>
</head>
<body>
  <ul>
    <li>One</li><li>Two</li><li>Three</li>
    <li>Four</li><li>Five</li><li>Six</li>
  </ul>
  <div class="list">
    <p>First para</p><div>First div</div>
    <p>Second para</p><div>Second div</div>
    <p>Third para</p><div>Third div</div>
  </div>
</body>
</html>

The list shows red borders on items 1, 4, and 7, the 3n+1 pattern. In the div, the

elements are type-indexed, so the second

gets the blue border, and the

s are counted separately, so the first three
children get the grey background even though they are the 2nd, 4th, and 6th children overall. Run it. You see exactly what each selector counts.

nth-of-type vs nth-child: The Head-to-Head

When the choice is :nth-of-type vs :nth-child, the decision is always about your markup’s element-type mix. If every sibling in a container is the same element, a list of

  • items, a grid of elements, then both produce identical results. The difference appears the moment a
    , , or

    joins the party. :nth-child sees the whole crowd; :nth-of-type sees only the ones that share a name. Use :nth-of-type when your HTML structure has a predictable type rhythm, sections with alternating

    and

    tags, where you want to style the odd

    , not the odd child. Use :nth-child when the type is irrelevant to the visual pattern, like striping every row of a table or colouring every other card in a flex container, regardless of whether a card wraps in a

    or a .

    A common mistake is assuming :nth-of-type is a shortcut for :nth-child when the markup is clean. It is not. The two diverge on the sibling index, and the formula you write is interpreted against that index. Write :nth-child(2n) on a container with mixed elements and you get every second child, not every second of any type. Write :nth-of-type(2n) on the same container and you get every second

    AND every second

    , separately. The latter is often what designers mean when they ask for ‘alternating blocks’, but it is a different pattern. Test your selector in the browser’s dev tools before committing. The cost of a wrong assumption is a layout you will spend an hour debugging.

  • Select Every Third Element: A Practical Recipe

    Flex-Grid Margin Reset

    The search ‘select every third element’ usually comes from someone building a grid or a list where the third item needs a margin reset or a different width. The formula is :nth-child(3n+3) or its equivalent :nth-child(3n) with an offset, but the cleanest is :nth-child(3n+3) for the 3rd, 6th, 9th, and so on. Here is a runnable example that clears the left margin on every third item in a flex row, the classic float-grid replacement:

    /* In a flex container, remove the right margin on every 3rd item */
    .container {
      display: flex;
      flex-wrap: wrap;
      gap: 0;
    }
    .item {
      width: calc((100% - 2rem) / 3);
      margin-right: 1rem;
    }
    .item:nth-child(3n+3) {
      margin-right: 0;
    }
    

    This works because the 3n+3 formula lands on items 3, 6, 9, and clears their right margin, while every other item keeps the 1rem spacing. The child combinator is optional but recommended: .item:nth-child(3n+3) scopes to elements with that class, avoiding accidental matches on nested spans. If your items are not all the same element type, switch to :nth-of-type(3n+3) so the count resets per type, but then the margin clearing may not align, because the 3rd

    and the 3rd

    are different indices. That is the trade-off. Test it.

    The Math Behind the Magic

    How An+B Evaluates

    The An+B notation is not a CSS invention; it is a linear function applied to the sibling index. The notation is An+B, where A and B are integers, n is a non-negative integer starting at 0, and the result must be a positive integer to match. So :nth-child(2n+1) evaluates n=0 to 1, n=1 to 3, n=2 to 5, and so on, the odd items. The keyword ‘odd’ is pure sugar for 2n+1; ‘even’ is 2n. What trips people is the zero case. With :nth-child(0n+3), the formula becomes just 3, selecting only the third child, because 0n is always 0. With :nth-child(-n+3), as noted, you get a descending sequence that stops at 1. The negative A is what creates ‘first N items’ patterns, and it is the single most useful trick here.

    Striped Table with Header Row

    Here is a second complete sample that uses An+B to create a striped table with a header row, mixing both pseudo-classes:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <style>
      table {
        border-collapse: collapse;
        width: 100%;
      }
      tr:nth-child(even) {
        background: #f9f9f9;
      }
      tr:nth-child(even) td {
        border-bottom: 1px solid #ddd;
      }
      /* Highlight first row after header */
      tbody tr:nth-child(1 of :not(.skip)) {
        font-weight: bold;
      }
    </style>
    </head>
    <body>
      <table>
        <thead><tr><th>Name</th><th>Score</th></tr></thead>
        <tbody>
          <tr><td>Alice</td><td>42</td></tr>
          <tr><td>Bob</td><td>37</td></tr>
          <tr class="skip"><td>Skip</td><td>0</td></tr>
          <tr><td>Carol</td><td>29</td></tr>
        </tbody>
      </table>
    </body>
    </html>
    

    Here, the even rows are striped, and the ‘of S’ syntax in the bold rule scopes the count to children that match :not(.skip), so the first non-skipped row gets bold. This is the newer ‘of S’ feature, which you should use with a fallback, as covered below.

    The ‘of S’ Syntax: Filtering What You Count

    A newer addition to :nth-child is the ‘of S’ clause, which lets you filter the siblings before applying the index. Written :nth-child(An+B of S), it counts only children that match S. This decouples the count from the raw sibling position. For example, :nth-child(3 of .item) selects the third child that has the class ‘.item’, ignoring any non-.item siblings. The same works with :nth-of-type, though it is less needed there since type already filters.

    The ‘of S’ syntax shipped in Chrome 111, Safari 16.4, and Firefox 113, all in the first half of 2023, and is marked Baseline newly available as of July 2023. It is not yet widely available. Check caniuse for the current status. The accepted fallback is an @supports test with selector(), like this:

    @supports (selector(:nth-child(1 of .item))) {
      .list > :nth-child(odd of .item) {
        background: #eee;
      }
    }
    @supports not (selector(:nth-child(1 of .item))) {
      .list > .item:nth-child(odd) {
        background: #eee;
      }
    }
    

    The fallback assumes your .item elements are the only ones with that class, so the plain :nth-child(odd) works. If not, you are back to JavaScript or a markup change. This is not a criticism of the feature; it is the cost of doing pattern matching in CSS.

    For most users, the core :nth-child and :nth-of-type without ‘of S’ are Baseline widely available since 2015, meaning every browser you care about supports them without prefixes. The ‘of S’ addition is the only part that needs a progressive enhancement strategy, and the @supports route is the honest one.

    Selector Performance: What It Costs in Layout and Paint

    Right-to-Left Matching

    A performance-conscious developer asks what a selector actually costs. The answer for :nth-child and :nth-of-type is: very little, but not zero. Browsers match selectors right-to-left, meaning they start at the rightmost part and walk up the DOM tree. For :nth-child, the engine must compute the sibling index of each element, which requires walking through the parent’s child list once to count. That is O(n) per parent, but browsers cache this index for a given DOM subtree. The first match on a page is the expensive one; subsequent rules re-use the calculation. Selecting every third item in a list of 1000

  • elements is a few microseconds of work, nothing compared to layout or paint.

    Avoiding Layout Thrash

    The real performance trap is not the selector itself but what you do with it. A declaration like :nth-child(2n) { display: none; } forces a layout change each time the index changes, but if the DOM is static, the cost is paid once. Avoid using :nth-child inside a property that triggers layout repeatedly, like animating width or top on a matched element. The selection cost is fixed; the style recalculation is not. Right-to-left matching means a selector like .container > li:nth-child(3) is fast because the rightmost part is a type selector with a class, and the browser short-circuits. The worst case is a complex list with many sibling types, but even then, modern Blink, WebKit, and Gecko engines have optimized this path for a decade. If you are worried, measure with the browser’s performance profiler. Do not guess.

  • Common Mistakes and How to Avoid Them

    Zero-Based Confusion

    The first mistake is confusing the two selectors. The second is expecting the B offset to be zero-based. :nth-child(2n) selects the 2nd, 4th, 6th, not the 0th, 2nd, 4th. There is no 0th child, and the formula’s result must be 1 or higher. So :nth-child(2n+0) is the same as :nth-child(2n), and writing :nth-child(2n+1) with an intention to get the first item is wrong; that gets you the 3rd, 5th, 7th.

    Type Mismatch

    The third mistake is using :nth-child where :nth-of-type is intended, because the visual pattern breaks the moment you add a

    wrapper or an

    heading. A fourth, less common, stumble is assuming :nth-child applies to the element itself rather than its siblings. It does not. It queries the parent’s children. If you write div:nth-child(2), you select a
    that is the second child of its parent, not the second
    in a row. For the latter, you need :nth-of-type, or :nth-child(2 of div).

    Inconsistent Markup

    The failure case is when neither selector does what you need because your markup is inconsistent, say, some items are wrapped in

  • and others in
    . The fix is to normalize the markup, not to fight the selector. Add a class to the wrappers and use :nth-child(An+B of .item), or restructure so the type is uniform. Do not reach for JavaScript unless you have no other option. The pattern is exactly what these pseudo-classes were built for.

  • Browser Support and Baseline Status: What Is Safe Today

    The core :nth-child() and :nth-of-type() are among the most widely supported CSS features in existence. They shipped in Firefox 1 (November 2004), Safari 3.1 (March 2008), Chrome 1 (December 2008), and Edge 12 (July 2015, the EdgeHTML engine). That means every browser since 2015 has had them, and the Baseline status is ‘widely available’ as of Baseline 2015. There is no need for a fallback, no @supports guard, no vendor prefix. The ‘of S’ syntax is different: it is ‘newly available’ since July 2023, with Chrome 111, Safari 16.4, and Firefox 113 shipping it in the first half of 2023. Check caniuse for the current support picture. If you use it, the @supports guard above is the accepted fallback, or you accept that older browsers will not apply the filter.

    For the working developer, the practical guidance is simple: use core :nth-child and :nth-of-type without fear in any production codebase today. Use ‘of S’ only if you ship a fallback or you know your user base is on modern evergreen browsers, and even then, test. The cost of a missing fallback is that a pattern silently fails, not that the page breaks. That is the trade-off of progressive enhancement, and it is the right one for a feature this mature.

    What This Selector Replaces: The JavaScript Antipattern

    Before these pseudo-classes were ubiquitous, the go-to solution for alternating row colours or highlighting every third card was a JavaScript loop: iterate over a NodeList, check the index with a modulo operator, and add a class. That is throwaway code that runs on every DOM mutation, bloats your bundle, and fails on initial render before the script executes. The CSS technique replaces all of that with a declaration that costs nothing at runtime and degrades gracefully. The old technique of manual class assignment, .row-1, .row-2, .row-3, in server-side templates is equally dead. It hard-codes the pattern into the markup, making any visual change a template change.

    For :nth-of-type, the older technique was to wrap elements in extra containers to isolate element types, a

    around every other

    so that :nth-child(odd) would work. That is markup for the sake of CSS. It is wrong. The correct approach is to use :nth-of-type and let the type index do the work. If your design system still has utility classes like ‘even-row’ or ‘third-item’, you are carrying a pre-CSS2 workaround. Replace it with a selector, and you have one less class to forget, one less hook for a stray script, and one less thing to maintain.

    FAQ: Five Questions That Answer The Rest

    What is the difference between :nth-child and :nth-of-type?

    :nth-child counts all element siblings under a parent, ignoring their type. :nth-of-type counts only siblings that share the same element type (e.g., all

    or all

    ). So :nth-child(2) selects the second child regardless of whether it is a

    or a

    ; :nth-of-type(2) selects the second

    and the second

    separately.

    How do I select the first three items with :nth-child?

    Use the negative-n formula :nth-child(-n+3). This evaluates to 3, 2, 1 for n=0, 1, 2, matching the first three children. For :nth-of-type, use :nth-of-type(-n+3) to get the first three of each element type.

    Can I use :nth-child inside a media query?

    Yes, :nth-child is a normal pseudo-class and works inside any rule, including those wrapped in @media. It is not affected by viewport size; it only counts siblings.

    What does the An+B notation mean exactly?

    An+B is a linear formula where A is the period (how often to repeat), B is the offset (where to start), and n is a non-negative integer beginning at 0. The result must be positive to match. For example, 3n+1 matches the 1st, 4th, 7th items.

    Is :nth-of-type supported in all major browsers?

    Yes, :nth-of-type has been widely available since 2015 across Chrome, Firefox, Safari, and Edge. The ‘of S’ syntax is newer, shipped in 2023, and requires a fallback for older browsers.

    Who This Subject Suits and Who It Does Not

    This subject suits the working front-end developer who writes CSS daily and needs to ship pattern-based styling without JavaScript. It suits the performance-conscious developer who understands that a selector’s cost is in right-to-left matching steps, not in the formula’s aesthetics. It suits the technical writer or educator who needs accurate, sourced statements about pseudo-classes without laundering guesses into facts. The reader who will stay is the one building real interfaces, tables, grids, navigation, and needs the mechanics to be second nature.

    It does not suit the designer who wants to learn why a layout works visually; that reader should go to Every Layout or Refactoring UI for design rationale, not CSS mechanics. It does not suit someone learning to code from zero; that beginner belongs on web.dev/learn/css or MDN’s CSS first-steps guide, where the fundamentals are taught without the baggage of selector edge cases. It does not suit anyone debugging a React state bug, because that is a JavaScript problem, not a CSS one. And it does not suit someone comparing CSS-in-JS libraries, because that is a tooling question about JavaScript execution, not about what a selector matches. This is for the person who has a list, a pattern, and a deadline, and wants the declarative answer now.