Combinators
Learn to combine selectors based on the relationships between HTML elements using descendant, child, adjacent sibling, and general sibling combinators.
So far you've selected elements in isolation — by tag, class, ID, or attribute. Combinators let you select elements based on their relationship to OTHER elements in the document tree: is it inside another element, is it a direct child, does it come right after a sibling? There are four combinators in CSS, and mastering them unlocks much more precise, HTML-structure-aware styling.
The descendant combinator (space)
Writing two selectors separated by a space, like nav a, selects any <a> element that is nested ANYWHERE inside a <nav> element, no matter how deeply nested (a direct child, a grandchild, or deeper). This is the most commonly used combinator and the one you've likely already seen in earlier lessons without it being named.
The child combinator (>)
Writing selectors separated by a greater-than sign, like ul > li, selects only elements that are DIRECT children of the first selector — one level down, not grandchildren. This is more restrictive and predictable than the descendant combinator, which is useful when you specifically don't want styles leaking into nested structures (e.g., a nested <ul> inside an <li>).
The adjacent sibling combinator (+)
Writing selectors separated by a plus sign, like h2 + p, selects an element that comes IMMEDIATELY after another element, as a sibling sharing the same parent. h2 + p selects a <p> only if it is directly, immediately preceded by an <h2> with no other elements in between (text nodes don't count, but other elements do).
The general sibling combinator (~)
Writing selectors separated by a tilde, like h2 ~ p, selects ALL sibling elements that come after the first one (not just the immediate next one), as long as they share the same parent. This is less strict than the adjacent sibling combinator — order matters (the target must come after), but adjacency doesn't.
The four combinators
A B { } /* descendant: B anywhere inside A */
A > B { } /* child: B is a direct child of A */
A + B { } /* adjacent sibling: B immediately follows A */
A ~ B { } /* general sibling: B follows A anywhere among siblings */Code Examples
nav a matches the <a> even though it's nested two levels deep inside <nav> (through <ul> and <li>) — the descendant combinator doesn't care how deep, only that it's somewhere inside. CSS: nav a { text-decoration: none; } ul > li matches only the outer <li> directly, not the nested <li> inside the inner <ul> — because that nested <li>'s direct parent is the inner <ul>, not the outer one. CSS: ul > li { border-top: 1px solid #eee; } h2 + p matches only the first <p>, because it is the element immediately following the <h2>. The second <p> is a sibling too, but it isn't immediately adjacent to the <h2> — a <p> comes before it instead. CSS: h2 + p { font-weight: bold; } h2 ~ p matches BOTH paragraphs, because the general sibling combinator matches any later sibling of the same parent, regardless of what elements appear in between (the <div> doesn't break the relationship). CSS: h2 ~ p { color: gray; }
<nav>
<ul>
<li><a href="#">Home</a></li>
</ul>
</nav><ul>
<li>Item
<ul>
<li>Nested item</li>
</ul>
</li>
</ul><h2>Section Title</h2>
<p>First paragraph — gets styled.</p>
<p>Second paragraph — does NOT get styled by +.</p><h2>Section Title</h2>
<p>Styled by ~.</p>
<div>Not a paragraph, skipped.</div>
<p>Also styled by ~.</p>Combinators visualized
A and B here refer to sibling positions under the same Parent for + and ~, and to nesting depth for > and space.
Parent
+-- Child A
+-- Child B
+-- Child C
A > B direct child only
A B any descendant (any depth)
A + B B, only if immediately next after A
A ~ B B, and any later sibling of AImportant Rules
Space = descendant (any depth).
> = direct child only (one level).
+ = the very next sibling only.
~ = any sibling that comes after, not just the next one.
For +, and ~, the two selectors must share the same parent element.
Common real-world patterns
The '> * + *' pattern (sometimes called the 'lobotomized owl' selector) is a popular trick for adding consistent spacing between sibling elements without needing margin on the first child. The adjacent sibling combinator is also frequently used for form validation styling, like input:invalid +.error-message { display: block; }..sidebar li { color: gray; } <!-- intent: style only top-level sidebar items, but this also styles items in nested sub-lists -->.sidebar > ul > li { color: gray; } <!-- targets only direct list items of the sidebar's top-level list -->
The descendant combinator (space) reaches arbitrarily deep, which often over-selects. When you truly mean 'direct child only', use > instead.
[Predict the Output] Given the CSS rule div > p { color: red; }, how many <p> elements below will be red? <div> <p>One</p> <section> <p>Two</p> </section> <p>Three</p> </div> A. 0 B. 1 C. 2 D. 3
C. 2 — The child combinator only matches direct children. 'One' and 'Three' are direct children of <div>, so they turn red. 'Two' is a direct child of <section>, not of <div>, so it is not matched — even though it's nested inside the div overall.
Write a CSS rule using a combinator that adds top margin only to paragraphs that immediately follow another paragraph (so the first paragraph in a group has no extra top margin).
p + p { margin-top: 16px; } Use the adjacent sibling combinator with p as both selectors.