If you've ever wished you could style a container based on what's inside it, your wish just came true. The CSS :has() pseudo-class has moved from experimental territory to real browser support, opening up styling possibilities that developers have requested for years.
The feature landed first in Safari, with other browsers expected to follow soon. Here's what you need to know about this addition to your styling toolkit.
How :has() Actually Works
At its core, :has() is a relational selector. It lets you target an element if a specific descendant exists or matches a condition. Think of it as asking CSS: "Does this element contain X? If yes, apply these styles."
The syntax is straightforward. You write the parent selector, add :has(), then include the child selector you want to check for.
One common scenario: making a link block-level only when it contains an image. That prevents awkward inline spacing issues when wrapping images in anchor tags. Another example handles figures with multiline captions by adjusting height constraints only when necessary.
Perhaps most practically: adding spacing to articles that lack headings. Previously, you needed JavaScript or convoluted class naming to handle that. Now one CSS rule handles it.
Why This Changes Things
CSS has always worked top-down. You select an element, then style it. Going the other direction—styling based on children—required workarounds. Developers stacked classes, wrote JavaScript observers, or simply accepted design limitations.
The :has() selector inverts that relationship. You can now write CSS that responds to content structure without touching your HTML or adding JavaScript. That simplifies maintenance and keeps styling decisions where they belong: in the stylesheet.
Browser support remains the current constraint. Safari shipped it first, which means early adopters can use it today if their audience data supports it. Since :has() is part of the official CSS Selectors Level 4 specification, cross-browser consistency should follow.
A Practical Scenario
Say you're building a responsive layout for a content site. You want list items that contain images to display in a grid, while text-only items stay in a vertical list. Without :has(), you'd manually add a class like "has-image" to each item when creating content. That creates busywork and leaves room for human error.
With :has(), you write one rule: `li:has(img) { display: grid; }`. Your HTML stays clean. The styling system handles the logic. Add an image to any list item, and the layout adapts automatically. Remove the image, and it reverts to the default styling.
Who Should Care
Component library authors:** Build self-adapting UI components that respond to content without requiring specific class applications.
CMS theme developers:** Create templates that gracefully handle varying content types without manual intervention or template conditionals.
Plugin and widget developers:** Hide containers until dynamic content loads (advertisement slots, injected widgets) using pure CSS instead of JavaScript callbacks.
Performance-conscious developers:** Replace JavaScript MutationObserver patterns with CSS-only solutions that run in the browser's style calculation engine.
Accessibility-focused teams:** Add contextual spacing and layout adjustments based on content structure, improving readability across diverse content types.
Key takeaways
- The :has() pseudo-class lets you style parents based on their children, reversing CSS's traditional direction
- Safari has shipped support; other major browsers are working on implementations
- Use cases include conditional styling for links with images, figures with captions, and content containers with or without headings
- This reduces reliance on JavaScript workarounds and manual class assignments
Frequently asked questions
What browsers currently support the CSS :has() selector?
Safari was the first to ship support for :has(). Firefox and Chrome have implementations in development. Check Can I Use for current status.
Does :has() work with all CSS selectors?
:has() accepts most CSS selectors including class, ID, attribute, pseudo-class, and descendant selectors. Complex combinators are also supported.
Is :has() part of official CSS standards?
Yes. It's been added to the CSS Selectors Level 4 specification, which means browser implementations should be consistent across vendors.
How does :has() affect page performance?
For most typical uses, performance impact is negligible. Very complex selectors or large numbers of :has() rules on dynamic pages could increase calculation time, but this is unlikely to cause noticeable issues.
Can I use :has() to style the child element instead of the parent?
No. The selector always targets the element before :has(), checking for descendants. To style the child, you'd use standard descendant selectors or combinators.