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.

CodeNotes
CodeNotes
CodeNotes
16 Practical CSS Tricks Using :has() to Replace JavaScript

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Frontendweb developmentcssselectorCSS tricks:has()
CodeNotes
Written by

CodeNotes

Discuss code and AI, and document daily life and personal growth.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.