Stockio

CSS :has() Selector: Parent Selectors and Conditional UI

Use CSS :has() to style parents from child states, adjust optional-content layouts, control specificity, and ship readable fallbacks without extra classes.

By Jonah Weiss

Design Systems Lead ·

On this page

A card with an image needs different spacing from a text-only card. A field wrapper should change when its input receives focus. These are relationships in the document, but older CSS approaches often required JavaScript to add a second class that repeated information already present in the markup.

Start with a relationship worth expressing

The :has() pseudo-class selects an element when a relative selector matches from that element. Think of it as a relationship test, not simply a parent selector: it can examine children, descendants, or following siblings depending on the selector you write.

For a card that optionally contains media, use the Flexbox Playground to test how media and text share a row. Then use :has() to activate that arrangement only when the media element exists. The playground establishes the geometry; the selector expresses the condition that makes that geometry useful.

Choose a condition that reflects real structure. Checking for a known media class is usually clearer than searching for any image anywhere inside a large component. An icon inside a button should not accidentally trigger the same layout as a primary product photograph.

Match optional children precisely

Start with a stacked baseline and use a direct-child relationship to keep the enhancement local. This is especially valuable when cards can contain nested previews or rich descriptions that include their own images.

.result {
display: flex;
flex-direction: column;
gap: 1rem;
}

.result__media {
inline-size: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
}

@media (min-width: 42rem) {
.result:has(> .result__media) {
  flex-direction: row;
  align-items: start;
}

.result:has(> .result__media) > .result__media {
  inline-size: 10rem;
  flex: none;
}
}

.result__body {
min-inline-size: 0;
}

The direct-child combinator ensures that a deeply nested image does not qualify. Keep a meaningful class on the media element even if you could select a bare img; classes describe the component contract more clearly than incidental tag choices.

The MDN :has() reference explains relative selector syntax and limitations. In particular, you cannot nest :has() inside another :has(). Simplify the relationship or introduce a meaningful component class when a selector begins resembling a structural query language.

Express state without duplicating state

A field wrapper can use :has(input:focus-visible) to add an outline around the group. It can use :has(input:checked) to emphasize a selected option. These are derived states: the input already owns the state, and the parent’s presentation follows it.

Do not replace native labels, input relationships, or submission behavior with styling. A wrapper that looks selected still needs a real control inside it. For broader interaction requirements, consult the existing accessible interfaces guide.

Validation needs special care. :has(input:invalid) can match required fields before a person has typed anything. If immediate error styling is inappropriate, consider :user-invalid where your browser policy supports it, or an explicit submitted-state class maintained by your form logic. The selector should follow the product’s validation timing rather than dictate it.

A useful review question is whether the same fact is now stored twice. If JavaScript toggles a wrapper class solely because a child checkbox is checked, :has() may remove synchronization work. If the class represents a server-side business status, keep it: document structure is not a substitute for application data.

Keep specificity predictable

The specificity of :has() comes from the most specific selector in its argument list. A list that includes an ID can therefore make a rule unexpectedly difficult to override even when the ID was not the branch that matched.

Use low-specificity selectors and avoid mixing unrelated cases. The expression .result:has(:where(> .result__media)) is not valid because a relative selector cannot be placed that way inside :where(). A valid alternative is .result:where(:has(> .result__media)), which keeps the condition’s contribution at zero while preserving the class specificity.

This distinction matters in shared stylesheets. An easy-to-read condition can still become a cascade problem if its arguments accumulate IDs, state classes, and deep structural paths. Review both what a selector matches and how strongly it competes.

Use MDN’s specificity guide when combining these pseudo-classes. Keep override rules near the component or in a deliberate cascade layer rather than adding repeated classes to win a specificity contest.

Scope queries and preserve the baseline

Avoid starting every condition at body or the universal selector. Broad subjects can require the browser to reconsider a large scope when descendants change. A bounded subject such as .result or .choice communicates intent and usually offers a more manageable matching problem.

Do not promise that every :has() selector is slow or that replacing JavaScript automatically improves performance. The actual cost depends on selector shape, tree size, and how frequently relevant content changes. Measure a real interaction if a dynamic page becomes sluggish.

You can isolate an enhancement with @supports selector(:has(*)). Keep the stacked card outside that block so unsupported browsers still show all content. Check Can I Use support data against the environments you ship to, including embedded browsers.

Test negative cases as carefully as positive ones: no media, media nested inside text, two optional media elements, disabled controls, and programmatically changed input states. The selector must stop matching as reliably as it starts matching.

Finally, keep component examples in your development documentation honest about ownership. CSS should infer presentation from existing structure and native state. It should not become an obscure place where business rules are hidden because a selector happened to make the screenshot look correct.

Distinguish alternatives from combined requirements

A comma-separated argument list inside the pseudo-class expresses alternatives: a card can match because it contains media or because it contains a badge. Chaining two separate relational pseudo-classes instead requires both conditions. This difference matters when optional content controls layout. Write down whether the design requires either feature or both before translating it into a selector.

Following-sibling relationships offer another useful pattern. A heading can be styled when a paragraph immediately follows it, without changing every heading in the component. Keep that relationship local and verify what happens when an editor inserts an intervening element. Structural selectors express the actual DOM, not the visual arrangement created by flex ordering or grid placement.

Create a small fixture with four cards: neither optional element, media only, badge only, and both. Toggle those elements dynamically and inspect the computed styles rather than checking just the initial screenshot. This catches mistaken alternatives and conditions that are broader than intended. Also verify that removing content restores the baseline without leaving a JavaScript class behind.

If explaining the condition requires a long paragraph, simplify the component contract. A clear explicit class is preferable to a clever selector whose behavior future maintainers cannot predict.

Key takeaways

  • Use :has() for local structural and native-state relationships.
  • Prefer direct children when nested content must not trigger a condition.
  • Control specificity with deliberate argument choices and :where().
  • Keep unsupported-browser baselines readable and complete.
  • Test both matching and nonmatching cases before removing state classes.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in HTML & CSS
HTML & CSS

10 Best Online Code Playgrounds Like CodePen

Compare ten browser-based places to write, test, and share code, from quick HTML experiments to full-stack demos, with practical advice for choosing the right workspace.

Includes: Flexbox playgroundJonah WeissJonah Weiss
HTML & CSS

Position Sticky Not Working? How to Fix CSS Sticky

Debug sticky positioning by tracing scroll containers, inset values, ancestor overflow, and layout stretching, with a reliable sidebar example and inspection checklist.

Includes: Flexbox playgroundJonah WeissJonah Weiss
HTML & CSS

Flexbox Overflow Fix: min-width, flex-basis and Shrink

Debug overflowing flex layouts by understanding automatic minimum sizes, flex-basis, shrinking, and text wrapping, with focused fixes for common component failures.

Includes: Flexbox playgroundJonah WeissJonah Weiss
Motion & Animation

10 Best JavaScript and CSS Animation Libraries

Compare ten web animation libraries for interface feedback, scrolling effects, and expressive motion. Find a practical match for your coding comfort, project size, and performance needs.

Includes: Flexbox playgroundAiko TanakaAiko Tanaka
UI Design

Search Bar and Filter UI Design for Catalogs

Create search and filter interfaces with clear scope, predictable application rules, useful result feedback and responsive layouts for catalogs and dashboards.

Includes: Flexbox playgroundMaya LindqvistMaya Lindqvist
JavaScript & Frameworks

Interactive Documentation: Why Live Code Demos Win

Design fast, accessible documentation demos that start with readable static examples, add focused controls, and measure whether readers complete real tasks.

Includes: Flexbox playgroundDaniel KimDaniel Kim