Native CSS nesting removes one common reason to compile Sass: keeping related selectors together without repeating the parent. It is supported in modern browsers and parsed directly by the browser. For straightforward component rules, the result can be pleasantly boring.
But native nesting is not Sass running in the browser. The two systems resolve some selectors differently, and Sass still provides modules, functions, mixins, loops, and compile-time values. A migration should begin with the CSS your project needs, not with a goal to delete a tool at any cost.
The simple cases feel the same
.card {
padding: 1rem;
> h2 {
margin-block: 0;
}
a {
color: var(--link);
}
&:hover {
border-color: var(--accent);
}
}
In native CSS, a nested selector that begins with a type selector can omit &. A leading combinator such as > also reads naturally. Use & when the nested rule modifies or repositions the parent selector, as in &:hover.
Nested media and container queries work too. Declarations inside them stay attached to the current selector, which keeps component changes near their base styles.
.card {
display: grid;
@container (width > 32rem) {
grid-template-columns: 10rem 1fr;
}
@media (prefers-reduced-motion: reduce) {
transition: none;
}
}
Native & is a selector, not text substitution
This is the most useful mental model. Sass can construct selectors during compilation. Native & represents the matched parent selector inside the browser's selector grammar. It does not behave like a string you can edit.
Clear native nesting
.button {
&:hover { ... }
&.is-busy { ... }
.icon { ... }
}
Compiled selector building
.button {
&--primary { ... }
&__icon { ... }
}
The Sass BEM shortcut does not translate
Sass treats &--primary as a suffix and compiles it to .button--primary. Native CSS cannot append text to a class name this way. Write the complete class instead.
.button {
/* Valid native CSS */
&.button--primary {
background: var(--accent);
}
}
/* Often clearer */
.button--primary {
background: var(--accent);
}
That repetition is not a failure. A complete selector is searchable, understandable outside its nesting context, and does not hide the class that actually reaches the HTML.
Selector lists can change specificity
Native nesting behaves like :is() for specificity. The most specific selector in the parent list contributes to every nested result. Sass traditionally expands each selector separately.
.card,
#featured {
.title {
color: green;
}
}
The native equivalent behaves like :is(.card, #featured) .title. Because the list contains an ID, the nested rule gets the ID's specificity even when it matches through .card. A Sass-expanded .card .title branch does not inherit that ID weight.
Avoid mixing selectors with very different specificity in one nested parent list. If you need both, split the rules or reduce the parent weight with :where() when that matches the cascade you intend.
:where(.card, #featured) {
.title {
color: green;
}
}
Check the compiled result, not just the indentation. Two nested sources can look almost identical while producing different selector behavior.
Sass still does much more than nesting
Native nesting can remove Sass only when nesting was the main reason Sass was present. Keep Sass when the codebase depends on features such as:
- modules loaded through
@useand@forward, - mixins that generate repeated declarations or browser-specific variants,
- functions and compile-time color or number calculations,
- maps, loops, and programmatic utility generation,
- selector construction or interpolation.
Some Sass features now have native alternatives. Custom properties handle runtime design tokens. Cascade layers organize precedence. Modern color functions can replace parts of a color utility library. None of that makes a mechanical migration safe.
A safe migration starts with an inventory
- Search for Sass-only syntax. Find variables, mixins, loops, functions, interpolation, placeholder selectors, and
&suffixes. - Inspect selector lists. Flag parent lists that mix IDs, classes, and element selectors.
- Move plain component nesting first. Start with low-risk rules whose output is obvious.
- Compare rendered states. Test hover, focus, invalid, open, disabled, and responsive variations, not just the default screenshot.
- Remove the build step last. A package can remain necessary even after most selectors become native CSS.
/* A low-risk first candidate */
.notice {
padding: 1rem;
strong {
color: var(--accent-deep);
}
&:focus-within {
outline: 2px solid var(--focus);
}
}
Choose the smallest tool that fits the stylesheet
Use native CSS nesting when it makes local component rules easier to scan and your supported browsers can parse it. Keep Sass when its compile-time features are actively helping the project. You can also use both while migrating, as long as your Sass version preserves native CSS semantics in .css files and the team knows which syntax it is reading.
The payoff is not modern syntax by itself. It is less tooling to maintain, selectors that remain predictable, and stylesheets that another developer can change without reverse-engineering the build pipeline.
