So far you have learned to target HTML elements using simple selectors like p, .classname, and #id. But what if you only want to style a <p> that is inside a specific <div>? Or only the <p> that comes immediately after an <h2>? Or only the direct children of a sidebar?
This is exactly what CSS combinators solve. A combinator is a symbol you place between two selectors to describe a relationship between them. Instead of targeting every <p> on the page, you can target a <p> only when it appears in a specific place in your HTML structure.
In this lesson you will learn:
- What a combinator is and why it exists
- All four CSS combinators: descendant (
), child (>), adjacent sibling (+), and general sibling (~) - How to read, write, and apply each combinator with working code examples
- How to combine multiple combinators to write powerful, precise selectors
- How to build a real-world styled article page using all four combinators together
By the end of this lesson, you will be able to write CSS selectors that are surgical ยท targeting exactly the right element in exactly the right context, without needing extra classes or IDs.
Before learning combinators, you need to understand two ideas: CSS selectors and the HTML family-tree structure.
1. What is a CSS Selector?
A CSS selector is the part of a CSS rule that identifies which HTML element(s) you want to style.
/* Selector โ Style rule */
p {
color: blue;
}Here, p is the selector ยท it targets every <p> element on the page.
You already know simple selectors:
pยท selects all paragraph elements.highlightยท selects all elements with class "highlight"#headerยท selects the element with id "header"
But sometimes you need to target an element only when it has a specific relationship to another element.
2. The HTML Tree ยท Parents, Children, Descendants, and Siblings
HTML elements are nested inside each other, forming a structure like a family tree. Understanding this tree is the key to understanding combinators.
Consider this HTML:
<div> โ parent
<p>First</p> โ child of div, sibling of Second and Third
<p>Second</p> โ child of div, sibling of First and Third
<span>
<p>Third</p> โ child of span, grandchild of div (descendant)
</span>
</div>
<p>Outside</p> โ NOT inside div at allVocabulary you need:
- Parent ยท the element directly containing another element. In the example above,
<div>is the parent of the first two<p>tags. - Child ยท an element directly inside a parent. The two
<p>tags and the<span>are direct children of<div>. - Descendant ยท any element inside another element, no matter how deeply nested. The third
<p>(inside<span>inside<div>) is a descendant of<div>, even though it is not a direct child. - Sibling ยท two elements that share the same parent. The two
<p>tags inside<div>are siblings of each other. - Adjacent sibling ยท a sibling that comes immediately after another in the HTML code.
Analogy: Think of it like a real family. Your parent is one level above you. Your siblings share the same parent. Your descendants are your children, grandchildren, great-grandchildren, etc.
What is a CSS Combinator?
A combinator is a special character placed between two selectors that tells the browser about the relationship required between those two elements for the style to apply.
/* General pattern */
selector1 COMBINATOR selector2 {
/* styles apply to selector2 when it has the right relationship to selector1 */
}There are four CSS combinators:
| Name | Symbol | Meaning |
|---|---|---|
| Descendant | (a space) | Any matching element anywhere inside the first |
| Child | > | Only direct children of the first |
| Adjacent Sibling | + | Only the immediately following sibling |
| General Sibling | ~ | All following siblings |
๐ก Key insight: Combinators do NOT style the first selector ยท they style the second selector, but only when the first selector's relationship condition is met. The first selector sets the context; the second is what actually gets styled.
Combinator 1 ยท The Descendant Selector (space)
Symbol: A single space between two selectors: A B
What it does: Selects every element matching B that is located anywhere inside element A ยท whether it is a direct child, grandchild, great-grandchild, or even deeper.
Think of it like: "Give me all the <p> tags that live inside this <div>, no matter how deep they are buried."
Syntax:
ancestor descendant {
/* styles */
}Simple Example 1 ยท Descendant Selector
<!DOCTYPE html>
<html>
<head>
<style>
div p {
background-color: yellow;
}
</style>
</head>
<body>
<div>
<p>Paragraph 1 โ inside div (styled โ
)</p>
<p>Paragraph 2 โ inside div (styled โ
)</p>
<section>
<p>Paragraph 3 โ inside section inside div (styled โ
)</p>
</section>
</div>
<p>Paragraph 4 โ OUTSIDE div (NOT styled โ)</p>
</body>
</html>Expected Output (visually):
[yellow background] Paragraph 1 โ inside div
[yellow background] Paragraph 2 โ inside div
[yellow background] Paragraph 3 โ inside section inside div
Paragraph 4 โ OUTSIDE div โ no yellow backgroundBreaking down the code:
div p ยท The space between div and p is the descendant combinator. It tells the browser: "Find all <p> elements that exist anywhere inside a <div>."
Notice that Paragraph 3 is inside <section> which is inside <div>. Even though there is an extra layer (section) between div and p, the descendant selector still finds it because it searches all levels down.
Paragraph 4 is outside the <div>, so it is NOT styled.
๐ค Thinking prompt: What if you added another
<p>inside a<span>inside the<div>? Would it get styled? Yes! The descendant selector goes as deep as needed.
Combinator 2 ยท The Child Selector (>)
Symbol: > (greater-than sign): A > B
What it does: Selects only elements matching B that are direct children of A. Grandchildren and deeper descendants are NOT selected.
Think of it like: "Only style my immediate children ยท not my grandchildren."
Syntax:
parent > direct-child {
/* styles */
}Simple Example 2 ยท Child Selector
<!DOCTYPE html>
<html>
<head>
<style>
div > p {
background-color: lightblue;
}
</style>
</head>
<body>
<div>
<p>Paragraph 1 โ direct child of div (styled โ
)</p>
<p>Paragraph 2 โ direct child of div (styled โ
)</p>
<section>
<p>Paragraph 3 โ child of section, NOT direct child of div (NOT styled โ)</p>
</section>
</div>
<p>Paragraph 4 โ outside div (NOT styled โ)</p>
</body>
</html>Expected Output (visually):
[light blue] Paragraph 1 โ direct child of div
[light blue] Paragraph 2 โ direct child of div
Paragraph 3 โ child of section, NOT direct child of div โ no styling
Paragraph 4 โ outside div โ no stylingBreaking down the code:
div > p ยท The > means "direct child only." Paragraphs 1 and 2 are direct children of <div>, so they are styled. Paragraph 3 is a direct child of <section>, not of <div> ยท so it is not styled by this rule.
Descendant vs. Child ยท The Key Difference:
| Selector | Selects Paragraph 3 inside <section> inside <div>? |
|---|---|
div p (descendant) | โ Yes ยท selects all levels deep |
div > p (child) | โ No ยท only direct children |
๐ค Thinking prompt: When would you want to use
>instead of(space)? Use>when you have nested structures and only want to style one specific level ยท for example, styling only the top-level menu items in a nav bar, but not items inside sub-menus.
Combinator 3 ยท The Adjacent Sibling Selector (+)
Symbol: + (plus sign): A + B
What it does: Selects the single element matching B that comes immediately after element A, AND both must share the same parent.
Think of it like: "Style only my next-door neighbour ยท the one who comes right after me."
Key rules:
- Both
AandBmust share the same parent Bmust appear directly afterAin the HTML ยท no other elements between them- Only one element is selected (the immediate next sibling), not multiple
Syntax:
element + immediately-following-sibling {
/* styles */
}Simple Example 3 ยท Adjacent Sibling Selector
<!DOCTYPE html>
<html>
<head>
<style>
h2 + p {
background-color: lightgreen;
}
</style>
</head>
<body>
<h2>A Heading</h2>
<p>This paragraph is right after h2 (styled โ
)</p>
<p>This paragraph is NOT right after h2 (NOT styled โ)</p>
<h2>Another Heading</h2>
<p>This paragraph is right after h2 again (styled โ
)</p>
<p>This one is not right after h2 (NOT styled โ)</p>
</body>
</html>Expected Output (visually):
A Heading
[light green] This paragraph is right after h2
This paragraph is NOT right after h2
Another Heading
[light green] This paragraph is right after h2 again
This one is not right after h2Breaking down the code:
h2 + p ยท "Find a <p> that comes immediately after an <h2>."
The first <p> after each <h2> is styled. The second, third, or any further <p> after the heading is not styled ยท only the immediately adjacent one.
๐ Real-World Use: This pattern is extremely common for styling the paragraph that follows a section heading differently ยท for example, making the first paragraph after a heading slightly larger (a "lead paragraph" or "drop cap" effect).
Second Example ยท What Happens When Another Element Is Between Them?
<!DOCTYPE html>
<html>
<head>
<style>
h2 + p {
color: red;
}
</style>
</head>
<body>
<h2>Heading</h2>
<span>I am between the h2 and the p!</span>
<p>This p is NOT right after h2 โ a span is in the way (NOT styled โ)</p>
</body>
</html>Expected Output:
Heading
I am between the h2 and the p!
This p is NOT right after h2 โ a span is in the way โ NOT redBecause the <span> is between the <h2> and the <p>, the <p> is no longer the immediate sibling of <h2>. The + combinator is not applied.
๐ก Important: The
+combinator is very strict ยท there must be zero other elements betweenAandB. Even a single<br>or<span>in between will break the match.
Combinator 4 ยท The General Sibling Selector (~)
Symbol: ~ (tilde): A ~ B
What it does: Selects all elements matching B that come after element A, as long as they share the same parent. They do not need to be immediately adjacent ยท there can be other elements between them.
Think of it like: "Style all of my younger siblings ยท not just the one right next to me, but all of them who come after me."
Key differences from +:
+selects only the one immediately following sibling~selects all following siblings that match
Syntax:
element ~ all-following-siblings {
/* styles */
}Simple Example 4 ยท General Sibling Selector
<!DOCTYPE html>
<html>
<head>
<style>
h2 ~ p {
background-color: lightyellow;
}
</style>
</head>
<body>
<p>This p comes BEFORE h2 (NOT styled โ)</p>
<h2>The Heading</h2>
<p>Paragraph 1 after h2 (styled โ
)</p>
<p>Paragraph 2 after h2 (styled โ
)</p>
<span>I am a span โ not a p, so not styled</span>
<p>Paragraph 3 after h2 (still styled โ
)</p>
</body>
</html>Expected Output (visually):
This p comes BEFORE h2 โ NOT styled
The Heading
[light yellow] Paragraph 1 after h2
[light yellow] Paragraph 2 after h2
I am a span โ not a p, so not styled
[light yellow] Paragraph 3 after h2Breaking down the code:
h2 ~ p ยท "Find all <p> elements that come after an <h2>, anywhere after it, as long as they share the same parent."
Notice:
- The
<p>before the<h2>is NOT styled ยท the~combinator only looks forward, never backward. - The
<span>is ignored ยท it is not a<p>so the selector doesn't match it. - The third
<p>after the<span>is still styled ยท unlike+, the~selector doesn't care about other elements in between, as long as the<p>comes after the<h2>.
Here is a diagram to show the structure and what each combinator selects:
HTML Structure:
<div>
<p>A</p> โ direct child of div
<section>
<p>B</p> โ grandchild of div, child of section
</section>
<p>C</p> โ direct child of div
<p>D</p> โ direct child of div
</div>
<p>E</p> โ outside div entirely| Selector | What gets styled |
|---|---|
div p (descendant) | A, B, C, D ยท all p's inside div at any depth |
div > p (child) | A, C, D ยท only direct children of div |
section + p | C ยท the p immediately after section |
section ~ p | C, D ยท all p's that come after section |
Example 5 ยท Descendant Selector in a Navigation Menu
<!DOCTYPE html>
<html>
<head>
<style>
/* Style ALL links that are anywhere inside the nav element */
nav a {
color: white;
text-decoration: none;
font-weight: bold;
}
nav {
background-color: #333;
padding: 10px;
}
</style>
</head>
<body>
<nav>
<a href="#">Home</a>
<div>
<a href="#">Products</a> <!-- nested inside a div, but still inside nav -->
<a href="#">Services</a>
</div>
</nav>
<a href="#">This link is outside nav โ not styled</a>
</body>
</html>Expected Output:
[dark bar with white bold links] Home Products Services
This link is outside nav โ not styled โ default blue underlined linkThe nav a selector styles ALL anchor tags anywhere inside <nav>, regardless of whether they are direct children or inside nested <div> elements.
Example 6 ยท Child Selector for a List Menu (Avoids Deep Nesting)
<!DOCTYPE html>
<html>
<head>
<style>
/* Style only DIRECT children of ul.menu โ not nested sub-menu items */
ul.menu > li {
list-style-type: none;
display: inline-block;
padding: 10px 20px;
background-color: #4a90d9;
color: white;
margin: 2px;
}
</style>
</head>
<body>
<ul class="menu">
<li>Home</li>
<li>About
<ul>
<li>Team</li> <!-- NOT styled โ this is a child of inner ul, not menu -->
<li>History</li> <!-- NOT styled -->
</ul>
</li>
<li>Contact</li>
</ul>
</body>
</html>Expected Output (visually):
[blue box: Home] [blue box: About] [blue box: Contact]
Team โ plain text, no blue box
History โ plain text, no blue boxUsing ul.menu > li instead of ul.menu li (descendant) means only the top-level list items get the blue button styling ยท the nested sub-menu items are left unstyled.
๐ Real-World Connection: This exact pattern ยท
nav ul > liยท is used in almost every multi-level navigation menu on the web to style top-level items differently from sub-menu items.
Example 7 ยท Adjacent Sibling for a Lead Paragraph
<!DOCTYPE html>
<html>
<head>
<style>
/* Style only the first paragraph after each section heading */
h2 + p {
font-size: 18px;
font-style: italic;
color: #555;
}
p {
font-size: 14px;
color: #333;
}
</style>
</head>
<body>
<h2>Introduction</h2>
<p>This is the lead paragraph โ larger and italic.</p>
<p>This is a regular paragraph โ smaller and normal.</p>
<p>Another regular paragraph.</p>
<h2>Chapter One</h2>
<p>This is the lead paragraph for Chapter One โ larger and italic.</p>
<p>Back to regular size.</p>
</body>
</html>Expected Output:
Introduction
[18px italic grey] This is the lead paragraph โ larger and italic.
[14px normal] This is a regular paragraph โ smaller and normal.
[14px normal] Another regular paragraph.
Chapter One
[18px italic grey] This is the lead paragraph for Chapter One โ larger and italic.
[14px normal] Back to regular size.The h2 + p rule fires once per heading ยท only the paragraph immediately following the <h2> gets the special treatment.
Example 8 ยท General Sibling for a "Selected" State Effect
<!DOCTYPE html>
<html>
<head>
<style>
/* When a radio button is selected, style all p siblings after it */
input[type="radio"]:checked ~ p {
color: green;
font-weight: bold;
}
</style>
</head>
<body>
<input type="radio" name="demo" id="on">
<label for="on">Toggle on</label>
<p>This paragraph changes when the radio is checked!</p>
<p>So does this one!</p>
</body>
</html>Expected Output:
- Before clicking: both paragraphs appear in normal black text.
- After clicking the radio button: both paragraphs turn green and bold.
This uses the ~ combinator with :checked pseudo-class to create a CSS-only toggle effect ยท no JavaScript needed!
Exercise 1 ยท Warm-Up: Descendant vs. Child
Objective: Understand the visual difference between descendant and child combinators.
Scenario: You are building a content page with a main article and a sidebar. You want to colour the paragraphs in the article but NOT the paragraphs in the sidebar.
<!DOCTYPE html>
<html>
<head>
<style>
/* YOUR TASK: Write a selector here that colours ONLY the
paragraphs inside #article, NOT those inside #sidebar */
#article p {
color: darkblue;
}
</style>
</head>
<body>
<div id="article">
<p>Article paragraph 1</p>
<p>Article paragraph 2</p>
<div class="pull-quote">
<p>Quoted text inside article</p>
</div>
</div>
<div id="sidebar">
<p>Sidebar paragraph โ should NOT be blue</p>
</div>
</body>
</html>Expected Output:
[dark blue] Article paragraph 1
[dark blue] Article paragraph 2
[dark blue] Quoted text inside article โ styled because it's a DESCENDANT of #article
Sidebar paragraph โ should NOT be blue โ normal black textSelf-check Questions:
- The "Quoted text inside article" is inside a
<div class="pull-quote">which is inside#article. Is it styled? Why? - What would you change to make
#article > pinstead? Which paragraph would then NOT be styled? - Try it: change
#article pto#article > p. What changes?
Exercise 2 ยท Building a Table of Contents with Sibling Selectors
Objective: Use sibling selectors to style headings and the content that follows them.
Scenario: You are formatting a recipe page. Each recipe section has an <h3> title followed by several elements. You want to:
- Make the first paragraph after each
<h3>(the description) appear in italic - Make ALL paragraphs after each
<h3>have a slightly indented left margin
Starter Code:
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Georgia, serif;
max-width: 600px;
margin: 30px auto;
}
/* Adjacent sibling: first p right after h3 โ the recipe description */
h3 + p {
font-style: italic;
color: #666;
}
/* General sibling: ALL p after h3 โ indent them all */
h3 ~ p {
margin-left: 20px;
}
</style>
</head>
<body>
<h3>Jollof Rice</h3>
<p>A rich, smoky one-pot rice dish loved across West Africa.</p>
<p>Prep time: 20 minutes. Cook time: 45 minutes. Serves: 6.</p>
<p>Pairs well with fried plantain and grilled chicken.</p>
<h3>Egusi Soup</h3>
<p>A hearty melon seed soup with leafy greens and meat or fish.</p>
<p>Prep time: 30 minutes. Cook time: 1 hour. Serves: 8.</p>
</body>
</html>Expected Output:
Jollof Rice
[italic grey indented] A rich, smoky one-pot rice dish loved across West Africa.
[indented] Prep time: 20 minutes. Cook time: 45 minutes. Serves: 6.
[indented] Pairs well with fried plantain and grilled chicken.
Egusi Soup
[italic grey indented] A hearty melon seed soup with leafy greens and meat or fish.
[indented] Prep time: 30 minutes. Cook time: 1 hour. Serves: 8.Note: The first paragraph after each <h3> gets BOTH the h3 + p style (italic, grey) AND the h3 ~ p style (margin-left). This is because it matches both selectors ยท CSS applies them both!
Self-check Questions:
- Why does the first paragraph get both
font-style: italicandmargin-left: 20px? - Do the second and third paragraphs get
font-style: italic? Why or why not? - If you added a
<hr>line between the<h3>and the first<p>, would theh3 + prule still match? Why or why not?
Exercise 3 ยท Navigation Menu with Child Combinator
Objective: Build a two-level navigation menu where only the top-level items are styled as tab buttons.
<!DOCTYPE html>
<html>
<head>
<style>
/* Reset */
* { box-sizing: border-box; margin: 0; padding: 0; }
.navbar {
background-color: #2c3e50;
}
/* Style ONLY direct children of .navbar > ul โ the top-level items */
.navbar > ul > li {
display: inline-block;
padding: 14px 20px;
color: white;
font-family: Arial, sans-serif;
font-size: 15px;
cursor: pointer;
position: relative;
}
.navbar > ul > li:hover {
background-color: #e74c3c;
}
/* Sub-menu styling โ nested uls */
.navbar ul {
list-style: none;
}
.navbar > ul > li > ul {
display: none;
position: absolute;
background-color: #34495e;
top: 100%;
left: 0;
min-width: 150px;
}
.navbar > ul > li:hover > ul {
display: block;
}
.navbar > ul > li > ul > li {
display: block;
padding: 10px 15px;
color: #ccc;
font-size: 14px;
}
.navbar > ul > li > ul > li:hover {
background-color: #e74c3c;
color: white;
}
</style>
</head>
<body>
<nav class="navbar">
<ul>
<li>Home</li>
<li>Products
<ul>
<li>Laptops</li>
<li>Tablets</li>
<li>Phones</li>
</ul>
</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
<p style="padding: 20px;">Hover over "Products" to see the sub-menu.</p>
</body>
</html>Expected Output:
[dark bar] Home Products About Contact
(On hover over Products, a dropdown appears:)
Laptops
Tablets
PhonesSelf-check Questions:
- Why do we use
.navbar > ul > liinstead of.navbar li? What would break if we used the descendant combinator instead? - What does
position: relativeon the parent<li>andposition: absoluteon the sub-menu<ul>achieve? - Can you add another top-level item called "Blog" with two sub-items: "News" and "Tutorials"?
Mistake 1 ยท Confusing Descendant (space) with Child (>)
/* WRONG intent: want to style only direct children */
div p { /* โ This styles ALL p descendants, not just direct children */
color: red;
}
/* CORRECT: use > for direct children only */
div > p {
color: red;
}When this bites you: You style a nav bar's top-level items, but the nested sub-menu items also get the same style unexpectedly.
Mistake 2 ยท Expecting + to Match When There Is an Element In Between
<h2>Title</h2>
<span>Some note</span> <!-- โ This breaks the adjacency! -->
<p>Lead paragraph</p> <!-- h2 + p will NOT match this -->/* This will NOT work because span is between h2 and p */
h2 + p {
font-style: italic;
}Fix: Either remove the element in between, or use h2 ~ p if you don't need strict adjacency.
Mistake 3 ยท Expecting ~ to Work Backwards
<p>This comes BEFORE h2</p>
<h2>Heading</h2>
<p>This comes AFTER h2</p>/* h2 ~ p only styles p that come AFTER h2, never before */
h2 ~ p {
color: green;
}
/* Result: only the SECOND <p> is green โ the first <p> is NOT affected */Why this happens: All sibling combinators (+ and ~) only look forward in the document ยท they never look backward.
Mistake 4 ยท Confusing + (one sibling) with ~ (all siblings)
/* Styles only the FIRST p after h2 */
h2 + p { font-size: 20px; }
/* Styles ALL p elements after h2 */
h2 ~ p { font-size: 20px; }Beginners often write h2 + p when they mean "all paragraphs after a heading." If you want all of them, use ~.
Mistake 5 ยท Forgetting That Combinators Style the SECOND Element
/* This does NOT change the div โ it changes the p */
div p {
color: red;
}The div is used to set the context (the p must be inside a div). The div itself is not styled by this rule. Only the <p> gets color: red.
Mistake 6 ยท Writing Extra Spaces Where They Change Meaning
div>p { ... } /* Valid โ same as div > p */
div > p { ... } /* Valid โ spaces around > are optional */
divp { ... } /* INVALID โ this reads as a selector for <divp> element! */Always make sure there is either a proper combinator symbol (>, +, ~) or a clear space between selectors. No space and no symbol means the browser treats it as one single selector name.
In this project, you will style a complete news article page using all four CSS combinators in a meaningful, practical way.
Project Overview
You are building the article detail page for a fictional online magazine called "The Codebreaker". The article page needs:
- Body text paragraphs styled in dark grey
- The first paragraph after each section heading styled as a "lead paragraph" (larger, italic)
- All paragraphs following section headings indented
- Navigation links styled using descendant combinator
- Section headings and their direct content styled differently from the main body
Stage 1 ยท HTML Structure
<!DOCTYPE html>
<html>
<head>
<title>The Codebreaker โ Article</title>
<link rel="stylesheet" href="article.css">
</head>
<body>
<!-- Navigation using descendant combinator -->
<nav id="top-nav">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Tech</a></li>
<li><a href="#">Science</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<!-- Article content using all combinators -->
<article id="main-article">
<h1>How CSS Changed the Web Forever</h1>
<p class="meta">Published by Ada Okafor ยท April 2026</p>
<h2>The Beginning</h2>
<p>Before CSS, all styling was done inline or with HTML attributes. Pages were messy, hard to maintain, and looked identical everywhere.</p>
<p>CSS was introduced to separate structure from presentation, making web development cleaner and more powerful.</p>
<p>Today, CSS is used by millions of developers worldwide to build beautiful, responsive interfaces.</p>
<h2>The Revolution</h2>
<p>When CSS2 arrived, developers gained new tools for layout, positioning, and more expressive typography.</p>
<p>It allowed a single stylesheet to control the appearance of hundreds of pages simultaneously.</p>
<h2>What Comes Next</h2>
<p>CSS continues to evolve with features like Grid, Flexbox, custom properties, and container queries.</p>
<p>The future is bright for web designers and developers who invest time in mastering this language.</p>
</article>
<!-- Sidebar using child combinator -->
<aside id="sidebar">
<h3>Related Articles</h3>
<ul>
<li><a href="#">Understanding Flexbox</a></li>
<li><a href="#">CSS Grid Complete Guide</a></li>
<li><a href="#">The History of HTML</a></li>
</ul>
</aside>
</body>
</html>