The CSS :has() Selector

A Complete Guide to Mastering Parent Selection, Sibling Targeting, and State-Based Styling in Modern CSS

What is the CSS :has() Selector?

The CSS :has() selector is a functional pseudo-class that represents an element if any of the relative selectors passed as arguments match at least one element. Often called a "parent selector" or "relational selector," :has() allows styling based on descendants or siblings--something developers have wanted in CSS for over a decade.

Before :has(), styling a card differently based on its contents required JavaScript. Now you can write declarative CSS that responds to your HTML structure without any script overhead. This powerful selector finally brings the ability to style parent elements based on their children, a capability that has been one of the most requested CSS features for years.

Understanding how :has() works is essential for modern web development, as it enables sophisticated styling patterns that were previously impossible with CSS alone.

Browser Support Status

100%

Modern Browser Support

2023

Baseline Year

4+

Major Browsers

Syntax and Basic Usage

Simple :has() Syntax

The :has() pseudo-class takes a relative selector list as its argument. Unlike other pseudo-classes that don't take arguments, :has() wraps a selector to check for matching descendants or siblings.

/* Select card when it contains an image */
.card:has(img) {
 display: flex;
 flex-direction: row;
}

/* Select section when it has a featured child */
section:has(.featured) {
 border: 2px solid blue;
}

Relative Selectors Explained

Relative selectors start with a combinator (>, +, ~) rather than an absolute element name. The :has() pseudo-class uses these to look forward in the DOM hierarchy:

  • Descendant combinator (space): article:has(p) - article contains any paragraph
  • Child combinator (>): nav:has(> li) - nav directly contains list items
  • Adjacent sibling (+): h1:has(+ p) - paragraph immediately follows h1
  • General sibling (~): div:has(~ div) - any div follows another div

This approach allows for incredibly flexible styling patterns that were previously impossible with CSS alone. Developers can now create adaptive layouts that respond to their content structure without any JavaScript intervention, making websites more responsive and maintainable.

To fully leverage :has(), it's helpful to understand how CSS classes and selectors work together to target specific elements in your markup.

Use Case 1: Parent Selection

Selecting Elements That Contain Specific Children

This is the most common use case for :has(). Style a parent element when it contains a specific child, enabling adaptive layouts that respond to content.

/* Cards with images get horizontal layout */
.card:has(img) {
 display: flex;
 flex-direction: row;
}

/* Cards without images get different styling */
.card:not(:has(img)) {
 padding: 2rem;
 text-align: center;
}

/* Section with featured content gets special border */
section:has(.featured) {
 border: 2px solid #3b82f6;
 background: #f0f9ff;
}

Direct vs. Any Descendant Selection

Use the child combinator (>) for direct children only, or the space combinator for any descendant:

/* Any descendant image */
.article:has(img) { /* ... */ }

/* Only direct child image */
.article:has(> img) { /* ... */ }

This distinction is crucial for creating precise styling rules. When building responsive web applications, using the appropriate combinator ensures your styles match exactly what you intend.

Combining :has() with layout techniques like CSS Grid and Flexbox creates powerful adaptive designs that respond to content automatically.

Adding Indicators to Parent-Based Conditions

A common pattern: adding visual indicators to elements that have certain children. For example, dropdown arrows on navigation items with submenus:

/* Add dropdown arrow to nav items with submenus */
nav li:has(ul) > a::after {
 content: "▼";
 margin-left: 8px;
 font-size: 0.6em;
}

This CSS automatically adds arrows to any nav link that has a nested <ul>--no JavaScript required. This technique is particularly useful for building accessible navigation menus in modern web interfaces.

Use Case 2: Previous Sibling Selection

The Game-Changing Sibling Pattern

Before :has(), CSS had no way to select previous siblings. The adjacent sibling combinator (+) and general sibling combinator (~) only look forward. With :has(), you can now style an element based on what comes before it.

