Testing

CSS :has(): Browser Support, Features, Limitations

CSS :has() works in Chrome 105+, Edge 105+, Firefox 121+, Safari 15.4+, Opera 91+, and Samsung Internet 20+. Learn :has() browser support and quirks.

Author

Prince Dewani

May 1, 2026

CSS :has() is a relational pseudo-class in CSS Selectors Level 4 that selects an element when any selector inside its parens matches a child or sibling. It works in Chrome 105+, Edge 105+, Firefox 121+, Safari 15.4 on macOS and iOS, Opera 91+, Samsung Internet 20+, and Android Browser 147+, while Internet Explorer never added support.

This guide covers what :has() is, the browsers that support it, the key features, how to check support, and the known issues.

What is CSS :has()?

CSS :has() is a relational pseudo-class defined in the CSS Selectors Level 4 specification from the W3C. It accepts a relative selector list as its argument and matches the anchor element when any of those selectors find at least one matching child or sibling. It is the first true parent selector in CSS.

Which browsers does CSS :has() support?

CSS :has() works in every modern desktop and mobile browser. Chrome 105, Edge 105, Firefox 121, Safari 15.4 on macOS and iOS, Opera 91, and Samsung Internet 20 all enable it by default, while Internet Explorer never received support.

Loading browser compatibility data...

CSS :has() compatibility in Chrome

Chrome supports CSS :has() by default from Chrome 105 on Windows, macOS, Linux, ChromeOS, and Android. Chrome 101 to 104 had :has() disabled by default behind the Experimental Web Platform features flag in chrome://flags, and Chrome 4 to 100 did not support it at all.

CSS :has() compatibility in Edge

Microsoft Edge supports CSS :has() by default from Edge 105 on Windows, macOS, and Linux. Edge 12 to 104 did not support :has() at all. Legacy EdgeHTML versions never received the relational pseudo-class, so users on stale Edge builds should switch to a current Chromium-based Edge release.

CSS :has() compatibility in Firefox

Firefox supports CSS :has() by default from Firefox 121 on Windows, macOS, Linux, and Android. Firefox 103 to 120 had :has() disabled by default behind the layout.css.has-selector.enabled preference in about:config, and Firefox 2 to 102 did not support it. Firefox for Android picks up support from Firefox 121.

CSS :has() compatibility in Safari

Safari supports CSS :has() by default from Safari 15.4 on macOS and from iOS 15.4 on iPhone and iPad. Safari 3.1 to 15.3 on macOS and Safari on iOS 3.2 to 15.3 do not support :has(). The WebKit team shipped the first production-quality :has() implementation, ahead of Chromium and Firefox.

CSS :has() compatibility in Opera

Opera supports CSS :has() by default from Opera 91 on desktop and from Opera Mobile 80 on Android. Opera 9 to 90 did not support :has(). Opera follows the Chromium release cadence, so it inherits the same Blink :has() implementation that Chrome uses.

CSS :has() compatibility in Samsung Internet

Samsung Internet supports CSS :has() by default from Samsung Internet 20 on Galaxy phones and tablets. Earlier Samsung Internet builds, from version 4 through 19.0, did not support :has(). The browser shares the underlying Chromium engine with Chrome on Android.

CSS :has() compatibility in Android Browser

Chrome for Android supports CSS :has() by default from Chrome 105, and the modern Android Browser ships :has() from version 147 onward. WebView on Android 5.0 and later inherits the Chromium engine, so apps that embed WebView pick up :has() once the system WebView updates past Chromium 105.

CSS :has() compatibility in Internet Explorer

Internet Explorer 5.5 through 11 do not support CSS :has() in any version. The Trident engine never received the relational pseudo-class. Users on Windows who need :has() should switch to Microsoft Edge 105 or later or to Chrome 105 or later.

Note

Note: CSS :has() breaks across browsers when shipped without a fallback. Test it on real browsers and OS with TestMu AI. Try TestMu AI free!

What are the key features of CSS :has()?

CSS :has() opens up DOM-aware styling that previously needed JavaScript. The selector keeps a small surface area but unlocks several patterns CSS could never express on its own.

  • First true parent selector: :has() lets a parent style itself based on its children, which CSS has lacked since the language shipped. The rule figure:has(figcaption) styles only figures that ship a caption.
  • Relative selector list as input: :has() accepts any combinator after the anchor: descendant, child (>), next sibling (+), or general sibling (~). For example, h1:has(+ p) styles an h1 only when a paragraph follows it directly.
  • Logical AND and OR: Multiple :has() chains form an AND. A comma-separated list inside one :has() forms an OR. The rule body:has(video):has(audio) requires both, while body:has(video, audio) matches either.
  • Form state styling without JS: Pair :has() with :checked, :invalid, or :focus-within to style ancestors based on the state of inputs nested inside them. The rule form:has(:invalid) highlights an entire form whenever any field is invalid.
  • Predictable specificity: :has() takes the highest specificity of any selector inside its parens. The pseudo-class itself adds nothing; the inner selectors decide weight, which keeps cascade behavior easy to reason about.
