What is the CSS :has() Selector?
The :has() pseudo-class represents an element if any of the relative selectors passed as an argument match at least one element. This powerful selector enables styling based on descendants or subsequent siblings--a capability developers have requested for over two decades.
For years, CSS selectors worked only in one direction: from parent to child. With :has(), you can now style parent elements based on their children, or style elements based on what follows them. This eliminates the need for JavaScript in many common UI patterns that previously required complex event listeners and class manipulation.
Why :has() Matters
The :has() pseudo-class is the only CSS selector that enables selecting elements based on whether they contain specific descendants. This represents a paradigm shift in how we approach CSS architecture:
- Eliminate JavaScript dependencies for common UI patterns like conditional styling and form validation
- Cleaner, more maintainable code organization that reflects the visual hierarchy
- Better performance through native CSS selection rather than JavaScript-driven DOM manipulation
- More intuitive CSS that allows you to style based on the actual content structure
Before :has(), developers had to use workarounds like adding classes to parent elements via JavaScript or using the "reverse" descendant selector pattern. These approaches added complexity and made CSS harder to maintain. With :has(), you can write declarative CSS that adapts to content presence and state without any scripting.
Since reaching Baseline status in December 2023, :has() is now supported across all modern browsers, making it safe for production use in most scenarios.
For teams working on web development services, mastering :has() opens up new possibilities for creating adaptive interfaces with significantly less code.
Browser Compatibility
105+
Chrome Version
15.4+
Safari Version
103+
Firefox Version
2023
Baseline Status
Syntax and Fundamentals
The :has() pseudo-class follows a simple but powerful pattern:
/* Basic syntax */
target:has(selector)
/* Examples */
section:has(.featured) { } /* Section containing .featured */
li:has(p + p) { } /* List item with consecutive paragraphs */
form:has([required]) { } /* Form with required fields */
/* With combinators */
.card:has(> img) { } /* Direct child img */
h1:has(+ h2) { } /* h1 followed by h2 */
The selector inside :has() is a relative selector, meaning it matches from the element being selected, not from the document root. This allows for intuitive, contextual styling that adapts to the actual content structure.
Key Rules and Specifications
The :has() pseudo-class follows several important rules that govern its behavior:
| Rule | Description |
|---|---|
| Specificity | Takes the specificity of the most specific selector in its arguments, similar to :is() and :not() |
| Nesting | Cannot be nested within another :has() |
| Pseudo-elements | Pseudo-elements are not valid selectors within :has() |
| Valid Selectors | Works with all standard CSS selectors: classes, IDs, attributes, pseudo-classes |
Selector Types That Work Inside :has()
| Selector Type | Example | Description |
|---|---|---|
| Class | .card:has(.featured) | Element containing a class |
| ID | section:has(#header) | Element containing an ID |
| Attribute | form:has([required]) | Element with matching attribute |
| Pseudo-class | .nav:has(:hover) | Element with active state |
| Child | .card:has(> img) | Direct child only |
| Descendant | .card:has(img) | Any descendant |
| Adjacent Sibling | h1:has(+ h2) | Immediately following sibling |
| General Sibling | section:has(~ figure) | Any subsequent sibling |
The :has() pseudo-class cannot be nested within another :has(), and pseudo-elements are not valid selectors within :has(). This is because many pseudo-elements exist conditionally based on the styling of their ancestors, and allowing these to be queried by :has() can introduce cyclic querying.
For deeper insights into modern CSS techniques, explore our resources on web performance optimization.
1/* Element with specific class descendant */2article:has(.featured) {3 border: 2px solid #3498db;4}5 6/* Direct child selector */7.card:has(> .promo) {8 margin-top: 1rem;9}10 11/* Adjacent sibling selector */12h1:has(+ h2) {13 margin-bottom: 0.25rem;14}15 16/* Attribute selector */17form:has([type="email"]:invalid) {18 border-color: #e74c3c;19}20 21/* Multiple selectors */22.container:has(.sidebar, .toc) {23 display: grid;24 grid-template-columns: 1fr 250px;25}Parent Element Selection
The :has() pseudo-class enables what developers call the "parent combinator"--the ability to style parent elements based on their children. This was previously impossible with pure CSS and required JavaScript workarounds or awkward CSS patterns.
Styling Parents Based on Children
/* Style article cards when they contain images */
.article-card:has(img) {
display: grid;
grid-template-rows: auto 1fr;
gap: 1rem;
}
/* Add visual indicator to nav items with dropdowns */
.nav-item:has(.dropdown-menu) {
position: relative;
}
.nav-item:has(.dropdown-menu)::after {
content: "▼";
font-size: 0.6rem;
margin-left: 0.5rem;
}
/* Highlight forms containing validation errors */
.form-group:has(.error-message) {
border-left: 3px solid #e74c3c;
padding-left: 1rem;
background-color: #fdf0ed;
}
Direct vs. Descendant Selection Performance
Choosing between direct child (>) and general descendant selectors has significant performance implications, especially on pages with complex DOM structures:
/* Direct child only - more performant */
.card:has(> .promo-badge) {
padding-top: 2rem;
}
/* Any descendant - broader match */
.card:has(.promo-badge) {
padding-top: 2rem;
}
When to use direct children (:has(> child)):
- You know the element is an immediate child
- Performance is critical on complex pages
- You want predictable, bounded DOM traversal
When to use descendants (:has(descendant)):
- The element could be at any depth
- Content structure may change
- You're styling flexible component layouts
The direct child approach (>) provides better performance because the browser only needs to check immediate children rather than traversing the entire subtree. For pages with hundreds of components or complex DOM structures, this difference becomes noticeable during rendering.
Understanding these performance considerations is essential for building performant web applications that scale effectively.
Sibling Combinators with :has()
One of the most powerful features of :has() is its ability to style elements based on what follows them in the DOM. This "look-ahead" capability was previously impossible with pure CSS and enables sophisticated content-aware styling.
Adjacent Sibling Selection (+)
The adjacent sibling combinator allows you to style an element when it's immediately followed by another specific element:
/* Reduce spacing after h1 when followed by h2 */
h1:has(+ h2) {
margin-bottom: 0.25rem;
}
/* Style paragraphs followed by blockquotes */
p:has(+ blockquote) {
border-left: 3px solid #3498db;
padding-left: 1rem;
}
/* Highlight list items with consecutive siblings */
li:has(+ li) {
border-top: 1px solid #e0e0e0;
}
General Sibling Selection (~)
The general sibling combinator styles elements based on any subsequent siblings, not just immediate ones:
/* Style heading when any paragraph follows */
.heading:has(~ p) {
color: #2c3e50;
}
/* Style section when figure appears anywhere after */
section:has(~ figure) {
background-color: #fafafa;
padding: 1rem;
}
Practical Sibling Selection Patterns
Content-aware typography: Style lead paragraphs differently when followed by body text
/* First paragraph that has a following paragraph gets special styling */
p:first-of-type:has(+ p) {
font-size: 1.25rem;
font-weight: 300;
line-height: 1.6;
}
Conditional spacing: Adjust spacing based on what content follows
/* Remove bottom margin from figures followed by captions */
figure:has(+ figcaption) {
margin-bottom: 0;
}
/* Add extra padding to sections containing code followed by output */
.code-block:has(+ .output) {
border-bottom-left-radius: 0;
}
Styling based on preceding content: Use :has() with sibling selectors to style context-aware components
/* Style cards that follow promotional banners */
.promo-banner:has(~ .card) {
margin-bottom: 1.5rem;
}
/* Style headers when any section follows */
.section-header:has(~ section) {
border-bottom: 2px solid #3498db;
padding-bottom: 0.5rem;
}
These sibling selection patterns enable typographic control and conditional styling that adapts to content structure without JavaScript.
State-Based Styling Without JavaScript
The :has() pseudo-class enables sophisticated state-based styling that previously required JavaScript event handlers or class manipulation. This opens up powerful patterns for creating responsive, interactive interfaces with pure CSS.
Form Validation Styling
Create comprehensive form feedback systems that style entire containers based on input states:
/* Style form when any input is invalid and has been interacted with */
form:has(input:invalid:not(:placeholder-shown)) {
border: 2px solid #e74c3c;
}
/* Highlight specific field on invalid focus */
input[type="email"]:focus:invalid {
border-color: #e74c3c;
background-color: #fdf0ed;
}
/* Style entire field group when it contains a focused input */
.field-group:has(input:focus) {
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.3);
border-radius: 4px;
}
/* Show validation message styling based on error presence */
.form-group:has(.error) .error-message {
display: block;
color: #e74c3c;
font-size: 0.875rem;
}
Focus and Interaction States
Style parent containers based on child interaction states for enhanced UX:
/* Style parent card when any child has focus */
.card:has(*:focus) {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
/* Hover effects that span relationships */
.card:has(.btn:hover) {
border-color: #3498db;
}
/* Navigation active states */
.nav-item:has(.active) {
background-color: #f0f4f8;
border-left: 3px solid #3498db;
}
/* Highlight card when internal content is hovered */
.card:has(*:hover) {
transition: all 0.2s ease;
}
Conditional Button States and Progress Indicators
Create dynamic interfaces that respond to user interaction without JavaScript:
/* Disable form submit when any required field is empty */
form:has([required]:placeholder-shown) .submit-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Show loading indicator when form is processing */
.form-group:has(.loading) .status-indicator {
display: flex;
animation: spin 1s linear infinite;
}
/* Highlight sections with unsaved changes */
.section:has(.dirty) {
border-left: 3px solid #f39c12;
background-color: #fef9e7;
}
/* Accessibility: Indicate when expanded content is present */
.details:has([open]) summary {
font-weight: 600;
color: #2c3e50;
}
Accessibility Considerations
When using :has() for state-based styling, keep accessibility in mind:
- Focus indicators: The
:has(*:focus)pattern helps maintain visible focus states on complex components - Reduced motion: Always respect
prefers-reduced-motionwhen animating state changes - Screen readers: State changes via
:has()don't announce automatically--consider ARIA live regions for critical updates - Color contrast: Ensure validation styling maintains sufficient color contrast ratios
These state-based styling patterns are particularly valuable for SEO-optimized forms where user experience directly impacts conversion rates and search engine rankings.
Combining :has() with Other Pseudo-Classes
The :has() pseudo-class becomes even more powerful when combined with other pseudo-classes like :not(), :is(), and :where() to create sophisticated, flexible selectors.
Using :not() with :has()
The :not selector excludes certain elements from being styled based on specific criteria. This combination enables "include when X, exclude when Y" logic:
/* Style cards with images but no videos */
.card:has(img):not(:has(video)) {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
/* Highlight articles with featured class that aren't featured themselves */
article:has(.featured):not(.featured) {
opacity: 0.9;
}
/* Style items with two paragraphs but no images */
li:has(p + p):not(:has(img)) {
padding: 1rem;
}
/* Exclude already-processed items from styling */
.list-item:has(.new):not(:has(.read)) {
background-color: #e8f4fd;
}
Using :is() and :where()
These pseudo-classes provide flexible selector matching that simplifies complex :has() patterns:
/* Match any heading type followed by paragraph */
h1:is(h1, h2, h3):has(+ p) {
margin-bottom: 0;
}
/* Style sections containing any interactive element */
section:has(:is(button, a, input)) {
padding: 1rem;
border-radius: 8px;
}
/* Flexible selector matching for media elements */
.card:has(:is(img, video, iframe)) {
aspect-ratio: 16 / 9;
}
/* Reduce specificity with :where() for easier overrides */
.card:has(:where(.promo, .featured)) {
border-color: #3498db;
}
Specificity Considerations
When combining pseudo-classes, specificity becomes important:
| Combination | Specificity |
|---|---|
:has(.class) | Class specificity (0,1,0) |
:has(#id) | ID specificity (1,0,0) |
:has(:is(.a, .b)) | Highest specificity in list |
:has(:where(.a)) | Zero specificity (overridable) |
Best practices for readable combinations:
- Use :is() for grouped matching - simplifies multiple selector patterns
- Use :where() for base styles - allows easier overriding
- Chain :not() at the end - keeps intent clear
- Comment complex selectors - explain the selection logic
/* Complex but readable: style articles that have media but no video */
article:has(:is(img, audio)):not(:has(video)) {
/* Clear intent: has media, but not video */
}
These combinations enable patterns that would require multiple JavaScript event handlers or complex class toggling with pure CSS.
Real-world patterns you can implement today with CSS :has()
Dynamic Badges and Labels
Automatically add badges to content based on properties like new articles, updated posts, or featured items--without JavaScript.
Smart Card Components
Create adaptive card layouts that change based on content presence--images, videos, actions, or text-only layouts.
Navigation Enhancements
Style navigation based on dropdown presence, active states, and user interactions for better UX.
Table Row Styling
Apply conditional formatting to table rows based on content, status indicators, or action buttons.
Form Feedback
Create responsive forms that adapt styling based on validation states and user input.
Content-Aware Typography
Adjust spacing and formatting based on content structure and heading relationships.
Performance Considerations
While :has() is well-optimized in modern browsers, understanding performance implications helps you write efficient CSS that scales.
Browser Optimization
Modern browsers have optimized :has() selectors to avoid full document rescans. When a state change occurs, browsers use optimized internal mechanisms to update only affected elements. This means :has() performs significantly better than equivalent JavaScript-based solutions that require manual DOM queries and class manipulation.
Best Practices for Performance
1. Be Specific with Selectors: More specific selectors perform better by limiting the search scope
/* Better performance - specific element type */
div:has(article.promo) { }
/* Lower performance - too broad */
div:has(.child) { }
2. Prefer Direct Children: Using > limits the search scope to immediate children only
/* Better performance - limited scope */
.card:has(> img) { }
/* Broader search - entire subtree */
.card:has(img) { }
3. Limit Nesting Depth: Deeper DOM traversal is more expensive
/* Better performance - single level */
section:has(.featured) { }
/* Lower performance - deeper traversal */
section:has(.content .featured) { }
4. Avoid Overly Complex Pseudo-Class Chains: While each pseudo-class adds minimal overhead, chaining many together can impact rendering
/* Efficient - single condition */
.card:has(.featured) { }
/* Less efficient - multiple conditions */
.card:has(.featured):has(.urgent):has(.new) { }
Progressive Enhancement with @supports
Use feature detection to provide graceful degradation:
/* Base styles for all browsers */
.card {
padding: 1rem;
}
/* Enhanced styles for :has() support */
@supports (selector(:has(*))) {
.card:has(.featured) {
border: 2px solid #3498db;
}
.card:has(*:hover) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
/* Fallback for unsupported browsers */
@supports not (selector(:has(*))) {
.card.featured {
border: 2px solid #3498db;
}
}
When to Prefer JavaScript
While :has() handles most use cases well, consider JavaScript for:
- Complex state logic that depends on multiple external factors
- Animations triggered by state changes that require precise control
- Legacy browser support for IE11 or very old browsers
- Server-side rendering where CSS :has() state changes aren't available
For the vast majority of modern web applications, :has() provides the best balance of performance, maintainability, and browser compatibility.
Explore our complete guide to CSS media queries for more performance optimization techniques.
Frequently Asked Questions
Conclusion
The CSS :has() pseudo-class represents a significant advancement in CSS capabilities, enabling patterns that previously required JavaScript or complex CSS hacks. As browser support has reached Baseline status, :has() is now safe for production use in most scenarios.
By enabling parent and preceding-sibling selection, :has() opens new possibilities for responsive, content-aware styling that adapts to the actual content and state of elements:
- Cleaner code without JavaScript dependencies for common UI patterns
- More intuitive CSS organization reflecting the visual hierarchy
- Better performance through native CSS selection rather than DOM manipulation
- Progressive enhancement with @supports fallbacks for older browsers
The key benefits we explored include parent element selection, sibling combinators, state-based styling without JavaScript, and powerful combinations with other pseudo-classes. These capabilities enable you to create adaptive, responsive interfaces that respond to content presence and user interaction.
Start incorporating :has() into your projects today to simplify styling and create more maintainable web interfaces. Begin with simple use cases like conditional card styling or form validation, then expand to more complex patterns as you become comfortable with the selector.
For teams building modern web applications, mastering :has() is essential for taking advantage of CSS's full potential without sacrificing browser compatibility or performance.