Moving between plain CSS, SCSS, and native CSS nesting without breaking production for a week. Migrations work best in small slices with a clear direction, not a single ‘rewrite all styles’ PR.
CSS to SCSS (legacy rescue)
Rename styles.css to styles.scss, split obvious sections into partials, and introduce tokens for repeated values. Do not refactor everything on day one.
// Step 1: rename and compile - behaviour unchanged
// styles.scss (formerly styles.css) - valid SCSS is valid CSS
// Step 2: extract repeated values
$color-primary: #0066cc; // was copy-pasted 14 times
// Step 3: partials
@use 'base';
@use 'components/button';
The intermediate mini project covers a fuller refactor. Here the point is incremental extraction: tokens first, then partials, then mixins only where duplication hurts.
SCSS to plain CSS (step down)
Start with files that only use nesting and variables. Replace Sass variables with custom properties, rename .scss to .css, enable native nesting (or PostCSS nesting), remove the Sass compile step for those files.
// Before - SCSS
$space-md: 1rem;
.card {
padding: $space-md;
&__title { font-weight: 600; }
}
/* After - native CSS */
:root {
--space-md: 1rem;
}
.card {
padding: var(--space-md);
&__title { font-weight: 600; }
}
Leave map-heavy token generation in SCSS until you have a JSON or build script replacement. Migrate leaf files first; keep one SCSS entry for generated tokens until the last consumer moves.
Native nesting vs Sass nesting
Syntax is similar but not identical. Sass allows more selector interpolation and parent-reference tricks. Native nesting uses & with stricter rules in some engines.
// Sass - parent selector suffix
&__item { /* .block__item */ }
&--large { /* .block--large */ }
// Native CSS - same pattern, check browser targets
&:not(:first-child) { margin-top: 1rem; }
Run a dual-compile period if needed: Sass output and native CSS output compared with diff or visual regression until they match.
Strangler pattern for large codebases
- Freeze new SCSS in legacy areas; new features use CSS modules or plain CSS
- Extract tokens to custom properties consumed by both SCSS and CSS files
- Convert one route or component bundle at a time
- Delete Sass entry points when nothing imports them
Track progress with a simple inventory: which pages still import compiled SCSS bundles vs CSS-only bundles. Shrink the SCSS list over quarters, not days.
@import to @use (Sass modernisation)
If you are staying on Sass but modernising, migrate @import to @use before larger migrations. The Sass migrator helps:
npm install -g sass-migrator
sass-migrator module --migrate-deps scss/main.scss
Review the diff. Namespaces change; globals disappear. Fix broken references in one PR, run visual regression, merge.
Testing migrations
- Visual regression on key templates before and after
- Compare compiled CSS size – large swings mean missed rules
- Search for orphaned class names in HTML after renames
Try it
- Convert one partial from Sass variables to custom properties; compile and verify output.
- Run
sass-migratoron a branch and note three breaking changes it introduces. - Write a migration order for your project: which files move first and why.

