Combinators are special selectors that target elements based on their relationship with other elements in the HTML structure. They allow you to select elements that are descendants, children, or siblings of other elements, giving you precise control over which elements get styled.
What Are Combinators?
In CSS, a combinator is a character or sequence that explains the relationship between two selectors. Instead of selecting elements in isolation, combinators let you select elements based on where they appear in relation to other elements.
There are four main types of combinators in CSS:
- Descendant Combinator (space) - Selects elements nested inside other elements
- Child Combinator (>) - Selects direct children only
- Adjacent Sibling Combinator (+) - Selects the next sibling element
- General Sibling Combinator (~) - Selects all following sibling elements
Remember: Understanding HTML structure (parent-child relationships, nesting, siblings) is crucial for using combinators effectively. If you need a refresher on HTML document structure, review our HTML tutorials.
Descendant Combinator (Space)
The descendant combinator is a space between two selectors. It selects all elements that are nested inside another element, regardless of how deeply nested they are.
Syntax
/* Syntax: selector1 selector2 (space between) */
ancestor descendant {
property: value;
}
/* Examples */
div p {
/* Selects all <p> inside any <div> */
color: blue;
}
.container a {
/* Selects all <a> inside elements with class "container" */
color: red;
}
article h2 {
/* Selects all <h2> inside <article> elements */
font-size: 24px;
}How It Works
The descendant combinator matches any element that is a descendant (child, grandchild, great-grandchild, etc.) of the specified ancestor element.
<div class="container">
<p>This paragraph will be styled</p>
<div>
<p>This nested paragraph will also be styled</p>
<section>
<p>Even this deeply nested paragraph will be styled</p>
</section>
</div>
</div>
<p>This paragraph won't be styled (not inside .container)</p>/* Selects ALL paragraphs inside .container, no matter how deep */
.container p {
color: blue;
font-style: italic;
}Descendant Combinator Example
Common Use Cases
- Navigation links:
nav atargets all links inside navigation - List items:
ul lior.menu li - Form elements:
form inputor.form-group label - Card content:
.card por.card img
/* Style all links inside navigation */
nav a {
color: white;
text-decoration: none;
padding: 10px 15px;
}
/* Style all images inside articles */
article img {
max-width: 100%;
height: auto;
border-radius: 8px;
}
/* Style all list items in a menu */
.menu li {
list-style: none;
padding: 8px 0;
}
/* Style inputs inside a specific form */
.contact-form input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
}Multiple Levels
You can chain multiple descendant combinators:
/* Selects <a> inside <li> inside <ul> inside <nav> */
nav ul li a {
color: white;
}
/* Selects <p> inside <div> inside <article> */
article div p {
line-height: 1.8;
}
/* Be careful: too specific can be hard to override */
header nav ul li a.active {
font-weight: bold;
}Avoid Over-Specificity: While chaining descendant selectors is powerful, using too many levels creates overly specific selectors that are hard to override and maintain. Try to keep your selectors as simple as possible—use classes instead of deep nesting when you can.
Child Combinator (>)
The child combinator is a greater-than sign (>) between two selectors. It selects elements that are direct children only—not grandchildren or deeper descendants.
Syntax
/* Syntax: selector1 > selector2 (with >) */
parent > child {
property: value;
}
/* Examples */
div > p {
/* Selects <p> that are direct children of <div> only */
color: blue;
}
.container > h2 {
/* Selects <h2> that are direct children of .container */
font-size: 28px;
}
ul > li {
/* Selects <li> that are direct children of <ul> */
list-style: disc;
}Descendant vs Child: The Difference
This is a critical distinction. Let's see them side by side:
<div class="parent">
<p>Direct child paragraph</p>
<div class="nested">
<p>Grandchild paragraph (not a direct child)</p>
</div>
</div>/* Descendant combinator - matches BOTH paragraphs */
.parent p {
color: blue;
}
/* Child combinator - matches ONLY the direct child */
.parent > p {
color: red;
}
/* Result:
- First <p> is red (direct child)
- Second <p> is blue (descendant but not direct child)
*/Child Combinator Example
When to Use Child Combinator
- Direct list styling: Style only top-level list items, not nested ones
- Card structure: Target direct children of a card component
- Grid/Flex items: Style only direct children of flex or grid containers
- Preventing style inheritance: When you don't want styles to cascade down
/* Style only top-level list items (not nested lists) */
.menu > li {
padding: 10px;
border-bottom: 1px solid #ddd;
}
/* Style direct children of flex container */
.flex-container > div {
flex: 1;
padding: 20px;
}
/* Style only direct paragraphs in a card */
.card > p {
margin-bottom: 15px;
}
/* Nested navigation - only direct children */
nav > ul > li {
display: inline-block;
margin-right: 20px;
}Practical Example: Nested Lists
A common use case is styling multi-level lists differently:
Nested Lists with Child Combinator
Adjacent Sibling Combinator (+)
The adjacent sibling combinator is a plus sign (+) between two selectors. It selects an element that is immediately after another specific element, and both must share the same parent.
Syntax
/* Syntax: selector1 + selector2 (with +) */
element1 + element2 {
property: value;
}
/* Examples */
h2 + p {
/* Selects <p> that comes immediately after <h2> */
font-weight: bold;
}
.intro + .content {
/* Selects .content that comes immediately after .intro */
margin-top: 20px;
}
label + input {
/* Selects <input> that comes immediately after <label> */
display: block;
}Understanding "Adjacent"
"Adjacent" means the element must be the very next sibling. If there's any other element in between, the selector won't match.
<h2>Heading</h2>
<p>This paragraph is immediately after h2 - WILL be styled</p>
<p>This second paragraph - WON'T be styled (not immediately after h2)</p>
<h2>Another Heading</h2>
<div>This div is in between</div>
<p>This paragraph - WON'T be styled (div is in between)</p>h2 + p {
/* Only matches the FIRST paragraph after each h2 */
font-size: 18px;
color: #e74c3c;
}Adjacent Sibling Combinator Example
Common Use Cases
- First paragraphs: Style the intro paragraph after headings
- Form spacing: Add margin between form elements
- List styling: Style elements after specific list items
- Image captions: Style text immediately after images
/* Make the first paragraph after headings larger */
h1 + p,
h2 + p {
font-size: 1.2em;
color: #555;
}
/* Add spacing between form fields */
input + input {
margin-top: 10px;
}
/* Style caption after images */
img + p {
font-size: 12px;
color: #888;
font-style: italic;
text-align: center;
}
/* Add margin after checked checkboxes */
input[type="checkbox"]:checked + label {
font-weight: bold;
color: green;
}Practical Example: Form Styling
Adjacent Sibling in Forms
General Sibling Combinator (~)
The general sibling combinator is a tilde (~) between two selectors. It selects all elements that are siblings of a specified element (not just the immediately adjacent one), as long as they come after it.
Syntax
/* Syntax: selector1 ~ selector2 (with ~) */
element1 ~ element2 {
property: value;
}
/* Examples */
h2 ~ p {
/* Selects ALL <p> that follow an <h2> (same parent) */
color: gray;
}
.active ~ li {
/* Selects all <li> that follow .active */
opacity: 0.5;
}
input:checked ~ label {
/* Selects all <label> that follow a checked input */
font-weight: bold;
}Adjacent vs General Sibling: The Difference
The key difference from the adjacent sibling combinator:
+(Adjacent) - Only the next sibling~(General) - All following siblings
<div>
<h2>Heading</h2>
<p>First paragraph</p>
<p>Second paragraph</p>
<p>Third paragraph</p>
<div>A div element</div>
<p>Fourth paragraph</p>
</div>/* Adjacent sibling (+) - only first paragraph */
h2 + p {
color: red; /* Only first <p> */
}
/* General sibling (~) - all following paragraphs */
h2 ~ p {
color: blue; /* All four <p> elements */
}General Sibling Combinator Example
Common Use Cases
- Section content: Style all paragraphs after a heading
- Tab panels: Show/hide content based on active tab
- Accordion sections: Toggle visibility of sibling content
- Checkbox/radio styling: Style related elements based on input state
/* Style all paragraphs after an important heading */
.important ~ p {
font-weight: 600;
color: #e74c3c;
}
/* Hide all sections after the active one */
.active ~ .section {
display: none;
}
/* Style all items after the selected one differently */
.selected ~ .item {
opacity: 0.5;
}
/* Custom checkbox styling */
input[type="checkbox"]:checked ~ .content {
display: block;
}Practical Example: Checkbox Toggle
Toggle Content with General Sibling Combinator
Combining Combinators
You can mix and match different combinators to create powerful, specific selectors:
/* Descendant + Child */
.container > div p {
/* Paragraphs inside divs that are direct children of .container */
}
/* Descendant + Adjacent Sibling */
article h2 + p {
/* First paragraph after h2 inside article */
}
/* Child + General Sibling */
nav > ul ~ div {
/* Divs that are siblings of ul (which is direct child of nav) */
}
/* Multiple combinators */
.sidebar > section h3 ~ p {
/* All paragraphs after h3 inside sections that are direct children of .sidebar */
}
/* Real-world example */
.blog-post > header + .content p:first-of-type {
font-size: 1.2em;
font-weight: 600;
}Combined Combinators Example
Keep It Simple: While combining combinators is powerful, overly complex selectors are hard to read, maintain, and override. Aim for clarity over cleverness. If a selector becomes too complex, consider using classes instead.
Combinator Quick Reference
CSS Combinators Summary
| Property | Values | Description |
|---|---|---|
Descendant (space) | selector1 selector2 | All elements that are descendants (any level deep) |
Child (>) | selector1 > selector2 | Elements that are direct children only |
Adjacent Sibling (+) | selector1 + selector2 | The element immediately following the first element |
General Sibling (~) | selector1 ~ selector2 | All elements following the first element |
Visual Summary
div p All <p> anywhere inside <div>
div > p Only <p> that are direct children of <div>
h2 + p Only the <p> immediately after <h2>
h2 ~ p All <p> elements after <h2> (same parent)Best Practices with Combinators
1. Prefer Classes Over Deep Nesting
/* Avoid: Too specific, hard to maintain */
header nav ul li a.active {
color: gold;
}
/* Better: Use a class */
.nav-link-active {
color: gold;
}
/* Avoid: Deep descendant chains */
.sidebar .widget .content .list .item .link {
color: blue;
}
/* Better: Flatten with classes */
.widget-link {
color: blue;
}2. Use Child Combinator to Prevent Cascade
/* Use child combinator when you don't want styles to cascade */
/* Bad: Affects all nested lists */
.menu li {
font-size: 18px;
}
/* Good: Only affects direct children */
.menu > li {
font-size: 18px;
}
/* Nested lists keep their own styling */
.menu li ul li {
font-size: 14px;
}3. Use Adjacent/General Sibling for Spacing
/* Add consistent spacing between elements */
/* Space between all paragraphs */
p + p {
margin-top: 1rem;
}
/* Space after headings */
h2 + * {
margin-top: 1.5rem;
}
/* Space between form fields */
.form-field ~ .form-field {
margin-top: 15px;
}4. Combine Combinators Thoughtfully
💡 Pro Tip:
Use combinators to create flexible, maintainable CSS, but don't go overboard. A good rule of thumb: if you can't quickly explain what a selector does, it's probably too complex. Consider using classes or splitting it into multiple simpler rules.
Complete Practical Example
Let's build a blog post layout using all four combinators:
<article class="blog-post">
<header>
<h1>Understanding CSS Combinators</h1>
<p class="meta">By John Doe • December 24, 2024</p>
</header>
<div class="content">
<p>This is the introduction paragraph...</p>
<h2>What Are Combinators?</h2>
<p>First paragraph explaining combinators...</p>
<p>Second paragraph with more details...</p>
<h2>Types of Combinators</h2>
<p>Another section begins here...</p>
<ul>
<li>Descendant combinator
<ul>
<li>Nested item 1</li>
<li>Nested item 2</li>
</ul>
</li>
<li>Child combinator</li>
<li>Sibling combinators</li>
</ul>
<div class="note">
<h3>Important Note</h3>
<p>This is a special note...</p>
</div>
</div>
<footer>
<p>Thanks for reading!</p>
</footer>
</article>/* Base article styling */
.blog-post {
max-width: 800px;
margin: 0 auto;
padding: 40px;
background-color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* Descendant combinator - all headers inside article */
.blog-post header {
border-bottom: 2px solid #eee;
padding-bottom: 20px;
margin-bottom: 30px;
}
/* Child combinator - direct child h1 only */
.blog-post > header > h1 {
font-size: 36px;
color: #2c3e50;
margin-bottom: 10px;
}
/* Adjacent sibling - meta info right after h1 */
.blog-post h1 + .meta {
color: #7f8c8d;
font-size: 14px;
font-style: italic;
}
/* Descendant combinator - all paragraphs in content */
.content p {
line-height: 1.8;
color: #444;
margin-bottom: 15px;
}
/* Adjacent sibling - first paragraph after h2 */
.content h2 + p {
font-size: 18px;
color: #555;
font-weight: 500;
}
/* General sibling - all paragraphs after h2 */
.content h2 ~ p {
margin-top: 10px;
}
/* Child combinator - only top-level list items */
.content > ul > li {
font-size: 16px;
margin-bottom: 10px;
color: #2c3e50;
}
/* Descendant - nested list items styled differently */
.content ul ul li {
font-size: 14px;
color: #7f8c8d;
}
/* Child combinator - direct children of .note */
.note > h3 {
color: #e67e22;
font-size: 18px;
margin-bottom: 10px;
}
.note > p {
background-color: #fef5e7;
padding: 15px;
border-left: 4px solid #e67e22;
}
/* Descendant combinator - footer paragraphs */
.blog-post footer p {
text-align: center;
color: #95a5a6;
font-size: 14px;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
}Key Takeaways
- Combinators target elements based on their relationships
- Descendant (space): Matches any nested element, regardless of depth
- Child (>): Matches direct children only, not grandchildren
- Adjacent Sibling (+): Matches the very next sibling element
- General Sibling (~): Matches all following sibling elements
- Child combinator prevents unwanted style cascade to nested elements
- Sibling combinators work only on elements with the same parent
- You can combine multiple combinators for specific targeting
- Keep selectors simple—prefer classes over deep nesting
- Combinators are powerful but can create overly specific selectors
What's Next?
Now that you understand combinators and can target elements based on their relationships, you're ready to learn about pseudo-classes. Pseudo-classes let you style elements based on their state (like :hover, :focus, :active) or position (like :first-child, :nth-child). This adds even more power to your CSS selectors!
Combinators are incredibly useful for writing clean, maintainable CSS. Practice using them, but remember: simpler is usually better. Use classes when they make your code clearer, and save combinators for situations where they provide real value.