Parent Selectors in CSS and Sass

Hero image for Parent Selectors in CSS and Sass. Image by Sergey Zolkin.
Hero image for 'Parent Selectors in CSS and Sass.' Image by Sergey Zolkin.

A longstanding weakness with CSS has been the omission of a parenttype selector. This all boils down to the fact that CSS rules are conventionally matched by the browser from righttoleft, and concomitantly the primary reason that using a flat, nonnested method like BEM is far more performant than a nestedselectors approach.

There is potential in the Selectors Level 4 draft with the (potential) introduction of :has() pseudoclass, 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 BEMtype 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 CSSinsideJavaScript, combined with JSXtype 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...


Sass Parent Selectors and CSS Parent Matching are Different

The Sass & selector is a compiletime convenience. It helps generate selectors based on the current nesting context, but it does not let the browser select a parent based on its children. CSS :has() is the feature that changed that part of the platform.

That distinction matters because Sass can make selector authoring tidier, whilst :has() changes what the browser can match at runtime. They solve relatedlooking problems at different stages of the stack.


A Current Note on :has()

:has() is now a practical option in modern browser support matrices, but it should still be used deliberately. Parent matching can make styles more expressive, especially for form states and component variants, but the selector still needs to stay readable.


Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.