Chapter 72: CSS Selectors & HTML Structure

Structural Pseudo-Classes & Logic

DOM tree navigation with `:first-child`, `:nth-child(An+B)`, `:nth-of-type()`, and modern logic selectors `:is()`, `:where()`, and `:not()`.

LEARNING OBJECTIVES
  • Differentiate between index-based child selectors (:first-child, :nth-child) and type-based selectors (:first-of-type, :nth-of-type).
  • Master the algebraic An+B formula to construct zebra stripes, ranges (-n+3), and repeating patterns.
  • Harness the modern nth-child(An+B of S) selector to filter siblings matching a specific class.
  • Utilize modern logical pseudo-classes (:is(), :where(), :not()) and control specificity vectors with surgical precision.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

Imagine a high-school sports coach organizing students lined up in the gymnasium.

The coach gives two completely different kinds of instructions:

  1. Positional Instruction: "Every 3rd student in this line, take two steps forward!" The coach doesn't care if the 3rd person is a basketball player, a runner, or a swimmer—they are physically standing at index #3 in the line. This is :nth-child(3n).
  2. Category-Specific Instruction: "The first swimmer in this line, grab the stopwatch!" If the first three students in line are basketball players, the coach skips past them until encountering the very first person who is a swimmer. This is :first-of-type.

In CSS, structural pseudo-classes give you this exact mathematical power over DOM node hierarchies.

Furthermore, modern CSS logic functions like :is(), :where(), and :not() act as logical operators (OR, ZERO-SPECIFICITY, and NOT), allowing you to condense dozens of repetitive selectors into a single readable line.


Technical Deep Dive & Specifications

1. Child vs. Type Selectors

The difference between :nth-child and :nth-of-type is a frequent source of bugs in frontend engineering:

DOM Parent Container <section>:
  <h1>Title</h1>           <-- 1st Child,  1st <h1> of type
  <p>Paragraph 1</p>       <-- 2nd Child,  1st <p> of type
  <p>Paragraph 2</p>       <-- 3rd Child,  2nd <p> of type
  <div>Widget</div>        <-- 4th Child,  1st <div> of type
  <p>Paragraph 3</p>       <-- 5th Child,  3rd <p> of type
  • p:first-child: Does NOT match anything! (Because the 1st child of the parent is <h1>, not <p>).
  • p:first-of-type: Matches Paragraph 1 (The first sibling matching <p>).
  • p:nth-child(2): Matches Paragraph 1 (It is the 2nd child overall, and it is a <p>).
  • p:nth-of-type(2): Matches Paragraph 2 (The 2nd <p> among all <p> siblings).

2. The An+B Algebraic Formula

The argument inside :nth-child(An+B) or :nth-of-type(An+B) is evaluated as $A \times n + B$ for all non-negative integers $n \ge 0$:

+-----------------------------------------------------------------------------------------+
|                               THE An+B FORMULA CHEAT SHEET                              |
+-------------------+--------------------+------------------------------------------------+
| Expression        | Math Evaluation    | Target Matched Nodes                           |
+-------------------+--------------------+------------------------------------------------+
| `even` or `2n`    | 0, 2, 4, 6, 8...   | All even-indexed items (Zebra striping)        |
| `odd` or `2n+1`   | 1, 3, 5, 7, 9...   | All odd-indexed items                          |
| `3n`              | 0, 3, 6, 9, 12...  | Every 3rd item (3, 6, 9, 12)                   |
| `3n + 1`          | 1, 4, 7, 10...     | Items 1, 4, 7, 10 (First of every 3-column row)|
| `-n + 3`          | 3, 2, 1, 0, -1...  | The FIRST 3 items (Indices 1, 2, 3)            |
| `n + 5`           | 5, 6, 7, 8...      | ALL items starting from index 5 to infinity    |
| `4`               | 4                  | Exactly the 4th item                           |
+-------------------+--------------------+------------------------------------------------+
                        RANGE SELECTOR: :nth-child(-n + 3)
                        
   Item #1       Item #2       Item #3       Item #4       Item #5
  [ MATCH ]     [ MATCH ]     [ MATCH ]     [ SKIP ]      [ SKIP ]
  \___________________________________/
           First 3 Elements

3. CSS Selectors Level 4: nth-child(An+B of S)

Modern browsers support filtering :nth-child against a specific selector list:

/* Matches every 2nd .visible item, IGNORED hidden items in the count! */
li:nth-child(even of .visible) {
  background-color: #334155;
}

4. Logic Pseudo-Classes: :is(), :where(), and :not()

