The CSS :has() relational pseudo-class selects the element before it when a related element matches the selector inside the parentheses. For example, .card:has(> img) selects a card with an image as a direct child; it selects the card, not the image. The selector can test descendants, children, and siblings, so it is more than a “parent selector.”
How does the CSS :has() selector work?
Use this general form:
subject:has(relative-selector-list) {
/* declarations */
}
The subject is the element the complete selector can match. CSS evaluates each selector in the parentheses relative to that subject. If any one of the listed relationships matches, the subject matches. If no combinator begins an argument, the relationship is to a descendant.
In article:has(img, video), for example, the article matches if it has an img descendant or a video descendant. The comma means “or,” not “and.”
How do you select a parent or another related element?
The combinator inside :has() describes the relationship being tested from the subject. The subject before :has() receives the style.
#1 Best Overall
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
| Relationship | Example | What matches |
|---|---|---|
| Descendant | .card:has(img) |
A .card with an image anywhere inside it |
| Direct child | .card:has(> img) |
A .card whose immediate child is an image |
| Immediately following sibling | h2:has(+ p) |
An h2 immediately followed by a paragraph |
| Later sibling | h2:has(~ p) |
An h2 followed later in the same parent by a paragraph |
Test for any descendant
/* Add a border to a card containing an image at any depth. */
.card:has(img) {
border-color: steelblue;
}
Test for a direct child
/* Remove top padding only when the image is a direct child. */
.card:has(> img) {
padding-top: 0;
}
Test for an adjacent sibling
/* Tighten the gap after a heading only when a paragraph follows it immediately. */
h2:has(+ p) {
margin-bottom: 0;
}
Test a form state or multiple alternatives
/* Style a form when it contains an invalid control. */
form:has(:invalid) {
border-color: crimson;
}
/* Match an article containing either media type. */
article:has(img, video) {
/* declarations */
}
What restrictions and specificity should you know?
:has()cannot be nested inside another:has().- Pseudo-elements are not valid in its relative selector list.
- The argument contributes to specificity according to the most specific selector in that list, similarly to
:is(). A highly specific argument can therefore make the full rule harder to override in the cascade. - A comma-separated list inside
:has()is an alternative list: any matching argument is enough.
These rules follow the matching model in the W3C Selectors Level 4 specification and are described in the MDN reference for :has().
Does :has() work in all browsers?
No. The compatibility record consulted on October 3, 2026, lists support beginning with Chrome 105, Edge 105, Safari 15.4, and Firefox 121. Older releases listed before those thresholds do not support it. These are release thresholds, not a guarantee that every visitor uses a supporting browser; check your project’s actual browser-support matrix. See Can I Use’s compatibility table.
Rank #2
Use progressive enhancement for optional styling
Keep useful baseline styling outside a feature query, then add the relational enhancement only when the browser supports the selector:
.card {
border: 1px solid #bbb;
}
@supports selector(:has(*)) {
.card:has(> img) {
border-color: steelblue;
}
}
This lets unsupported browsers retain the baseline border. Do not assume an ordinary comma-separated selector list will keep its valid branch when another selector in that list is unsupported; MDN explains the behavior of unsupported selectors in its selector-list reference.
Rank #3
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
Or skip the browser setup
If you need a screenshot of a page to inspect a layout or state, ScreenshotNeo can capture it through one GET request. The example saves a WebP response; see the API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
- Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each of these steps can be turned off.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status.
- An MCP server provides screenshot tools for AI agents, including Claude, Cursor, and other MCP clients.
- The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
Rank #4
Frequently Asked Questions
Can :has() test more than one relationship?
Yes. Put a comma-separated relative selector list inside the parentheses; the subject matches if any one of its arguments matches.
Is :has() only a parent selector?
No. It can test a descendant, child, or sibling relationship, and styles the element before :has().
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




