There's a project on every developer's machine that has Sass installed for one reason: &:hover {}. Not @mixin. Not @each. Just the nesting. The variables long since became --custom-properties. The only thing still justifying node_modules/sass is the ability to write child selectors inside parent rules.
CSS added that natively in 2023. It shipped in Chrome 112, Firefox 117, and Safari 16.5 — every major browser released in the last two years. The compiler is not earning its spot anymore.
What you've been writing in Sass
The classic pattern — component styles scoped to a block, with states and modifiers nested inside:
.card {
padding: 1.5rem;
border-radius: 0.5rem;
background: var(--surface);
&:hover {
background: var(--surface-hover);
}
&__title {
font-size: 1.125rem;
font-weight: 600;
}
&--featured {
border: 2px solid var(--accent);
}
}
The output is flat, specificity-controlled CSS. The source is organized by component. That's the trade Sass nesting has always offered — and native CSS now offers the same deal.
The same thing in native CSS
.card {
padding: 1.5rem;
border-radius: 0.5rem;
background: var(--surface);
&:hover {
background: var(--surface-hover);
}
& .card__title {
font-size: 1.125rem;
font-weight: 600;
}
&.card--featured {
border: 2px solid var(--accent);
}
}
Two differences are worth noticing. First: pseudo-classes work exactly as in Sass — &:hover resolves to .card:hover with no extra syntax. Second: descendant selectors require an explicit & followed by a space. & .card__title becomes .card .card__title. This is where native nesting differs from BEM's __/-- convention: in native CSS, & is a selector reference, not a string concatenation operator.
If you're using BEM naming heavily, &__foo becomes & .block__foo. The compiled output is identical; the source is slightly more explicit about what's happening.
Media queries nested inside their rules
This is the feature that earns native nesting a permanent spot in how I write stylesheets.
Before, responsive CSS meant jumping between two locations in the same file — the base rule near the top and the @media block somewhere below it, often hundreds of lines away:
.hero {
font-size: 1.5rem;
padding: 2rem;
}
/* ... 200 lines later ... */
@media (min-width: 768px) {
.hero {
font-size: 2.5rem;
padding: 4rem;
}
}
With native nesting, the responsive rules live with the component they modify:
.hero {
font-size: 1.5rem;
padding: 2rem;
@media (min-width: 768px) {
font-size: 2.5rem;
padding: 4rem;
}
}
Reading an unfamiliar stylesheet gets faster. Instead of tracing a component across multiple @media blocks, all its states are in one place. You see what it looks like at every breakpoint without scrolling past unrelated rules.
Sass could nest @media queries this way too — but only by compiling them back out to separate blocks. Native CSS parses nested @media directly. No output transformation, no source map needed to trace back.
The one gotcha — & is not a string
In Sass, & is a string that gets concatenated. &__child produces .card__child. That's why BEM's double-underscore syntax worked so cleanly: the ampersand literally builds the class name character by character.
In native CSS, & is a selector. It means "the selector that contains this rule," and the browser uses it for matching, not text-building. So &__child doesn't produce .card__child — the parser sees it as & (a valid selector ref) followed by __child (an unknown pseudo-class-like token) and ignores the rule silently.
The fix is mechanical: everywhere you have &__element, write & .block__element. Everywhere you have &--modifier, write &.block--modifier (no space, modifier is on the same element). The specificity and the rendered output are identical. The migration is a find-and-replace once you know the pattern to look for.
Browser support — the honest picture
Chrome 112 (April 2023), Firefox 117 (August 2023), Safari 16.5 (May 2023). That's Baseline 2023: cross-browser in any browser released in the past two years.
For projects that still need to cover older browsers, native nesting degrades safely — unknown nested rules are silently skipped, so the outer rule's styles still apply:
/* Both browsers parse the outer rule */
.card {
padding: 1.5rem;
/* Older browsers skip this; newer ones apply it */
&:hover {
background: var(--surface-hover);
}
}
The progressive-enhancement story is the same one container queries, scroll-driven animations, and @layer all tell: declare it, get the enhancement in modern browsers, get the safe fallback in older ones.
What to do next
Open the project with Sass installed. Check whether you use anything beyond nesting: @mixin, @each, @function, @use. If the answer is no — just nesting — you have a straightforward migration. Rename .scss files to .css, change &__child patterns to & .child, remove the build step.
The loop is usually shorter than expected. Two file renames, a find-and-replace, one fewer devDependency. The styles work the same way; the pipeline gets simpler.
What Sass feature are you genuinely still relying on? Because the list gets shorter every year. Nesting was the last one most projects could point to — and it just moved to the platform.
Thanks for reading! Let's stay connected:
- ⭐ GitHub — follow me and star the projects: github.com/parsajiravand
- 📸 Instagram — frontend best practices, daily: @bestpractice___
- 💼 LinkedIn — linkedin.com/in/parsa-jiravand
- ✉️ Email (work & contract inquiries): bestpractice2026@gmail.com
Top comments (1)
Native CSS nesting is a game changer, but I'm curious how it handles complex selectors like &:hover. Will this finally make me ditch Sass? Would love to hear your thoughts on this.