Kilian Valkhof

Building tools that make developers awesome.

Progressive enhancement as a benefit of CSS Nesting

Accessibility, CSS & HTML, 6 March 2026, 3 minute read

Something I knew is that in browser that support CSS nesting you can nest @media queries inside of other CSS declarations. What I only fully grasped a few days ago is how well this works for progressive enhancement.

I’ve happily used (and wrote about) CSS and @media nesting for about six years now. Primarily through PostCSS, where it gets compiled away, and directly for the styling in Polypane where I know the exact browser engine. I have also had to implement my own parser for it in early 2023, when I implemented support for it in Polypane’s Elements panel (one of the first browsers that supported it).

So I’m far from a stranger to the workings of native CSS Nesting.

Progressive enhancement in context #

Progressive enhancement is a way of setting up your code such that you provide a well-supported basis that works across all the browsers you target, and then you add functionality when support is better.

In the context of accessibility, we often say that you should build the accessible variant as the default (for example, no animations) and only when the user indicates they don’t need the accessible variant (for example, no-preference for reduced motion) then you add features on top of that accessible default (by adding animations).

This makes the accessible version the one that gets used when anything goes wrong, and if everything goes right and people want a little extra, they can get that without hampering the experience for others.

The issues that brings #

Unfortunately in ‘traditional’ CSS this often ends up with having styles of an element living in two different places (one inside and one outside a media query).

.my-element {
  width: min(1000px, -1rem + 100vw);
  max-height: calc(-2rem + 100svh);
  /* ...and a bunch more */
  scale: 0.5;
  opacity: 0;
}

/* potentially lots of other CSS until 
   you get to the "accessibility block" */

@media (prefers-reduced-motion: no-preference) {
  .my-element {
    transition: 0.5s ease-in-out all;
    /* maybe some additional styling to make this work well */
  }
}

That causes quite a few potential complications, especially for the long-term maintenance of code:

Because there is nothing that keeps track of the inherent link between the two rulesets, it’s one of those “great in theory, slightly more complicated in practice” situations.

Progressive enhancement with CSS Nesting #

A couple of days ago I nested an @media query, something I must’ve done hundreds of times over the course of all those years. But in this case, here’s what I did:

.my-element {
  width: min(1000px, -1rem + 100vw);
  max-height: calc(-2rem + 100svh);
  /* ...and a bunch more */
  scale: 0.5;
  opacity: 0;
  
  @media (prefers-reduced-motion: no-preference) {
    transition: 0.5s ease-in-out all;
  }
  
  &:open {
    opacity: 1;
    scale: 1;
  }
}

Nothing out of the ordinary, but when looking at the ruleset I realized that this is now a fully self-contained, progressively enhanced CSS style!

The transition only gets applied for users with no-preference for motion. The logic for the progressive enhancement is co-located with the rest of the styles, and should I ever remove this component, I won’t need to hunt down a second place where I use the same selector.

Obvious? #

After writing it down it seemed really obvious, but that wasn’t the case for me before I had written it, and neither was it to folks on Bluesky, who seemed to enjoy my short post on it. So I figured it was worth a quick article on my blog as well.

If it was obvious to you and you had been doing it for a long time: that’s great! If this made you go “huh, yeah” then I hope it makes your progressive enhancements a little bit easier going forward.

Polypane browser for responsive web development and design Hi, I'm Kilian. I make Polypane, the browser for responsive web development and design. If you're reading this site, that's probably interesting to you. Try it out!