The Power of :has() in CSS

Master parent selection, previous sibling targeting, and progressive enhancement with CSS's most transformative pseudo-class

What is the :has() Pseudo-Class?

The :has() CSS pseudo-class represents a fundamental shift in what's possible with CSS. For decades, CSS selectors flowed in one direction--from parent to child--but never the reverse. Developers relied on JavaScript to style parents based on their children or to style elements based on their following siblings.

The :has() selector changes this paradigm entirely, enabling parent and previous-sibling selection directly in CSS. This capability aligns perfectly with modern component-based development in frameworks like Next.js, where performance and clean code are paramount.

By reducing dependency on JavaScript for UI state management, :has() helps build faster, more maintainable websites with better Core Web Vitals scores. Combined with CSS custom properties, modern CSS gives developers unprecedented control without adding JavaScript overhead.

Syntax and Basic Usage

The syntax follows a straightforward pattern where :has() contains one or more relative selectors. The selector before :has() determines which elements to check, while the selectors inside determine what to look for within those elements. For flexible layout patterns, combining :has() with Flexbox creates powerful responsive behaviors.

Basic Syntax Patterns

/* Style parent when it contains a specific child */
.card:has(.featured) {
 border: 2px solid gold;
}

/* Style element when followed by a specific sibling */
.label:has(+ input:checked) {
 color: green;
}

