Select parent elements based on their children — the “parent selector” CSS never had. Replaces JS DOM queries and class toggling for validation, layout, and state propagation.
:has() selects an element if any of the relative selectors
passed as arguments match. For example, form:has(:invalid) selects a <form>
that contains any invalid input — no JS querySelectorAll needed. This replaces
Bootstrap’s .was-validated JS class toggling pattern.
form:has(:invalid)The form border turns red and a warning appears when any field is invalid. Clear the required field or enter a valid email to see it turn green. Zero JS — replaces Bootstrap’s .was-validated class.
.card:has(img)Cards that contain an image automatically span 2 grid columns. No JS layout detection — pure CSS selector.
A simple text card. Takes 1 column.
This card has an <img>, so :has(img) makes it span 2 columns.
No image here. Single column.
.nav:has(.nav-link.active)Click a nav link to make it active. The nav container gets a bottom border highlight via :has(.nav-link.active). The parent reacts to child state — no JS parent manipulation.
Try removing all active states — the bottom highlight disappears because :has(.nav-link.active) no longer matches.
h2:has(+ h3)An <h2> followed immediately by an <h3> gets reduced margin and turns blue. The heading “knows” its next sibling via :has(+ h3). No JS DOM walking.
The h2 above has reduced margin and primary color because h2:has(+ h3) matches.
This h2 has no h3 sibling, so normal styling applies.
Same reduced margin applied automatically.
/* Form validation — replaces .was-validated JS */
form:has(:invalid) {
border-color: var(--bs-danger);
}
form:has(:invalid) .warning { display: block; }
/* Layout adaptation — replaces JS layout detection */
.card:has(img) {
grid-column: span 2;
}
/* Parent state from child — replaces JS parent manipulation */
.nav:has(.nav-link.active) {
box-shadow: 0 2px 0 0 var(--bs-primary);
}
/* Sibling awareness — replaces JS DOM walking */
h2:has(+ h3) {
margin-bottom: 0.25rem;
color: var(--bs-primary);
}