16 Practical CSS Tricks Using :has() to Replace JavaScript
The new CSS :has() selector finally lets parents be selected based on child states, eliminating dozens of lines of JavaScript; this article demonstrates sixteen real‑world use cases, explains compatibility across major browsers, and offers performance tips for safe adoption.
1. The Decade‑Long Frontend Problem
For years developers could only select children from a parent and had no way to select a parent based on a child’s state. The arrival of :has() overturns this limitation, giving selectors a true "parent" capability.
2. How to Read :has()
A:has(B) = "A that contains B"
The element that receives the style is the A (parent) , not B. This makes :has() the first genuine parent selector in CSS history.
/* Select label when its child input is checked */
label:has(input:checked) { color: green; }
/* h2 that is immediately followed by a p */
h2:has(+ p) { margin-bottom: 0; }
/* Articles that do not contain an img */
article:not(:has(img)) { padding: 20px; } :has()can be combined with +, ~, and > combinators to express sibling and adjacent relationships.
3. 16 Real‑World Scenarios That Wow
Scenario 1 – Form validation highlight
.field:has(input:invalid) {
border-color: #e34d59;
}
.field:has(input:invalid) .tip {
display: block; /* show error tip */
}Scenario 2 – Adaptive image‑text layout
.post:has(img) { display: grid; grid-template-columns: 1fr 1fr; }
.post:not(:has(img)) { display: block; }Scenario 3 – Dropdown submenu hover
.menu:has(.submenu:hover) {
background: #f5f7fa;
}Scenario 4 – Quantity‑driven style
ul:has(li:nth-child(6)) {
columns: 2; /* six items or more split into two columns */
}Scenario 5 – Lock page scroll when a dialog opens
body:has(dialog[open]) {
overflow: hidden;
}Scenario 6 – Placeholder for unloaded images
figure:has(img:not([src])) {
background: #f5f7fa url('placeholder.svg') center / 48px no-repeat;
}Scenario 7 – Row focus dimming
.row:has(~ .row:hover) {
opacity: .5;
}Scenario 8 – Auto‑mark completed steps
.step:has(~ .step.active) { color: #00a870; }
.step:has(~ .step.active)::before { content: '✓'; }Scenario 9 – Search box focus expansion
.search-box:has(input:focus) {
transform: scale(1.02);
box-shadow: 0 4px 16px rgba(0,0,0,.1);
}Scenario 10 – Empty‑state hint
.list:not(:has(.item)) .empty-tip { display: block; }Scenario 11 – Required field asterisk
.field:has(input:required) label::after {
content: ' *';
color: #e34d59;
}Scenario 12 – Page‑wide dark mode toggle
body:has(#dark-toggle:checked) {
background: #1a1a1a;
color: #eee;
}Scenario 13 – Table header highlight when all rows selected
.table:not(:has(tbody input:not(:checked))) thead {
background: #f0f6ff;
}Note: This also matches an empty table. Add .table:has(tbody input):not(:has(tbody input:not(:checked))) thead to require at least one row.
Scenario 14 – Card highlight when its radio is checked
.plan-card:has(input:checked) {
border-color: #0052d9;
background: #f0f6ff;
}Scenario 15 – Fallback for broken images
figure:has(img.broken) {
background: #f5f7fa url('fallback.svg') center / 64px no-repeat;
}Scenario 16 – Compress spacing between adjacent headings
h2:has(+ h3) {
margin-bottom: 4px;
}4. Two Pitfall Warnings
1. Compatibility is solid. As of 2026, Chrome, Safari, Firefox, and Edge all support :has(), covering over 93% of browsers. Use @supports selector(:has(*)) for graceful degradation.
@supports selector(:has(*)) {
.card:has(img) { border: 2px solid #0052d9; }
}2. Avoid overusing it. :has() has higher matching cost than simple selectors, especially with a universal selector like *:has(...). Scope it to a specific parent class to keep performance friendly.
/* ❌ Bad – traverses all elements */
*:has(> img) { ... }
/* ✅ Good – limited scope */
.gallery:has(> img) { ... }5. Final Thoughts
The significance of :has() goes beyond saving a few lines of code; it brings state‑driven interactions back into CSS, making them more declarative, maintainable, and often faster.
One‑line summary: Whenever a child’s state needs to affect its parent or siblings, consider using :has() first – you’ll find many JavaScript snippets become unnecessary.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