Immediate Previous Sibling

/* Style H1 when immediately followed by H2 */
h1:has(+ h2) {
 margin-bottom: 0.25rem;
 color: #374151;
}

/* Style label when immediately followed by checked checkbox */
label:has(+ input:checked) {
 color: #059669;
 font-weight: 600;
}

Any Previous Sibling

Using the general sibling combinator (~) inside :has() to match any previous sibling:

/* Add separator before current breadcrumb item */
.breadcrumb-item:has(~ .current)::after {
 content: "/";
 margin: 0 0.5rem;
 color: #9ca3af;
}

This capability opens up entirely new possibilities for creating sophisticated user interfaces that respond to their own structure.

Previous sibling selection is particularly powerful when combined with CSS animation techniques for creating complex interactive effects without JavaScript.

Use Case 3: State-Based Styling

Form Validation Without JavaScript

One of the most powerful applications: styling parent forms based on the validation state of their inputs. No JavaScript required for visual feedback!

/* Highlight form when any input is invalid */
form:has(input:invalid:not(:placeholder-shown)) {
 border-color: #ef4444;
}

/* Change button state based on checkbox */
button:has(input:checked) {
 background: #10b981;
}

/* Show warning when any required field is empty */
.form-group:has(input:required:placeholder-shown) {
 border-left: 3px solid #f59e0b;
 padding-left: 1rem;
}

Interactive UI Components

Create interactive components without JavaScript:

/* Expand details when checkbox is checked */
details:has(input:checked) {
 --expanded: 1;
}

/* Highlight active form field */
.input-wrapper:has(input:focus) {
 ring: 2px solid #3b82f6;
}

This approach reduces the need for complex JavaScript validation scripts, resulting in faster websites with better user experiences.

State-based styling with :has() works seamlessly with responsive design patterns to create forms that adapt to user input across all device sizes.

Use Case 4: Quantity Queries

Styling Based on Number of Children

The :has() selector enables "quantity queries" in CSS--the ability to style an element differently based on how many children it contains. This was previously only possible with JavaScript.

/* Has at most 3 children (excluding 0) */
ul:has(> :nth-child(-n+3):last-child) {
 outline: 1px solid #3b82f6;
}

/* Has at least 5 children */
ul:has(> :nth-child(5)) {
 columns: 2;
}

/* Has exactly 4 children */
.grid:has(> :nth-child(4):last-child) {
 grid-template-columns: repeat(2, 1fr);
}

Responsive Grid Adjustments

Create adaptive layouts that respond to content quantity:

/* 1 column for 1-3 items */
.gallery:has(> :nth-child(4):last-child) {
 grid-template-columns: repeat(3, 1fr);
}

/* 4 columns for 9+ items */
.gallery:has(> :nth-child(9)) {
 grid-template-columns: repeat(4, 1fr);
}

This technique is invaluable for building responsive designs that automatically adapt to varying content lengths.

Quantity queries work especially well with CSS Grid layouts, allowing you to create truly adaptive gallery and grid components that respond to their content automatically.

Use Case 5: Complex Selectors with :not()

Combining :has() with :not()

The :not() pseudo-class inside :has() creates powerful exclusion patterns:

/* Cards without images */
.card:not(:has(img)) {
 padding: 2rem;
 background: #f9fafb;
}

/* Images without alt text - accessibility check */
article:has(img:not([alt])) {
 border: 2px dashed #ef4444;
}

/* Forms with filled optional fields */
.form-group:has(input:not([required]):not(:placeholder-shown)) {
 background: #ecfdf5;
}

/* Items that contain only specific children */
.container:not(:has(:not(img))) {
 /* Container has ONLY images as children */
}

Advanced Exclusion Patterns

Catch accessibility issues and create sophisticated conditional logic without JavaScript. The :has(img:not([alt])) pattern is particularly useful for identifying accessibility problems during development, helping ensure your website meets accessibility standards.

