How and When to Use the CSS :has() Selector

Master the powerful 'parent selector' that enables state-based styling and conditional layouts without JavaScript

What is the CSS :has() Selector?

The CSS :has() pseudo-class is a functional selector that takes a relative selector list as its argument. It represents an element if any of the selectors passed as an argument match at least one element when anchored against this element.

In simpler terms: :has() lets you style an element based on its children or previous siblings--something that wasn't possible in CSS for decades. This opens entirely new possibilities for responsive layouts, state management, and conditional styling without JavaScript.

Historically, CSS selectors have worked in a "top-down" fashion. You could select a child based on its parent, but not a parent based on its child. The :has() pseudo-class flips this paradigm, working in a "bottom-up" fashion that enables entirely new styling patterns. This shift is particularly valuable for modern front-end development projects that prioritize performance and clean code.

Browser Support

The :has() selector reached Baseline status in December 2023, meaning it works reliably across all major browsers:

  • Safari 15.4+ (March 2022)
  • Chrome/Edge 105+ (August 2022)
  • Firefox 121+ (December 2023)

Current global browser support stands at approximately 92% according to Can I Use, making it safe to use in production with progressive enhancement for edge cases.

Syntax and Basic Usage

The syntax follows the pattern element:has(selector). Inside the parentheses, you can use any valid CSS selector, including combinators for descendants, children, and siblings.

Basic Examples

/* Select a card that contains an image */
.card:has(img) {
 border: 2px solid #3b82f6;
}

/* Select a section with a featured article */
section:has(.featured) {
 background-color: #f0f9ff;
}

/* Select a list item that has a link */
li:has(a) {
 padding: 0.5rem;
}

/* Select a form with required fields */
form:has([required]) {
 border: 1px solid #e5e7eb;
}

The flexibility of the selector inside :has() makes it incredibly powerful for a wide range of use cases, from simple content detection to complex conditional styling. Combined with our responsive design services, this enables sophisticated layouts that adapt to content without JavaScript.

Key Use Cases for :has()

1. Parent Element Selection

Parent selection was long considered the "holy grail" of CSS selectors. The :has() pseudo-class finally delivers this capability, allowing you to style a parent element based on its children.

Practical applications:

  • Highlighting cards that contain images
  • Adding special spacing to containers with specific content types
  • Styling form sections that contain errors
  • Adding visual indicators to lists with certain item types
/* Add border to cards containing images */
.card:has(img) {
 border: 2px solid #3b82f6;
}

