What is the CSS :has() Selector?
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. In simpler terms, :has() allows you to select elements based on their children, descendants, or siblings--something that was previously impossible with CSS alone.
Before :has(), styling a parent element based on its children required workarounds like JavaScript event listeners or adding wrapper classes to parent elements. Now, with native CSS support, you can write cleaner, more semantic markup while achieving the same visual results.
Browser support for :has() reached Baseline status in December 2023, with support in Chrome 105+, Firefox 121+, and Safari 15.4+. This universal support means you can confidently use :has() in production applications today.
Key Capabilities of :has()
- Parent Selection: Style elements based on their children
- Sibling Targeting: Select previous siblings (previously impossible in CSS)
- State-Based Styling: Create responsive forms without JavaScript
- Quantity Queries: Adapt layouts based on number of children
By mastering these techniques, you can create more performant and responsive web applications that rely less on JavaScript for visual effects.
Four powerful use cases that transform your CSS workflow
Parent Selection
Style parent elements based on their children. No more JavaScript workarounds or wrapper classes needed.
Previous Sibling Selection
Finally, CSS can style elements based on what comes before them. Create complex interactions without JavaScript.
State-Based Styling
Create sophisticated form validation feedback and interactive patterns using pure CSS.
Quantity Queries
Adapt layouts based on how many children an element contains. Create truly responsive components.
Parent Selection: Styling Based on Children
The most anticipated use case for :has() is selecting parent elements based on their children. This capability transforms how we approach component styling.
Basic Parent Selection
/* Style cards that contain an image */
.card:has(img) {
display: flex;
flex-direction: column;
}
/* Style sections that contain a featured article */
section:has(.featured) {
border: 2px solid #3b82f6;
}
This approach keeps your HTML clean and semantic while giving CSS full control over styling based on content presence.
Selecting Based on Specific Child Classes
/* Style buttons that contain an icon */
button:has(.icon) {
display: flex;
gap: 0.5rem;
align-items: center;
}
/* Style navigation items that have a dropdown */
nav li:has(ul) > a::after {
content: "+";
margin-left: 0.5rem;
}
Combining :has() with Other Selectors
The true power of :has() emerges when combined with other CSS selectors. You can chain multiple :has() conditions or combine them with :not() for sophisticated selection logic:
/* Select cards that have an image but no video */
.card:has(img):not(:has(video)) {
background: linear-gradient(to bottom, #f8fafc, #e2e8f0);
}
/* Style articles that contain at least one image followed by a paragraph */
article:has(img + p) {
padding: 1.5rem;
}
This flexibility makes :has() invaluable when building reusable components in frameworks like Next.js, where the same component might render different content and need to adapt its styling accordingly. By leveraging the parent selector, your component library becomes more dynamic and self-adapting.
For teams focused on modern frontend development, these CSS techniques reduce the need for conditional class rendering and keep component logic focused on functionality rather than styling concerns.
Previous Sibling Selection
Before :has(), CSS could only select subsequent siblings. Now we can style elements based on what comes before them.
Styling Labels Based on Checkbox State
/* Style the label when its checkbox is checked */
label:has(+ input:checked) {
color: #22c55e;
font-weight: 500;
}
/* Highlight form fields that have focus */
.input-wrapper:has(input:focus) {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}
Conditional Styling Based on Sibling Presence
/* Add separator to items that have siblings after them */
.breadcrumb-item:has(~ .current) {
position: relative;
}
.breadcrumb-item:has(~ .current)::after {
content: "/";
margin-left: 0.5rem;
color: #9ca3af;
}
Complex Hover Effects
The previous sibling selection opens up possibilities for hover effects that respond to multiple elements. For example, you can create a list where hovering over one item affects the styling of preceding items:
/* Style list items based on hovered state of subsequent items */
li:has(+ li:hover) {
opacity: 0.7;
}
li:hover {
opacity: 1;
transform: scaleY(1.1);
}
This pattern is particularly useful for creating sophisticated navigation menus, image galleries, and interactive card grids where visual relationships between elements enhance the user experience. Combined with CSS transitions and transforms, these effects create polished, responsive interfaces without any JavaScript.
These techniques complement other advanced CSS methodologies by enabling more expressive styling without compromising performance.
State-Based Styling for Forms
One of the most practical applications of :has() is creating sophisticated form styling based on validation states.
Form Validation Styling
/* Style the form when it contains invalid fields */
form:has(input:invalid:not(:focus)) {
border-color: #ef4444;
}
/* Style input groups that have error messages */
.input-group:has(.error-message) input {
border-color: #ef4444;
background-color: #fef2f2;
}
Toggle and Switch Interfaces
/* Expandable content without JavaScript */
.toggle-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.toggle-wrapper:has(input:checked) .toggle-content {
max-height: 500px;
}
This approach enables progressive enhancement patterns where forms provide visual feedback as users complete them. The :has() selector works seamlessly with other pseudo-classes like :invalid, :required, :focus, and :checked to create comprehensive form styling systems. Learn more about CSS :has() patterns from LogRocket
For developers building custom web applications, these CSS-only patterns reduce JavaScript dependencies, improve performance, and enhance accessibility by relying on native browser behavior.
When working on responsive web design projects, these form techniques ensure consistent user experiences across all device sizes without complex media query logic.
Quantity Queries with :has()
Style elements based on how many children they contain--perfect for adaptive layouts.
Styling Based on Number of Children
/* Style lists with exactly 3 items differently */
ul:has(> li:nth-child(3):last-child) {
grid-template-columns: repeat(3, 1fr);
}
/* Gallery grid that adapts to number of images */
.gallery:has(> img:nth-child(2):last-child) {
grid-template-columns: repeat(2, 1fr);
}
.gallery:has(> img:nth-child(4):last-child) {
grid-template-columns: repeat(4, 1fr);
}
Conditional Content Display
/* Only show 'and more' text when there are additional items */
.item-list:has(> .item:nth-child(3)) .more-indicator {
display: block;
}
/* Highlight sections with no content */
section:not(:has(*)) {
display: none;
}
Quantity queries are invaluable for creating components that automatically adapt their styling based on the content they receive. A gallery grid, for instance, can switch between single-column, two-column, and four-column layouts depending on how many images are present--without any media queries or JavaScript. This pattern is especially useful when building responsive websites that need to handle variable content volumes gracefully.
These adaptive layout techniques are essential for performance-optimized web applications where minimizing client-side logic improves load times and user experience.
Performance Best Practices
While :has() is powerful, understand its performance characteristics to use it effectively.
Selector Complexity
The :has() selector can be computationally expensive with complex selectors. Keep these guidelines in mind:
- Prefer specific selectors within
:has()to reduce evaluation scope - Avoid nesting
:has()selectors, as this significantly increases complexity - Combine with class selectors rather than element selectors when possible
/* Good: Specific selector within :has() */
.card:has(.featured) { }
/* Avoid: Generic element selector */
.card:has(img) { }
Testing Performance in Browser Dev Tools
Modern browsers provide excellent tooling for debugging and profiling CSS selector performance:
- DevTools Performance Panel: Record interactions and watch for layout recalculations triggered by
:has()selectors - CSS Overview: Chrome DevTools shows CSS selector complexity and potential performance issues
- Rendering Settings: Enable "Show layout shift regions" and "Show paint rectangles" to identify
:has()impact
Fallback Strategies
For critical styling, provide fallbacks for older browsers:
/* Primary styling with :has() */
.card:has(.badge) {
padding-left: 1.5rem;
}
/* Fallback for browsers without :has() support */
@supports not (:has(.badge)) {
.card.has-badge {
padding-left: 1.5rem;
}
}
By following these best practices and testing with real content volumes, you can leverage :has() effectively while maintaining excellent performance in your performance-optimized web applications.
Practical Examples for Modern Web Development
Next.js Component Pattern
In Next.js applications, :has() enables clean component patterns:
/* Card component with conditional header styling */
.card:has(.card-header) {
padding-top: 0;
}
/* Responsive grid that adapts to content */
.grid-item:only-child {
grid-column: 1 / -1;
}
.grid-item:has(~ .grid-item) {
grid-column: span 1;
}
Accessible Interactive Patterns
Create accessible interactive patterns without JavaScript:
/* Expandable sections */
details:has([open]) summary {
margin-bottom: 1rem;
}
/* Highlight form validation */
.form-field:has(:invalid:focus) {
border-color: #ef4444;
}
Adaptive Layout Components
Build layout components that automatically adapt:
/* Sidebar that shows/hides based on content */
.sidebar:has(.widget) {
width: 280px;
}
.sidebar:not(:has(.widget)) {
width: 0;
padding: 0;
}
Conclusion
The CSS :has() selector represents a significant leap forward in what we can accomplish with pure CSS. By enabling parent selection, previous sibling targeting, and state-based styling, it reduces our dependence on JavaScript for common UI patterns while keeping our markup cleaner and more semantic. Explore practical :has() examples from Bejamas
As browser support is now universal for modern browsers and the selector has achieved Baseline status, there's no reason to avoid using :has() in your projects. Start incorporating it into your CSS toolkit to create more adaptive, responsive, and maintainable interfaces. For Next.js developers specifically, :has() aligns perfectly with the performance-first approach--allowing complex UI patterns without client-side JavaScript, resulting in faster page loads and better user experiences.
To continue advancing your frontend skills, explore our comprehensive web development services and learn how modern CSS techniques integrate into professional development workflows.
Frequently Asked Questions
Sources
- MDN Web Docs - CSS :has() Selector - Official documentation covering syntax, browser support, and performance guidelines
- Bejamas - Learn CSS :has() selector by examples - Comprehensive guide with practical demos
- LogRocket Blog - The different ways to use CSS :has(), with examples - Detailed exploration with code examples
- Can I Use - CSS :has() - Browser support information