...

How do you check if a browser supports CSS :has()?

The cleanest check is the @supports at-rule with a selector() condition. Wrap the :has() rules so the browser only applies them when the engine recognizes the selector. Pair it with @supports not to ship a fallback for older browsers.

/* Feature-detect :has() before applying it. The block runs only when the
   browser parses :has(); older browsers skip it and use the fallback below. */
@supports selector(:has(*)) {
    .card:has(img) {
        padding: 1rem;
        border: 1px solid #ddd;
    }

    form:has(:invalid) {
        outline: 2px solid red;
    }
}

/* Fallback for browsers without :has() support. */
@supports not selector(:has(*)) {
    .card {
        padding: 1rem;
    }
}

For runtime checks, the CSS.supports JavaScript API parses the same selector string. Paste this snippet into the DevTools console of any browser to see whether the engine recognizes :has().

// Run in any browser DevTools console to check :has() support at runtime.
// CSS.supports parses the selector string and returns true when the engine
// recognizes it.

const supportsHas = CSS.supports('selector(:has(*))');
console.log(`:has() supported in this browser: ${supportsHas}`);

// Logs true on Chrome 105+, Edge 105+, Firefox 121+, Safari 15.4+,
// Opera 91+, and Samsung Internet 20+. Logs false everywhere else.

If CSS.supports returns false, the browser silently ignores any rule that contains :has(). Use the @supports not block above to set a baseline style for those users instead of leaving the layout broken.

What are the known issues with CSS :has()?

CSS :has() is now Baseline 2023, but the way browsers parse and resolve it still has rough edges that hit production sites. Plan around these before you replace JavaScript-driven class toggling with :has().

  • Forgiving relative list, unforgiving rule: :has() takes a forgiving relative selector list, so an unknown selector inside it does not invalidate the whole rule. But a :has() rule itself fails in browsers that do not parse :has(), which can invalidate adjacent rules in the same stylesheet. Always wrap :has() rules in @supports.
  • No nesting :has() inside :has(): The spec forbids the pattern :has(:has(...)), and every browser rejects it. Use a comma-separated list or a sibling combinator inside the same :has() instead.
  • Pseudo-element restrictions: :has() cannot wrap ::before, ::after, or other pseudo-elements as its argument. The matcher operates on real DOM nodes, not on generated content.
  • Performance on broad anchors: Anchoring :has() to body, :root, or * forces the engine to re-evaluate on every DOM mutation in the entire document. Anchor :has() to the smallest container that owns the children you actually need to inspect.
  • Long-tail Firefox traffic: Firefox 103 to 120 ship :has() behind the layout.css.has-selector.enabled preference, so a slice of long-tail Firefox users still cannot use :has() without flipping the flag. Server-side analytics on the user agent string should still expect Firefox traffic on 120 and below.

In my experience, the silent failure mode that bites teams the most is forgetting the @supports wrapper: a :has() rule outside an @supports block can break the cascade on Internet Explorer and on stale Android WebView builds. Always feature-detect before shipping :has() to a multi-browser audience.

...

Citations

All CSS :has() version numbers and platform notes in this guide come from these primary sources:

Author

Prince Dewani is a Community Contributor at TestMu AI, where he manages content strategies around software testing, QA, and test automation. He is certified in Selenium, Cypress, Playwright, Appium, Automation Testing, and KaneAI. Prince has also presented academic research at the international conference PBCON-01. He further specializes in on-page SEO, bridging marketing with core testing technologies. On LinkedIn, he is followed by 4,300+ QA engineers, developers, DevOps experts, tech leaders, and AI-focused practitioners in the global testing community.

Open in ChatGPT Icon

Open in ChatGPT

Open in Claude Icon

Open in Claude

Open in Perplexity Icon

Open in Perplexity

Open in Grok Icon

Open in Grok

Open in Gemini AI Icon

Open in Gemini AI

Copied to Clipboard!
...

3000+ Browsers. One Platform.

See exactly how your site performs everywhere.

Try it free
...

Write Tests in Plain English with KaneAI

Create, debug, and evolve tests using natural language.

Try for free

Frequently asked questions

Did you find this page helpful?

More Related Hubs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests