Has Native CSS Parent Selector

Master the :has() pseudo-class for powerful parent and sibling selection in modern CSS. No JavaScript required.

What Is the CSS :has() Pseudo-Class?

CSS has long been limited in its ability to select parent elements based on their children. Developers have relied on JavaScript workarounds, CSS preprocessors, or creative class naming conventions to style parent elements conditionally. The CSS :has() pseudo-class changes this fundamental limitation, bringing true parent selection to native CSS for the first time.

The :has() pseudo-class represents an element if any of the relative selectors passed as arguments match at least one element when anchored against that element. This powerful selector, now supported across all modern browsers, opens new possibilities for building responsive, maintainable stylesheets without JavaScript dependencies.

Unlike traditional CSS selectors that flow downward from parent to child, :has() works bidirectionally--allowing selectors to look upward to parents and backward to preceding siblings. This capability makes :has() the most significant addition to CSS in recent years, enabling advanced CSS architecture patterns that reduce JavaScript dependencies and improve maintainability.

Browser Support and Compatibility

Understanding browser support is crucial for making informed decisions about when to adopt new CSS features. The :has() pseudo-class reached a significant milestone when it became part of Baseline 2023, which indicates that the feature works across the latest devices and browser versions.

Timeline

  • Chromium-based browsers (Chrome, Edge, Opera): Added in version 105 (2022)
  • Safari (WebKit): Added in version 15.4 (2022)
  • Firefox: Added in version 121 (2023)

Progressive Enhancement

When using :has(), it's important to understand how browsers handle unsupported scenarios. If a browser doesn't support :has(), the entire selector block will fail unless :has() is placed within a forgiving selector list like :is() or :where(). This behavior allows progressive enhancement strategies where modern browsers get enhanced styles while older browsers fall back to simpler baseline styling.

For production deployments, ensure your CSS specificity understanding accounts for how :has() inherits specificity from its arguments, preventing unexpected style conflicts.

Syntax and Basic Usage

Understanding :has() Syntax

The :has() pseudo-class accepts a relative selector list as its argument, following this basic syntax pattern: :has(<relative-selector-list>). The selectors inside :has() are relative, meaning they're evaluated starting from the element being selected rather than from the document root.

The syntax can be combined with any CSS selector to create powerful matching conditions:

  • Parent selection: section:has(.featured) selects sections containing a .featured element
  • Sibling selection: h1:has(+ p) selects h1 elements followed immediately by paragraphs
  • Child combinators: article:has(> .highlight) matches articles with direct .highlight children

Important Restrictions

Pseudo-elements are not valid selectors within :has(), and pseudo-elements are also not valid anchors for :has(). Additionally, :has() cannot be nested inside another :has() selector. Understanding these constraints helps you design robust CSS systems that work predictably across your projects.

Basic :has() Syntax Examples
1/* Parent selection - style cards containing images */2.card:has(img) {3 padding-top: 0;4}5 6/* Sibling selection - heading followed by subheading */7h1:has(+ h2) {8 margin-bottom: 0.25rem;9}10 11/* Direct child selection */12.article:has(> .featured) {13 border-left: 4px solid #0066cc;14}15 16/* Multiple conditions */17.card:has(img):has(.promo) {18 background: #f8f9fa;19}

Practical Use Cases for Modern Web Development

Responsive Layout Adjustments

Modern web layouts often need to adapt based on the content they contain rather than just viewport dimensions. The :has() pseudo-class enables content-aware adaptations that respond to actual page content.

/* Cards with images get different styling */
.card:has(img) {
 display: grid;
 grid-template-columns: 1fr 2fr;
}

.card:not(:has(img)) {
 padding: 1.5rem;
}

Form Validation and State Management

Forms represent a perfect use case for :has() because validation states often depend on the state of child input elements.

/* Style entire field group when input is invalid */
.field-group:has(input:invalid) {
 border-color: #dc3545;
}

/* Highlight group when any input has focus */
.field-group:has(input:focus) {
 box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.25);
}

Navigation State Styling

Navigation items can display the active state without JavaScript:

/* Style nav item when it contains active link */
.nav-item:has(.active) {
 background-color: #e9ecef;
}

.nav-item:has(.active) .nav-link {
 font-weight: 600;
}

These use cases demonstrate how :has() complements modern CSS Grid layouts by enabling content-aware responsive behavior without JavaScript dependencies.

Performance Considerations

How :has() Impacts Performance

The :has() pseudo-class introduces new performance considerations because it can cause browsers to re-evaluate styles when child elements change. Unlike traditional selectors that flow in one direction, :has() creates bidirectional dependencies.

Best Practices

  1. Keep selectors simple: Simple parent-child checks like section:has(.child) are extremely fast because browsers optimize these common patterns.

  2. Avoid overly broad element matches: Instead of body:has(div.some-class), use more specific selectors that limit the search scope.

  3. Be cautious with frequently changing DOM: Test :has() selectors under animation, drag operations, or other interactive contexts.

  4. Specificity awareness: The selector specificity of :has() follows the same rules as :is() and :not(), taking on the specificity of the most specific selector in its arguments.

For complex applications built with our web development services, performance testing should verify that :has() selectors perform well across different device categories and browser versions.

Complete Code Examples

Parent Selection Patterns

/* Style cards that contain images differently */
.card:has(img) {
 padding-top: 0;
}

/* Highlight forms with invalid fields */
.form-group:has(input:invalid) {
 border-color: #dc3545;
}

/* Style sections containing featured content */
section:has(.featured) {
 background: linear-gradient(to right, #f8f9fa, #fff);
}

/* Adjust navigation items with dropdown menus */
.nav-item:has(.dropdown) > .nav-link {
 padding-right: 1.5rem;
}

Sibling Selection Patterns

/* Reduce spacing after headings followed by subheadings */
h1:has(+ h2) {
 margin-bottom: 0.25rem;
}

/* Style list items followed by disabled items */
li:has(+ li[disabled]) {
 opacity: 0.7;
}

/* Highlight form fields followed by error messages */
.input-group:has(+ .error-message) label {
 color: #dc3545;
}

Complex Condition Patterns

/* Style cards that have images AND are featured */
.card:has(img):has(.featured) {
 border: 2px solid #0066cc;
}

/* Apply styles when ANY condition is met */
.card:has(img),
.card:has(.promo) {
 background: #f8f9fa;
}

/* Select based on child count */
.list:has(> li:nth-child(5)) {
 max-height: 300px;
 overflow-y: auto;
}
Complete :has() Examples
1/* Parent selection - style cards containing images */2.card:has(img) {3 padding-top: 0;4}5 6/* Sibling selection - heading followed by subheading */7h1:has(+ h2) {8 margin-bottom: 0.25rem;9}10 11/* Direct child selection */12.article:has(> .featured) {13 border-left: 4px solid #0066cc;14}15 16/* Multiple conditions */17.card:has(img):has(.promo) {18 background: #f8f9fa;19}20 21/* Form validation styling */22.field-group:has(input:invalid) {23 border-color: #dc3545;24}25 26/* Navigation active state */27.nav-item:has(.active) {28 background-color: #e9ecef;29}

Implementation Guidelines

Getting Started with :has()

When introducing :has() to a project, start with simple parent selection patterns that have clear, obvious benefits. These low-risk initial uses help teams build familiarity with the selector while delivering immediate value.

  1. Audit your codebase for patterns that currently rely on JavaScript for parent or sibling-based styling
  2. Start simple with obvious use cases like card styling based on image presence
  3. Establish team conventions for :has() usage to ensure consistency
  4. Test thoroughly across browsers and performance contexts

Progressive Enhancement Strategy

Design your CSS architecture to gracefully degrade in browsers that don't support :has(). For critical functionality, ensure that pages remain usable without :has() styles.

/* Using :is() for graceful degradation */
.card:is(:has(img)) {
 /* Styles for supporting browsers */
}

Wrapping :has() in :is() prevents the entire selector from failing in non-supporting browsers. Use this pattern judiciously when progressive enhancement is critical.

Testing Considerations

  • Test :has() selectors thoroughly across supported browsers
  • Include scenarios where :has() conditions toggle on and off
  • Use browser developer tools to monitor style recalculation times
  • Test accessibility with screen readers and keyboard navigation

By following these implementation guidelines alongside our web development services, teams can successfully integrate :has() into their CSS architecture while maintaining cross-browser compatibility and optimal performance.

Frequently Asked Questions

What browsers support CSS :has()?

All major browsers support :has() as of late 2023. Chrome, Edge, Firefox, and Safari all have full support. The feature is part of Baseline 2023, indicating stable support across modern browser versions.

Can :has() be nested inside another :has()?

No, :has() cannot be nested inside another :has() selector. This restriction prevents complex recursive queries that could impact performance.

How does :has() affect CSS specificity?

The :has() pseudo-class takes on the specificity of the most specific selector in its arguments, similar to :is() and :not(). For example, div:has(.class) has the specificity of .class.

Can pseudo-elements be used inside :has()?

No, pseudo-elements are not valid selectors within :has(). This restriction exists because many pseudo-elements exist conditionally based on styling, which could introduce cyclic dependencies.

Is :has() performant for large DOM trees?

Simple :has() selectors like element:has(.class) are well-optimized in modern browsers. However, complex selectors or those applied to frequently-changing DOM elements may require performance testing.

Conclusion

The CSS :has() pseudo-class represents a fundamental advancement in CSS capabilities, finally providing native parent and sibling selection to web developers. With universal browser support achieved in late 2023, :has() is ready for production use in modern web applications.

For Next.js projects specifically, :has() enables sophisticated component styling without client-side JavaScript dependencies. Components can adapt their presentation based on their content, forms can style entire field groups based on input states, and layouts can respond to their contained elements--all through declarative CSS.

As you incorporate :has() into your projects, start with clear, simple use cases that deliver immediate value. Build team familiarity with the selector before expanding to more complex patterns. With thoughtful implementation, :has() can significantly simplify your CSS architecture while creating more adaptive, responsive user experiences.

Ready to upgrade your web development capabilities? Our web development services help teams leverage modern CSS features like :has() to build better, more maintainable websites.

Ready to Modernize Your Web Development?

Our team builds custom web applications using the latest technologies and best practices. Let us help you create performant, maintainable websites.