Skip to content

What Native CSS Nesting Actually Replaced From Sass

Native CSS nesting covers most Sass nesting patterns, but a few Sass features still don't have a direct CSS equivalent, and that gap matters.

· · 4 min read

Quick Take

I tried removing Sass from a project on the assumption native nesting had made it redundant. Ninety percent of the file converted cleanly. The other ten percent is the part worth knowing about before you try this yourself.

Native CSS nesting shipped broadly enough by 2025 that "do I still need Sass" became a real question instead of a hypothetical one. I ran the actual experiment: took a mid-size component library's Sass files and converted them by hand, tracking exactly what converted cleanly and what didn't.

TL;DR: Native CSS nesting handles selector nesting, the most common Sass usage, cleanly, with one syntax difference: nested type selectors need an explicit & prefix where Sass allows them bare. What native CSS still can't do: Sass mixins (parameterized reusable blocks) and control-flow directives (@each, @for, @if) for generating CSS programmatically. If your Sass usage is mostly nesting, you can likely drop it. If it leans on mixins or loops, you can't yet.

What Converts Cleanly

// Sass
.card {
  padding: 16px;
  border-radius: 8px;

  .title {
    font-size: 1.25rem;
    font-weight: 600;
  }

  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }

  &.featured {
    border: 2px solid #2563eb;
  }
}
/* Native CSS nesting, functionally identical */
.card {
  padding: 16px;
  border-radius: 8px;

  & .title {
    font-size: 1.25rem;
    font-weight: 600;
  }

  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }

  &.featured {
    border: 2px solid #2563eb;
  }
}

Class-to-class nesting, pseudo-classes, and compound selectors (&.featured) all convert directly. The one change: .title needed an explicit & prefix (& .title) in the native version. Sass allows a bare nested selector because Sass resolves nesting entirely at build time, before the browser ever sees it. Native CSS nesting is resolved by the browser parser itself, and an unprefixed type selector nested directly (a { color: blue; } inside .card) risked ambiguity with the CSSOM's parsing rules in early nesting drafts, so the spec settled on requiring & for that specific case to keep the grammar unambiguous.

What Doesn't Convert: Mixins

// Sass mixin, no native CSS equivalent
@mixin truncate($lines: 1) {
  display: -webkit-box;
  -webkit-line-clamp: $lines;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card-title {
  @include truncate(2);
}

.card-description {
  @include truncate(3);
}

There's no native CSS construct that takes a parameter and expands into a reusable block of declarations. The closest native tool, CSS custom properties, lets you parameterize a single value inside a rule, not an entire reusable rule set with multiple properties:

/* Custom properties get partway there, for a single repeated value */
.truncate {
  --lines: 1;
  display: -webkit-box;
  -webkit-line-clamp: var(--lines);
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card-title {
  --lines: 2;
}

This works for the specific case of one varying number, but it's not a general mixin replacement, .truncate still has to be applied as a second class alongside .card-title, rather than the properties being inlined directly into .card-title's own rule the way @include does.

What Doesn't Convert: Loops and Conditionals

// Sass @each generating repetitive utility classes
$spacings: (1: 4px, 2: 8px, 3: 16px, 4: 24px);

@each $key, $value in $spacings {
  .p-#{$key} { padding: $value; }
  .m-#{$key} { margin: $value; }
}

This generates eight CSS rules from four lines of Sass. Native CSS has no loop construct at all, @each, @for, and @if/@else for generating rules are Sass-only, with no browser-native equivalent on the roadmap as of 2026. This kind of systematic class generation either stays in Sass, moves to a utility framework like Tailwind that generates these classes at build time through its own tooling, or gets hand-written if the set is small and stable enough not to need generation.

Old Way vs Modern Way

Sass featureNative CSS equivalentVerdict
Selector nesting& nesting, same syntax mostlyFully replaced
Nested type selectors (a { })Requires explicit & a { }Replaced, minor syntax change
@mixin/@includeNone (custom properties cover single values only)Not replaced
@each/@for/@ifNoneNot replaced
@import partials/modulesNative @import exists but lacks Sass's module scopingPartially replaced
Math operations ($a + $b)calc() covers most casesMostly replaced

A Reasonable Migration Path

Dropping Sass entirely only makes sense if an audit of your actual .scss files shows minimal mixin and loop usage. Grep your codebase for @mixin, @include, @each, @for, and @if before deciding, if those show up in a handful of files, migrate the majority that's pure nesting to native CSS and keep Sass compiling just those few remaining files, rather than an all-or-nothing decision.

Conclusion

Native CSS nesting replaced the Sass feature that shows up in nearly every .scss file, selector nesting, cleanly and with only a minor syntax adjustment for bare type selectors. It didn't touch the two features that are hardest to give up once a codebase depends on them: parameterized mixins and programmatic class generation via loops. Check which category your Sass usage actually falls into before assuming the migration is all-or-nothing.

Frequently Asked Questions

Is native CSS nesting the same syntax as Sass nesting?
Mostly, with one meaningful difference: nesting a plain element selector directly (like a { color: blue; } inside .card) requires the & prefix in native CSS (.card { & a { color: blue; } }) in most cases, where Sass allows it unprefixed. Without the &, browsers can parse a bare nested type selector like a as the start of a new top-level rule in older nesting implementations, so the & prefix keeps the nesting explicit and unambiguous.
Can native CSS nesting replace Sass mixins?
No, and this is the biggest real gap. Sass mixins let you define a reusable block of properties (@mixin, @include) with parameters, something native CSS has no equivalent for as of 2026. CSS custom properties and cascade layers cover some of the same use cases (a set of custom properties acting like configurable variables), but a parameterized, reusable block of arbitrary CSS is still Sass-only.
What about Sass functions and control flow (@if, @each, @for)?
Native CSS has no equivalent for Sass's @if/@else, @each, or @for loops used for generating repetitive CSS at build time (like a loop generating .col-1 through .col-12 utility classes). This is the second real gap alongside mixins. If your Sass usage leans heavily on generating classes programmatically, that logic either stays in Sass or moves to a different code-generation step entirely, native nesting doesn't touch this at all.