CSS Sibling Selectors: How to Select Elements
Learn when to use CSS `+` and `~` sibling selectors, how their direction works, and how `:has()` can match an earlier sibling based on what follows.
CSS sibling selectors match elements that share the same parent. Use the adjacent sibling combinator + when the target must come immediately after the first element; use the general sibling combinator ~ when the target can appear anywhere later among that parent’s children. Both select forward: the selector on the right is the element matched.
1. A minimal example
Here, all four elements have the same parent, the <main> element:
<main>
<h2>Heading</h2>
<p>First paragraph</p>
<div>Callout</div>
<p>Second paragraph</p>
</main>
h2 + p {
margin-top: 0;
}
h2 ~ p {
color: #444;
}
h2 + p matches only the first paragraph: it is the element immediately after the heading. h2 ~ p matches both paragraphs: each appears later than the heading under the same parent, even though a div comes between the heading and the second paragraph.
2. Choose the right sibling combinator
| Selector | What it matches | Use it when |
|---|---|---|
A + B |
A B immediately preceded by an A sibling |
Only the next element should match |
A ~ B |
A later B sibling after an A, with any number of intervening siblings |
Any qualifying later element should match |
For example, if a form label is directly followed by an input, label + input targets that input. If a section heading should affect every paragraph later in the same section container, h2 ~ p can target those paragraphs, including ones separated by other elements.
The selector to the right of the combinator is the target in both cases. These are not symmetric: h2 + p does not match the heading, and h2 ~ p does not select elements before the heading.
3. Siblings, children, and descendants
Sibling combinators require the matched elements to have the same immediate parent. They do not cross a nesting boundary. Compare these relationships:
| Syntax | Relationship | Example |
|---|---|---|
A B |
A descendant B at any depth inside A |
article p |
A > B |
A direct child B of A |
ul > li |
A + B |
The immediately following sibling B |
h2 + p |
A ~ B |
A later sibling B |
h2 ~ p |
For instance, this does not make the paragraph a sibling of the heading:
<div class="panel">
<h2>Heading</h2>
<div class="body">
<p>Nested paragraph</p>
</div>
</div>
h2 ~ p does not match that paragraph because the heading and paragraph have different parents. A selector such as h2 ~ .body p expresses a different relationship: a later sibling body container, then a paragraph inside it.
4. How to select a previous sibling
There is no standalone backward sibling combinator. p + h2 selects an h2 after a paragraph; it does not select the paragraph before an h2.
When the desired condition is “select this element if its next sibling is a paragraph,” use :has() with a relative selector:
h2:has(+ p) {
margin-bottom: 0;
}
This matches an h2 whose immediately following sibling is a paragraph. For a later sibling condition, the relative selector can use ~:
h2:has(~ p) {
border-bottom: 1px solid #ccc;
}
MDN marks :has() Baseline 2023. Check its support against your project’s target browsers, especially if you support older or constrained embedded browsers. See MDN’s :has() reference.
5. Practical patterns and selector details
Match a specific class or state
.field--invalid + .field-error {
display: block;
}
.is-open ~ .menu-item {
opacity: 1;
}
The combinator connects compound selectors too. The left and right sides can include classes, attributes, pseudo-classes, or element names, provided the resulting selector is valid.
Use the sibling relationship inside a larger selector
.card h2 + p {
margin-top: 0;
}
This means a paragraph immediately following an h2 that is inside a .card. The h2 and p still need to be siblings; the shared ancestor prefix does not make different nesting levels siblings.
Remember that matching is based on the DOM
Visual layout does not change sibling relationships. Flexbox or grid ordering can make elements appear in a different visual order, but combinators still follow the document tree order. Likewise, whitespace and comments in the HTML source do not create element siblings or break adjacency; another element between the two does.
6. Troubleshooting sibling selectors
| Symptom | Likely cause | What to check or change |
|---|---|---|
A + B never matches |
Another element sits between A and B, or they do not share a parent | Inspect the rendered DOM. Use ~ if intervening siblings should be allowed, or correct the markup/selector for the actual parent relationship. |
A ~ B never matches |
B comes before A, or B is nested under a different parent | Check document order and immediate parent. The general sibling combinator only reaches later siblings. |
| A nested element is not selected | The target is a descendant rather than a sibling | Write a descendant or child relationship for the nested structure, such as A .content p or A > .content. |
| The earlier element does not respond to a later one | The selector is written in the forward direction | Use :has(+ B) or :has(~ B) on the earlier element, and check :has() support for target browsers. |
| The rule matches but the style is not visible | Another declaration wins in the cascade, or the property has no visible effect in context | Inspect the matched element’s computed styles and competing rules in browser developer tools; verify the property and its value. |
| The rule works in one browser but not a target environment | The target has limited support, particularly for :has() |
Check compatibility for the exact browser and version you support, then provide a fallback or adjust the markup. |
7. Performance and reliability notes
For ordinary component styles, prefer the selector that states the intended relationship directly: + for immediate adjacency and ~ for later siblings. Keep selectors understandable and verify the actual DOM produced by templates or client-side rendering. If a framework conditionally inserts an element between two nodes, an adjacent selector stops matching; a general sibling selector may still match if the target remains later under the same parent.
The ~ combinator has been widely available and MDN documents it as available across browsers since July 2015. That date is useful context, but check unusual legacy or embedded browser targets. See MDN’s subsequent-sibling combinator reference. For the broader model of selector relationships, see MDN’s selectors and combinators guide.
8. Or skip the browser setup
To inspect a page’s rendered layout, capture it with ScreenshotNeo, a website screenshot API and MCP server. One GET request returns an image or PDF; the service also offers full-page capture, CSS selector element capture, custom CSS and JavaScript, and viewport options. The API parameter names other screenshot APIs use also work, which can make switching easier. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Next-sibling_combinator \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Next-sibling_combinator",
},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Next-sibling_combinator'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for the free plan.
9. FAQ
Does ~ match only the next sibling?
No. It matches qualifying later siblings, whether or not other sibling elements appear between them.
Can a sibling selector target text nodes?
No. These selectors match elements. Text nodes are not selected by CSS selectors.
Do the two siblings have to be the same element type?
No. They only need to be elements that match the selector parts and share the same parent.
Where can I read more about CSS selector syntax?
MDN’s selectors and combinators guide covers how these relationships fit into CSS selectors. For a broader book-length reference, O’Reilly lists CSS: The Definitive Guide, 5th Edition.