/* Check for multiple conditions */
.article:has(img, video) {
 --media-spacing: 1.5rem;
}
Common :has() Patterns
1/* Parent selection - style card containing an image */2.card:has(img) {3 display: flex;4 flex-direction: column;5}6 7/* Previous sibling - style label when followed by checked input */8label:has(+ input:checked) {9 color: green;10 font-weight: 500;11}12 13/* Combined with :not() - cards without images */14.card:not(:has(img)) {15 padding: 2rem;16 background: #f5f5f5;17}18 19/* Chaining :has() selectors */20.parent:has(.child):has(.another) {21 /* Matches parent containing both .child and .another */22}

Use Case 1: Parent Selection Patterns

Parent selection represents the most anticipated use case for :has(). Before this pseudo-class, styling a parent based on its children required JavaScript--adding event listeners, checking DOM relationships, and toggling classes. With :has(), this becomes pure CSS, reducing JavaScript bundle size and improving performance.

Conditional Component Styling

Modern component libraries and design systems benefit enormously from parent selection. Components can adapt their styling based on their contents without requiring props or wrapper components. This flexibility reduces component proliferation and makes styling more declarative.

For example, a Button component might need different padding when it contains an icon versus text only:

/* Buttons with icons get extra gap */
button:has(.icon) {
 gap: 0.5rem;
 padding-left: 1rem;
}

/* Form groups get error styling when input is invalid */
.form-group:has(input:invalid) {
 border-left: 3px solid red;
 padding-left: 0.75rem;
}

/* Add dropdown indicator to nav items with submenus */
nav li:has(ul) > a::after {
 content: "+";
 margin-left: 0.5rem;
}

This pattern eliminates the need for separate "Button with Icon" components and reduces prop drilling for styling decisions, contributing to cleaner code architecture.

Use Case 2: Previous Sibling Selection

The :has() selector's ability to target previous siblings solves a long-standing CSS limitation. Traditionally, CSS could only style elements based on what followed them, not what preceded them. Developers used workarounds like flexbox order manipulation or JavaScript class toggling. With :has(), styling based on previous siblings becomes native CSS.

Interactive UI Patterns Without JavaScript

Previous sibling selection enables sophisticated interactive patterns that previously required JavaScript. Consider a star rating component where hovering over a star highlights it and all previous stars:

/* Style label when followed by checked checkbox */
label:has(+ input:checked) {
 color: green;
 font-weight: 500;
}

/* Breadcrumb separators - no separator after last item */
.breadcrumb-item:has(~ .current)::after {
 content: "/";
 margin-left: 0.5rem;
}

/* Star rating hover effect */
li:has(+ li:hover) {
 transform: scaleY(1.1);
}

This pattern demonstrates how :has() can replace JavaScript-driven state management for certain UI interactions, reducing bundle size and improving page load performance.

Use Case 3: Form Validation Styling

Form validation styling benefits significantly from :has(). Traditionally, showing validation feedback required JavaScript to detect invalid states and apply classes. With :has(), forms can respond to their invalid inputs directly:

/* Style entire form when any input is invalid */
form:has(input:invalid) {
 border: 1px solid red;
}

/* Show error message when input is invalid */
.form-group:has(input:invalid) .error-message {
 display: block;
}

/* Style label based on input state */
label:has(+ input:placeholder-shown) {
 color: #666;
}

Conditional Form Layouts

Beyond validation, :has() enables conditional form layouts based on user selections. A common pattern involves showing additional fields when certain options are selected:

/* Show shipping fields when 'ship to different address' is checked */
.shipping-section:has(#ship-different:checked) {
 display: block;
}

.shipping-section {
 display: none;
}

This approach reduces form-related JavaScript significantly and ensures validation styles update instantly as users interact with inputs. Combined with CSS custom properties, forms become more dynamic without additional scripting.

Use Case 4: Quantity Queries

The :has() selector introduces true quantity queries to CSS--styling elements based on the number of their children. This capability enables responsive layouts that adapt to content rather than viewport size alone.

Adaptive Card Layouts

Quantity queries excel at creating adaptive card layouts. Instead of media queries that respond to viewport width, these layouts respond to actual content:

/* Style list when it has exactly 3 items */
ul:has(> :nth-child(3):last-child) {
 grid-template-columns: repeat(3, 1fr);
}

/* Different styling for cards based on count */
.cards-grid:has(> :nth-child(1):last-child) {
 /* Single card - full width */
 grid-column: 1 / -1;
}

.cards-grid:has(> :nth-child(2):last-child) {
 /* Two cards - side by side */
 grid-template-columns: 1fr 1fr;
}

Instead of media queries that respond to viewport width, these layouts respond to actual content. This is particularly powerful for dynamic content rendering where the number of items varies based on database queries or user-generated content.

Performance Considerations

Understanding :has() performance is crucial for production applications. The selector has evolved significantly since initial implementations, and modern browsers have optimized it well. However, performance characteristics depend on selector complexity and DOM structure.

Selector Performance Guidelines

/* Good - simple direct child check */
parent:has(.child) { /* fast */ }

/* Good - direct sibling check */
element:has(+ next-sibling) { /* fast */ }

/* Moderate - descendant check */
parent:has(.descendant .deep-child) { /* moderate */ }

/* Expensive - multiple chained :has() */
parent:has(.a):has(.b):has(.c) { /* more expensive */ }

Next.js Performance Integration

For Next.js projects, :has() fits well with the performance-first philosophy:

  • Zero JavaScript bundle impact for :has()-based styles
  • Styles apply immediately on initial paint (no hydration delay)
  • Works with CSS-in-JS solutions and CSS modules
  • Compatible with Next.js static generation and SSR

The key is using :has() for progressive enhancement rather than critical functionality. Users on unsupported browsers still get functional experiences--they simply miss the conditional styling.

Best Practices

Do: Use for Progressive Enhancement

Apply :has() to enhance experiences for modern browsers while maintaining functionality for all users:

/* Progressive enhancement pattern */
.card:has(img) {
 /* Enhanced styles for modern browsers */
}

.card {
 /* Fallback styles that work everywhere */
}

Do: Combine with Modern CSS Features

:has() works excellently with container queries and CSS custom properties:

@container (min-width: 400px) {
 .card:has(img) {
 flex-direction: row;
 }
}

Don't: Nest :has() Selectors

Nesting :has() is invalid CSS. You can chain :has() selectors instead:

/* Invalid - don't do this */
.parent:has(.child:has(.grandchild)) { }

/* Instead, chain :has() */
.parent:has(.child):has(.grandchild) { /* valid */ }

Don't: Use as a Substitute for Proper Semantics

:has() shouldn't replace proper HTML structure. Use it for enhancement, not to compensate for poor markup architecture.

Production-Ready :has() Patterns
1/* Pattern 1: Loading State Cards */2.card:has(.loading-spinner) {3 opacity: 0.7;4 pointer-events: none;5}6 7/* Pattern 2: Responsive Navigation */8nav li:has(ul) > a::after {9 content: "";10 border: 4px solid transparent;11 border-top-color: currentColor;12 margin-left: 0.25rem;13}14 15/* Pattern 3: Accessibility Enhancements */16.form-field:has(input:invalid:not(:focus)) label {17 color: #dc2626;18}19 20/* Pattern 4: Conditional Layouts */21.article:has(img + p) {22 flex-direction: row;23 align-items: flex-start;24}
Key :has() Capabilities

Everything you can achieve with CSS :has()

Parent Selection

Style elements based on their children without JavaScript manipulation

Previous Sibling Targeting

Style elements based on what precedes them in the DOM

Form Validation

Create responsive validation feedback without JavaScript logic

Quantity Queries

Adapt layouts based on the number of child elements

Frequently Asked Questions

Ready to Build Better Web Experiences?

We specialize in modern web development using Next.js, React, and the latest CSS capabilities to create fast, maintainable websites.

Sources

  1. CSS-Tricks: The Power of :has() in CSS - Comprehensive guide with syntax, use cases, and browser support data
  2. Bejamas: Learn CSS :has() selector by examples - Practical examples with CodePen demos
  3. MDN Web Docs: :has() - Official documentation on specificity and constraints