/* Style article cards that have a video */
.article-card:has(video) {
 background: linear-gradient(to bottom, #f0f9ff, #fff);
}

/* Highlight form groups with errors */
.form-group:has(.error) label {
 color: #dc2626;
}

2. Previous Sibling Selection

Before :has(), CSS could only select elements that came after other elements. Using sibling combinators inside :has() enables selecting elements based on what precedes them.

/* Style a heading that has a paragraph immediately following it */
h1:has(+ p) {
 margin-bottom: 0;
}

/* Style list items that follow a special item */
li.special:has(~ li) {
 border-top: 1px solid #e5e7eb;
}

3. State-Based Styling Without JavaScript

One of the most powerful applications is creating state-based styles without JavaScript. Combine :has() with form pseudo-classes for sophisticated conditional styling, as demonstrated by industry experts.

/* Highlight a field group when any field has focus */
.field-group:has(:focus-within) {
 border-color: #3b82f6;
 box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

/* Style a card based on checkbox state */
.pricing-card:has(input:checked) {
 border-color: #3b82f6;
 transform: scale(1.02);
}

This capability reduces the need for JavaScript event handlers and class toggling, resulting in cleaner codebases and improved performance--key benefits for any modern web application.

Performance Considerations

While :has() is powerful, it does have performance implications that developers should understand. The :has() pseudo-class creates a "retroactive" style application--when the matched element changes, the browser must re-evaluate all ancestors and preceding siblings.

Best Practices for Performance

  1. Keep selectors simple: More complex selectors inside :has() require more processing. Use simple element or class selectors when possible, as recommended by performance experts.

  2. Avoid deeply nested selectors: The deeper the selector chain inside :has(), the more expensive the style calculation becomes.

  3. Use specific selectors first: If combining :has() with other selectors, place the most specific conditions outside the :has() call when possible.

  4. Consider the scope: Applying :has() at the component level rather than the document level reduces performance impact.

/* Good - simple selector */
.card:has(img) { }

/* Avoid when possible - complex selector */
.card:has(.content .image-container img.special) { }

/* Better alternative if possible */
.card:has(.special-image) img { }

When to Use :has() vs JavaScript

Use :has() when:

  • Styling depends purely on DOM structure
  • State is tied to form validation
  • Visual feedback responds to user focus
  • Layout needs adjustment based on content type

Use JavaScript when:

  • Styles depend on external data
  • Business logic drives the decision
  • Animations need precise timing control
  • State must persist across sessions

For custom web development projects, :has() offers an elegant solution when styling decisions can be made purely through CSS selectors.

Best Practices

Progressive Enhancement

Since browser support is excellent but not universal, use @supports for graceful degradation. Many :has() use cases are "nice-to-have" enhancements that don't break functionality when absent.

/* Base styles for all browsers */
.card {
 padding: 1rem;
}

/* Enhanced styles for supporting browsers */
@supports selector(:has(*)) {
 .card:has(img) {
 padding: 1.5rem;
 }
}

Semantic HTML Still Matters

The :has() pseudo-class should complement, not replace, semantic HTML structure. Use :has() to enhance presentation based on meaningful content relationships, not to compensate for poor markup. This principle aligns with our approach to clean, maintainable code for long-term project success.

Combine with Modern CSS Features

The :has() selector works beautifully with other modern CSS features like container queries, custom properties, and logical properties:

@container (min-width: 400px) {
 .card:has(img) {
 display: grid;
 grid-template-columns: auto 1fr;
 gap: 1rem;
 }
}

When used alongside responsive design frameworks and CSS custom properties, :has() enables sophisticated adaptive designs that respond to both content and viewport conditions.

Common Patterns and Examples

Form Validation Feedback

/* Highlight the entire form group when a field is invalid */
.form-group:has(input:invalid:not(:focus-without)) {
 border-left: 3px solid #dc2626;
}

/* Show success state when all required fields are filled */
.form-section:has(input:required:valid) {
 background-color: #f0fdf4;
}

Interactive Cards and Lists

/* Add hover effect to card when it contains a link */
.card:has(a:hover) {
 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* Highlight items that have been favorited */
.list-item:has(.favorite-btn[data-active="true"]) {
 background-color: #fef3c7;
}

Navigation and Menu Styling

/* Style nav items that have dropdowns */
.nav-item:has(.dropdown) > a::after {
 content: " ▼";
 font-size: 0.7em;
}

/* Highlight the current section in a sidebar */
.sidebar-link:has(~ .active) {
 background-color: #f3f4f6;
}

These patterns demonstrate how :has() simplifies common UI requirements that previously required JavaScript event handling, reducing bundle size and improving maintainability for single-page applications and beyond.

Why Use CSS :has()?

Key benefits for modern web development

No JavaScript Required

Create sophisticated state-based styling without writing a single line of JavaScript

Parent Selection

Finally style parent elements based on their children--a long-requested CSS feature

Previous Sibling Selection

Style elements based on what precedes them, enabling entirely new layout patterns

Excellent Browser Support

92%+ global browser support with Baseline status since December 2023

Performance Benefits

Leverage the browser's native styling engine for faster, more efficient updates

Progressive Enhancement

Use @supports for graceful degradation on older browsers

Frequently Asked Questions

Ready to Modernize Your Web Development?

Our team builds custom websites using the latest CSS features and modern frameworks for optimal performance and user experience.

Sources

  1. MDN Web Docs - :has() Selector - Official CSS specification reference
  2. Josh W. Comeau - The Undeniable Utility Of CSS :has - Comprehensive practical guide with real-world examples
  3. LogRocket - The different ways to use CSS :has(), with examples - Multiple use cases with code examples
  4. Can I Use - CSS :has - Browser support data and compatibility information