Best Practices and Performance

Performance Considerations

Modern browsers have significantly optimized :has() performance. It's now as performant as other pseudo-classes in most cases. However, keep these considerations in mind:

  • Avoid deeply nested chains: div:has(span:has(a:has(b))) may impact performance
  • Keep selectors specific: More specific selectors match faster
  • Use containment wisely: Consider CSS containment for large lists

Fallback Strategies for Older Browsers

Graceful degradation when :has() is unsupported:

/* Graceful degradation with :is() */
:is(.card):has(img) {
 display: flex;
}

/* In unsupported browsers, card gets base styles without image-specific styles */
.card {
 /* base styles */
}

When NOT to Use :has()

  • Complex animations: Use CSS transitions on actual element states
  • Logic requiring calculation: JavaScript is still better for arithmetic
  • Deeply nested queries: Consider restructuring HTML for better performance

Following these best practices ensures your CSS architecture remains maintainable and performant.

For teams working on complex projects, establishing clear web development guidelines helps maintain code quality across the codebase.

Limitations and Edge Cases

What :has() Cannot Do

  • Cannot nest :has(): div:has(:has(span)) is invalid
  • Pseudo-elements not valid inside: div:has(::before) won't work
  • Cannot select parents of pseudo-elements: Prevents cyclic querying
  • Cannot use :has() inside :has(): Even when chained with different selectors
/* Invalid - nested :has() */
.headings:has(.subtitle:has(h2)) { /* Won't work */ }

/* Valid - chained :has() */
.headings:has(h2):has(.subtitle) h2 { /* Works */ }

Understanding these limitations helps you avoid frustration and choose the right tool for each styling challenge. For complex interactive features beyond :has() capabilities, consider our JavaScript development services.

Real-World Examples and Code Patterns

Complete Component Examples

Adaptive Card Component:

/* Default card styles */
.card {
 padding: 1.5rem;
 border: 1px solid #e5e7eb;
 border-radius: 0.5rem;
}

/* Card with image - horizontal layout */
.card:has(> img) {
 display: flex;
 gap: 1.5rem;
 padding: 0;
}

.card:has(> img) img {
 width: 40%;
 object-fit: cover;
}

/* Card with action button */
.card:has(.btn) {
 display: flex;
 flex-direction: column;
}

.card:has(.btn) .content {
 flex: 1;
}

Interactive Navigation:

/* Add indicator to nav items with dropdown */
nav a:has(+ ul) {
 position: relative;
}

nav a:has(+ ul)::after {
 content: "▼";
 font-size: 0.5em;
 margin-left: 0.25rem;
}

/* Highlight active page */
nav a:has(+ ul.active) {
 color: #3b82f6;
}

These patterns demonstrate how :has() enables sophisticated UI components with pure CSS, reducing JavaScript complexity and improving page load times.

When implementing these patterns in production, following established web development best practices ensures your CSS remains maintainable and performant.

Conclusion

The CSS :has() selector represents a paradigm shift in what CSS can accomplish declaratively. With full browser support since December 2023, it's now safe to use in production for:

  • Parent selection: Style containers based on their contents
  • Previous sibling targeting: Style elements based on what comes before them
  • State-based styling: Create interactive forms and components without JavaScript
  • Quantity queries: Adapt layouts based on content count

Start incorporating :has() into your projects today to reduce JavaScript dependency and create more maintainable, performant CSS. Whether you're building custom web applications or optimizing existing sites, this powerful selector belongs in every modern developer's toolkit.


Sources

  1. MDN Web Docs - :has()
  2. Can I Use - CSS :has()
  3. LogRocket - The different ways to use CSS :has()
  4. Bejamas - Learn CSS :has() selector by examples

Ready to Modernize Your Web Development?

Our team specializes in building performant, accessible websites using the latest CSS techniques and modern development practices.

Frequently Asked Questions