Parent Selectors in CSS and Sass

A long‑standing gap in CSS has been selecting a parent because of the children it contains. That is a different concern from how we name and organise classes: BEM can keep selectors predictable and specificity easier to manage, but a large performance advantage over nested selectors needs measurement in the actual page.
There is potential in the Selectors Level 4 draft with the (potential) introduction of :has() pseudo‑class, which would allow you to target elements that have specific children. For example:
a:has(> img) {
/* This would match an anchor with a direct child image */
}
li:has(a.current) {
/* This would match an li element where it contained an
* anchor with the class name 'current' */
}
section:not(:has(h1, h2, h3, h4, h5, h6)) {
/* This would match a section element where it does not
* contain any heading (h1 - h6) */
}This is very exciting, but at the time of writing, not a single browser supports it, so we have to be smart about our CSS, or simply use additional classNames to bridge the gap.
Fortunately, Sass does offer a partial solution in their own parent selector: &.
This special character essentially maps back to the immediate parent selector, allowing you to build up nested selectors like this:
// Sass:
.element {
color: lime;
&:hover {
color: purple;
img {
border: 1rem solid yellow;
}
}
& > a {
outline: 0.1rem solid navy;
}
}
// Generated CSS:
.element {
color: lime;
}
.element:hover {
color: purple;
}
.element:hover img {
border: 1rem solid yellow;
}
.element > a {
outline: 0.1rem solid navy;
}In this way you can still target elements based on their parent, by generating accurate nested selectors. It's not ideal, but it is very handy!
The Sass parent selector is also extremely useful for building up BEM‑type selectors:
// Sass:
.block {
color: lime;
&__element {
color: purple;
&--modifier {
color: navy;
}
}
}
// Generated CSS:
.block {
color: lime;
}
.block__element {
color: purple;
}
.block__element--modifier {
color: navy;
}Of course, the more recent significant adoption of CSS‑inside‑JavaScript, combined with JSX‑type syntax that allows you to change styles on the fly may well eventually make this obsolete without ever needing the 4th level of CSS selectors...
Postscript
December 2023: :has() became available across current major browsers. That changes the support picture described in the original September 2020 article. Sass & is still a compile‑time authoring tool: it expands the current nesting context, whereas :has() lets the browser match an element based on related elements. For a modern support matrix, :has() can be useful for form states and component variants; keep the selectors readable and check the browsers the project needs to support.