+---------------------------------------------------------------------------------------------------+
|                              LOGICAL PSEUDO-CLASS SPECIFICITY RULES                               |
+-------------------+-----------------+-------------------------------------------------------------+
| Pseudo-Class      | Specificity     | Characteristics & Forgiving Parser                          |
+-------------------+-----------------+-------------------------------------------------------------+
| `:is(s1, s2)`     | Highest of args | Replaces long duplicate selector lists. Forgiving parser.   |
| `:where(s1, s2)`  | ALWAYS (0,0,0,0)| Zero-specificity reset wrapper. Allows instant overrides.   |
| `:not(s1, s2)`    | Highest of args | Inverts condition (e.g. `button:not(.btn--primary)`).       |
+-------------------+-----------------+-------------------------------------------------------------+
/* TRADITIONAL REPETITION: */
header h1, header h2, header h3,
main h1, main h2, main h3,
footer h1, footer h2, footer h3 {
  color: #38bdf8;
}

/* MODERN :is() CONCISE EQUIVALENT: (Takes highest specificity among arguments) */
:is(header, main, footer) :is(h1, h2, h3) {
  color: #38bdf8;
}

/* MODERN :where() ZERO-SPECIFICITY RESET: */
:where(header, main, footer) :where(h1, h2, h3) {
  color: #38bdf8; /* Specificity is exactly (0, 0, 0, 0)! */
}

💻 Interactive Code Playground

Starter Code

Line-by-Line Code Breakdown

  • Line 37 (tbody tr:nth-child(even)): Applies a subtle background tint to alternate rows (rows 2, 4, 6...).
  • Line 45 (tbody tr:nth-child(-n+3) td:first-child::before): Uses -n+3 to match only rows 1, 2, and 3, prepending a fire emoji (🔥) to denote high-priority transactions.
  • Line 66 (.btn:not(.btn--outline):not(:disabled)): Uses chained :not() logic pseudo-classes to apply the glowing blue primary background to any button that is neither an outline button nor disabled.
  • Line 83 (:where(.prose) :where(p:not(:last-child))): Uses :where(). Despite its structural complexity, the total specificity remains exactly (0, 0, 0, 0), creating an ideal default system for design component resets.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
1. Transaction History (Zebra & Top 3 Matchers)
  TRANSACTION ID    SERVER NODE    STATUS
  🔥 TX-9011        us-east-1      Complete
  🔥 TX-9012        eu-central-1   Complete (Dark Zebra tint)
  🔥 TX-9013        ap-south-1     Pending
     TX-9014        us-west-2      Complete (Dark Zebra tint)
     TX-9015        sa-east-1      Failed

2. Negation Filtering with :not()
  [ Primary Action (Solid Blue) ] [ Secondary (Outline) ] [ Disabled (Dim) ]

🏋️ Hands-On Exercise

🎯 The Challenge: Build a 3-Column Responsive Pricing Table with Logic Selectors

Instructions:

  1. Create a 3-column pricing grid containing 3 .plan-card elements.
  2. Using :nth-child(2) or :nth-of-type(2), highlight the middle "Pro" plan with a prominent cyan border and a top badge.
  3. Using :is(.plan-card, .faq-card) h3, style the headings across cards in one concise rule.
  4. Using :where(), set default padding and border radius on all cards with (0, 0, 0, 0) specificity so variant modifiers can override them cleanly.
  5. In the feature lists, style the first item (:first-child) in bold and dim the last item (:last-child) if it represents an excluded feature.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Assuming :first-child filters by tag type: Writing p:first-child fails if the <p> is preceded by an <h1> or <img>. Use p:first-of-type if you want the first paragraph among sibling paragraphs.
  2. Unintentional High Specificity with :is(): :is(.a, #b, .c) takes the specificity of its heaviest argument (#b = 0,1,0,0). Even if it matches .a, the rule will have ID-level specificity. Use :where() if you want zero specificity.
  3. Confusing :nth-child(3) with 0-indexed arrays: In CSS, DOM child indexing is 1-indexed (1 is the first element, 2 is the second).

💡 Pro Tips

  1. CSS Resets with :where(): Modern CSS component libraries wrap their foundational resets in :where(html, body, h1, ...) so consumers can override any style with single-class utility selectors without fighting library specificity.
  2. The nth-child(An+B of S) Power: Filter active search results or visible cards in real time without mutating classes:
.product:nth-child(even of :not([hidden])) {
  background-color: #f8fafc;
}

📌 Key Takeaways

  • :nth-child is based on absolute sibling index position; :nth-of-type is based on index among siblings of the identical tag name.
  • The An+B syntax supports powerful formulas: even, odd, 3n+1, -n+3 (first 3), and n+4 (4th and up).
  • :is() groups selectors with a forgiving parser and takes the highest specificity among its arguments.
  • :where() groups selectors with a forgiving parser and always has (0, 0, 0, 0) specificity.
  • :not() negates matching conditions, enabling clean filtering without redundant class flags.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Given <main><h1>Title</h1><p>First</p><p>Second</p></main>, why does p:first-child fail to match <p>First</p>?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

What is the specificity weight of :where(h1, #header, .title)?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

Which An+B expression matches ONLY the first 4 elements of a container?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP