Let’s Talk About :has() — The Game-Changer You Didn’t Know You Needed
Okay, picture this: you’re deep into a project, juggling a mess of JavaScript and CSS just to get some simple UI toggles working. You’ve got checkboxes triggering sibling styles, or worse, relying on clunky hacks to style parent elements based on child states. Sound familiar? Yeah, me too. That was the norm — until :has() landed with a subtle but serious bang.
For the uninitiated, :has() is a CSS relational pseudo-class selector, and it’s nothing short of revolutionary. Unlike traditional selectors that only look down the DOM tree, :has() lets you select a parent element if it contains a child matching your criteria. Translation: you can style a container based on what’s inside it without touching JavaScript. That’s huge.
Honestly, when I first heard about :has(), I was skeptical. I thought, “Sure, sounds cool, but how often am I really going to ditch JS for interaction?” Spoiler: more than you think.
Why CSS-Only Interactive Components Matter
Remember those days when CSS was purely decorative? Now, we’re pushing its boundaries to deliver smoother, lighter, and more maintainable interfaces. Interactive UI components typically rely on JS for toggling states, managing focus, or controlling visibility. But with :has(), a lot of that complexity evaporates.
It’s not just about cutting down your JavaScript bundle size (although that’s a sweet bonus). It’s about embracing CSS as an actual logic layer — something that feels natural and keeps your stylesheets honest. I find that when you lean into CSS for interactivity, you force yourself to think cleaner, keep components modular, and avoid the “spaghetti event listeners” pitfall.
Plus, accessibility often gets a bump here. When you use semantic HTML and CSS for state changes, screen readers and keyboard users get a more predictable experience. No more wrestling with dynamic ARIA attributes that fall out of sync because of JavaScript bugs.
Real-World Example: A CSS-Only Accordion
Let me walk you through a quick, practical example. Imagine an accordion component — classic UI, but usually JS-laden. Here’s how I’d build it with :has():
<style>.accordion-item { border: 1px solid #ccc; margin-bottom: 0.5rem; border-radius: 4px; overflow: hidden;}.accordion-item <label> { display: block; padding: 1rem; background: #f0f0f0; cursor: pointer; user-select: none;}.accordion-item <input[type="checkbox"]> { display: none;}/* Here’s the magic: style the container when checkbox is checked */.accordion-item:has(input[type='checkbox']:checked) > label { background: #007acc; color: white;}/* Show content only when checked */.accordion-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; padding: 0 1rem; background: white;}.accordion-item:has(input[type='checkbox']:checked) > .accordion-content { max-height: 500px; /* arbitrarily high for demo */ padding: 1rem;}</style>
And the markup:
<div class="accordion-item"> <input type="checkbox" id="acc1"> <label for="acc1">Section 1</label> <div class="accordion-content"> <p>This is the content for section 1.</p> </div></div>
See what’s going on? The container .accordion-item responds to the state of the hidden checkbox inside it. When checked, the label changes color, and the content slides open — all without a single line of JavaScript.
Honestly, this felt like magic the first time I tried it. And it’s surprisingly robust, too. No event listeners to worry about, no state syncing headaches. Just pure CSS doing its thing.
Beyond Accordions: What Else Can :has() Do?
If you’re wondering if this is just a neat party trick for accordions, think again. The :has() selector opens doors to a bunch of interactive patterns:
- Custom toggles and switches: Change parent styling when a checkbox or radio inside is toggled.
- Form validation states: Style form groups based on input validity or focus.
- Menu interactions: Highlight navigation items when their dropdowns are active.
- Tabs: Switch styles on tab containers depending on which tab is selected inside.
One of my favorite use cases was a filter panel that highlighted active filters without any JS. Previously, I had to write messy scripts to track filter states; now, I just use :has() and semantic HTML checkboxes. Cleaner, faster, more maintainable.
Browser Support and What to Watch Out For
Now, before you start refactoring your entire codebase, a quick heads-up: :has() is still relatively new. As of mid-2024, it enjoys solid support in Chrome, Edge, Safari, and Opera. Firefox has been a holdout but recently started rolling out support behind flags or in nightly builds.
So, if you’re targeting a broad audience, especially corporate environments stuck on older browsers, you might want to feature-detect or provide fallbacks. But if you’re building modern web apps or internal tools, don’t hesitate to lean into it.
Also, performance-wise, :has() can be a bit heavier than typical selectors because it involves checking child elements. I’ve noticed it’s negligible on small components, but when used excessively on large DOM trees, it could introduce lag. Just something to keep in your mental toolbox.
Pro Tips for Using :has() Effectively
- Keep your markup semantic:
:has()works best when your HTML structure is clean and meaningful. Avoid nesting elements unnecessarily. - Pair with hidden inputs: Using checkboxes or radios as state toggles inside containers is a solid pattern.
- Test accessibility: Ensure keyboard navigation and screen reader behavior remain consistent. Sometimes CSS tricks can interfere if you’re not careful.
- Use it sparingly: Don’t overuse
:has()across huge sections of the page; target specific components for best results.
Wrapping Up — Why I’m Excited About :has()
After years of juggling CSS limitations and JS dependencies for interactive UIs, :has() feels like a breath of fresh air — a real step toward CSS being a more expressive, practical language. It’s empowering to build cleaner, lighter components that work elegantly and degrade gracefully.
So, what’s your next move? Maybe take a look at a small component in your current project and see if you can refactor it with :has(). Tinker with an accordion, a toggle, or even a simple form state. You might just find yourself reaching for JavaScript a little less often, and your CSS skills sharpening along the way.
Anyway, I’d love to hear how you use :has(), or if you’ve hit any quirks. Drop me a note or share your experiments — there’s a lot to explore here.






