What Can You Actually Do With CSS has?
What problem does the CSS has selector solve?
Styling a parent based on what is inside it. For most of CSS history you could only style downward, so a card that contains an image needed a modifier class added by a template or a script. The has pseudo-class removes that detour. MDN describes it as representing an element if any of the relative selectors passed as an argument match at least one element when anchored against that element.
In plainer terms, you can now write a rule that means: this card, but only if it contains an image. Or this form field, but only if its input is invalid. The condition lives in the stylesheet instead of in your markup.
That sounds small and it changes a surprising amount of day to day front end work. Here is what it does, what the rules are, and where we actually reach for it.
What does the has selector actually do?
It tests a relationship and styles the element you started from. The W3C Selectors Level 4 specification defines it as matching an element if there exists an element matching the relative selectors inside, when evaluated with the outer element as the anchor.
The word anchor is the important one. Everything inside the parentheses is evaluated relative to the element on the outside, and the element on the outside is what gets styled. So a rule written as card colon has open paren img close paren styles the card, not the image.
It also works sideways, not just downward. Because the inner selector can use sibling combinators, you can style an element based on what follows it, which was previously impossible in CSS. MDN notes this explicitly: it allows selecting a parent element or a previous sibling element based on what follows it.
Is the has selector safe to use in production?
Yes, on the modern browser set. MDN lists it as Baseline widely available since December 2023. Under the Baseline definitions, widely available means a feature has had a consistent history of support in each of the Baseline browsers for at least 2.5 years.
Those Baseline browsers are a specific list: Safari on iOS and macOS, Chrome on Android and desktop, Edge on desktop, and Firefox on Android and desktop. If your analytics show meaningful traffic outside that set, the calculus changes, but for a B2B marketing site it rarely does.
It has been shipping for a while in practice. Chrome's own announcement post put it as due for release in Chromium 105, which is why most teams first met it in 2022. The question in 2026 is no longer whether you can use it. Our notes on reading Baseline for web features cover how to make that call for anything else.
What can you build with it that you could not before?
Layouts that respond to their own content. Chrome's post lists the cases that still come up most: detecting when a card contains an image to trigger a two column layout without modifier classes, and floating a figure when it has no caption while showing it full width when it does.
Form state is the other big one. Chrome's examples cover styling labels and inputs based on invalid, valid, and focus states to signal field status. A label that turns red because its input is invalid used to need JavaScript or a very particular DOM order. Now it is one rule.
The fourth case from that post is navigation. Responding to an aria-expanded attribute to update menu styles means your CSS reads the same accessibility state your assistive technology does, rather than a parallel class that can drift out of sync. That is a genuine correctness win, not just convenience. The state cues worth showing are a design question as much as a technical one.
How does specificity work with the has selector?
It takes the specificity of its strongest argument. MDN states that has takes on the specificity of the most specific selector in its arguments, the same way is and not do. The pseudo-class itself adds nothing.
This catches people out in both directions. Putting an ID inside the parentheses makes the whole rule as specific as an ID selector, which is almost never what you intended. Putting a bare element name inside keeps the rule weak, which usually is.
The practical habit is to keep the inside of the parentheses as simple as the condition allows. You are describing a relationship, not building weight. If you find yourself needing more weight to win a cascade fight, the fight is the problem. Our notes on cascade layers cover the better tool for that.
What are the rules you cannot break?
Three, and they are all about avoiding circular logic. MDN is clear that the has pseudo-class cannot be nested within another has. You cannot ask whether an element has a child that itself has something.
Pseudo-elements are out on both sides. MDN says pseudo-elements are not valid selectors within has, and are not valid anchors for has. The reason given is sound: many pseudo-elements exist conditionally based on the styling of their ancestors, so allowing them to be queried can introduce cyclic querying.
The third rule is about failure. MDN notes that has is non-forgiving, so an unsupported or invalid selector inside it invalidates the rule. That is a deliberate design choice and it is the thing to plan around rather than fight.
Does the has selector hurt performance?
Less than its reputation suggests, and the restrictions exist for that reason. Chrome's announcement frames the limitations as being due to performance hits: no nested has, no pseudo-elements inside, and restrictions on use within certain pseudo-classes. The browser engineers removed the expensive cases rather than leaving them available.
We would still not put a very broad has rule at the top of a large document. A selector that asks whether the body has any element matching something is asking the engine to evaluate a lot. Scoping the rule to a component class first keeps the work small.
What we have not seen is the catastrophic cost people expected when it shipped. On a typical marketing site with component-scoped rules, this is not where your performance problem is. Render blocking resources and oversized scripts are almost always the real cost.
How do you write a safe fallback?
Wrap it, or accept the all or nothing behaviour. MDN explains that if has itself is not supported by a browser, the entire selector block will fail unless has sits inside a forgiving selector list such as is or where. That wrapping trick is the standard defence.
The simpler approach is to design so that the has rule is an enhancement rather than a requirement. If the card without the extra rule still reads correctly, just less elegantly, you do not need a fallback at all. That is ordinary progressive enhancement applied to a selector.
Where you do need a guarantee, a feature query is the explicit option. Asking whether the browser supports a selector keeps the fallback and the enhancement visibly separate in the stylesheet, which is easier for the next person to read than a clever wrapper. Our notes on progressive enhancement cover the same instinct more broadly.
Where does the has selector replace JavaScript?
Anywhere a script was only there to add a class. A surprising amount of front end JavaScript exists to observe the DOM and set a modifier, and most of that work is now a stylesheet concern. Removing it removes a race condition and a file.
The clearest wins are content-conditional layout, form state styling, and reacting to attributes that are already there for accessibility reasons. In each case the DOM already knows the answer, and the only reason a script was involved was that CSS could not ask the question.
It does not replace behaviour. If something needs to happen, not just look different, you still need code. The test we use is simple: if the only outcome was a class name, the has selector can probably do it instead.
How do we use it in our own builds?
Sparingly and visibly. We reach for it when the alternative is a modifier class that a CMS editor would have to remember to set. Editors forget, and a rule that reads the content directly cannot be forgotten.
We avoid it as a way to patch over a messy component API. If a card needs four different has rules to cover its variants, the component wants a real variant prop, not a stylesheet that reverse engineers its content. The selector is good at describing content, not at hiding design debt.
We also keep the rules scoped to a component class rather than leaning on document-wide conditions, partly for performance and mostly for readability. A rule that says what it applies to is easier to delete in two years, which is usually the real test. Our notes on container queries cover the other half of writing components that adapt themselves.
What comes after the has selector?
More CSS that can ask questions. The direction of travel is clear: container queries let components respond to their own space, the has selector lets them respond to their own content, and style queries extend that further. Each one moves logic out of templates and scripts and into the layer that owns presentation.
Our bet is that the teams who adopt these deliberately end up with smaller components and less glue code, and the ones who adopt them enthusiastically end up with stylesheets nobody can reason about. The feature is good. The restraint is the skill.
If you are looking at a front end full of modifier classes and wondering how much of it could go away, we are happy to take a look with you. Find us at phoenix.studio.
Want a site that performs like this?
Tell us about your project. We will come back with a clear next step, no pressure.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Have a project like this?
Tell us where you want to go. We'll tell you how we'd get you there.