The Challenge of Previous Sibling Selection
For years, CSS developers faced a frustrating limitation: there was no way to select previous siblings. You could easily target the next sibling with the adjacent combinator (+), but going backward in the DOM was impossible without JavaScript or creative HTML restructuring. The :has() pseudo-class changed everything, opening up entirely new possibilities for CSS-based styling patterns.
This guide explores how to leverage :has() to select previous siblings, with practical examples you can apply to modern web development projects.
The Game-Changer: CSS :has() Pseudo-Class
The :has() pseudo-class is a relational selector that checks for descendants or siblings. Part of the CSS Selectors Level 4 specification, :has() now enjoys broad support across all modern browsers, enabling "reverse" selection patterns that were previously impossible.
/* Select .box elements that are immediately followed by .circle */
.box:has(+ .circle) {
background-color: #e0f2fe;
border-color: #0284c7;
}
This selector finds all .box elements, filters to only those matching "self + .circle", and returns the .box that precedes the .circle. Understanding how these CSS selectors work opens up powerful new approaches to component styling.
Immediate Previous Sibling
Target the element immediately before another using :has() with the adjacent sibling combinator (+)
Nth Previous Sibling
Select elements further back in the DOM by chaining multiple adjacent combinators
All Preceding Siblings
Use the general sibling combinator (~) to select every element that comes before a reference
Conditional Styling
Create dynamic UI patterns based on sibling relationships without JavaScript
Selecting the Immediate Previous Sibling
The fundamental pattern for selecting the previous sibling combines :has() with the adjacent sibling combinator:
/* Select .box elements that are immediately followed by .circle */
.box:has(+ .circle) {
background-color: #e0f2fe;
border-color: #0284c7;
}
This selector works by:
- Finding all .box elements in the document
- Filtering to only those that match the pattern "self + .circle"
- Returning the .box that precedes the .circle
Example HTML Structure
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="circle"></div>
<div class="box"></div>
The selector will target the third .box (the one immediately before .circle).
This technique works seamlessly with CSS modules for component-scoped styling, keeping your selectors isolated and maintainable.
Selecting the Nth Previous Sibling
For selecting siblings that are not immediately adjacent, add more adjacent combinators:
/* Select .box that is 2 siblings before .circle */
.box:has(+ * + .circle) {
background-color: #fef3c7;
border-color: #d97706;
}
/* Select .box that is 3 siblings before .circle */
.box:has(+ * + * + .circle) {
background-color: #fce7f3;
border-color: #db2777;
}
The wildcards (*) represent intermediate elements between the target and the reference element. You can chain as many combinators as needed for your use case. This approach gives you precise control similar to CSS typed arithmetic for programmatic styling decisions.
Selecting All Preceding Siblings
Combine :has() with the general sibling combinator (~) to select all elements that come before a reference element:
/* Select all .box elements that precede a .circle anywhere */
.box:has(~ .circle) {
background-color: #ede9fe;
border-color: #7c3aed;
}
Selective Preceding Siblings
To select all preceding siblings except the immediately adjacent one:
/* Select .box elements before .circle, but not the one right before */
.box:has(~ * + .circle) {
background-color: #f0fdf4;
border-color: #16a34a;
}
Form Validation
Highlight input fields when the next field has validation errors
Table Row Styling
Add emphasis to rows that precede total or summary rows
Card Layouts
Add spacing after cards that precede CTA cards
Navigation States
Style nav items before the active item for visual flow
Performance Considerations
The :has() pseudo-class is evaluated during style calculation, similar to other CSS selectors. Here are key performance guidelines:
Best Practices
- Keep selector chains short: Deeply nested :has() chains may impact initial render
- Use specific element types: Avoid universal selectors (*) when possible
- Test on target devices: Complex :has() patterns should be tested across your supported browsers
- Avoid in animations: Don't use :has() inside @keyframes animations
Browser Optimization
Modern browser engines optimize :has() queries progressively, making them performant for typical UI patterns. The selector is generally fast enough for production use in interactive components.
Best Practices and Common Pitfalls
Do
- Use specific selectors rather than generic wildcards
- Test across target browsers and devices
- Combine with feature queries (@supports) for graceful degradation
- Keep selector intent clear and maintainable
Don't
- Chain too many :has() selectors (creates complexity)
- Rely on :has() for critical layout-dependent styles
- Use :has() with pseudo-elements (not supported)
- Nest :has() inside :has() (not permitted by specification)
Frequently Asked Questions
Conclusion
The ability to select previous siblings with CSS :has() represents a fundamental shift in what's possible with pure CSS. What once required JavaScript or creative DOM restructuring can now be achieved with elegant, maintainable selectors.
As browser support continues to grow and developers explore these patterns, we'll see more sophisticated UI patterns emerge that leverage the full power of relational selectors. Start incorporating :has() into your projects today to create responsive, interactive interfaces without relying on JavaScript for style-related functionality.
Related Resources:
Sources
- MDN Web Docs - :has() Selector - Official CSS specification documentation
- Tobias Ahlin - Selecting previous siblings with CSS :has() - Practical examples with visual demos
- MDN Web Docs - Next-sibling combinator - Adjacent sibling combinator documentation