SASS/SCSS

Migrating CSS to SASS & Legacy Code

20 min Lesson 28 of 30

Introduction to CSS to SASS Migration

Migrating an existing CSS codebase to SASS can seem daunting, but with a systematic approach, it becomes manageable and rewarding. In this comprehensive lesson, we'll explore proven strategies for migrating legacy CSS to modern SASS, handling vendor prefixes, upgrading from @import to @use/@forward, and avoiding common pitfalls during the migration process.

Tip: The beauty of SASS is that all valid CSS is valid SCSS. This means you can start using SASS immediately without rewriting everything at once.

Strategy for Migrating Existing CSS to SASS

A successful migration requires planning and a step-by-step approach. Rushing the process often leads to mistakes and maintenance issues.

Assessment Phase

Before starting, assess your current CSS:

Pre-Migration Checklist

□ Document current CSS structure
  □ How many CSS files exist?
  □ What is the total file size?
  □ Are there duplicate styles?
  □ Is there a naming convention?

□ Identify pain points
  □ Which styles are repeated?
  □ Which values are hardcoded?
  □ Which selectors are overly specific?
  □ Are there vendor prefixes everywhere?

□ Set migration goals
  □ Reduce code duplication
  □ Improve maintainability
  □ Establish consistent naming
  □ Create reusable components

□ Choose your approach
  □ Big bang (migrate all at once)
  □ Gradual (migrate file by file)
  □ Hybrid (migrate critical files first)
Note: For large projects, the gradual approach is recommended. It allows you to migrate while maintaining a working application and learn as you go.

Step 1: Rename .css to .scss

The first step is the easiest: simply rename your CSS files to SCSS. All valid CSS is valid SCSS, so your styles will continue to work immediately.

File Renaming

# Before
styles/
  main.css
  components.css
  utilities.css

# After (Step 1)
styles/
  main.scss
  components.scss
  utilities.scss

Update Build Process

After renaming, update your build tools to compile SCSS instead of CSS:

package.json - Update Build Scripts

{
  "scripts": {
    "build:css": "sass styles/main.scss dist/main.css --style=compressed",
    "watch:css": "sass --watch styles:dist"
  }
}

Test Everything

After renaming and updating your build process, verify that:

  • All styles compile without errors
  • The output CSS is identical to the original
  • The website looks exactly the same
  • All pages and components render correctly

Step 2: Extract Variables

The second step is to identify repeated values and extract them into variables. This is where you start seeing the benefits of SASS.

Identifying Candidates for Variables

Before - Hardcoded Values

/* Original CSS */
.header {
  background-color: #3498db;
  padding: 20px;
  font-family: "Helvetica Neue", Arial, sans-serif;
}

.button-primary {
  background-color: #3498db;
  padding: 10px 20px;
  font-family: "Helvetica Neue", Arial, sans-serif;
  border-radius: 4px;
}

.card {
  background-color: #ffffff;
  padding: 20px;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.sidebar {
  background-color: #f8f9fa;
  padding: 20px;
}

Creating a Variables File

_variables.scss - Extracted Variables

// Colors
$color-primary: #3498db;
$color-white: #ffffff;
$color-gray-light: #f8f9fa;
$color-shadow: rgba(0, 0, 0, 0.1);

// Typography
$font-family-base: "Helvetica Neue", Arial, sans-serif;

// Spacing
$spacing-base: 20px;
$spacing-small: 10px;

// Border
$border-radius-base: 4px;

// Shadows
$shadow-base: 0 2px 4px $color-shadow;

Replacing Hardcoded Values

After - Using Variables

// Import variables
@use 'variables' as *;

.header {
  background-color: $color-primary;
  padding: $spacing-base;
  font-family: $font-family-base;
}

.button-primary {
  background-color: $color-primary;
  padding: $spacing-small $spacing-base;
  font-family: $font-family-base;
  border-radius: $border-radius-base;
}

.card {
  background-color: $color-white;
  padding: $spacing-base;
  border-radius: $border-radius-base;
  box-shadow: $shadow-base;
}

.sidebar {
  background-color: $color-gray-light;
  padding: $spacing-base;
}
Tip: Use find-and-replace in your editor to quickly replace repeated values with variables. Most editors support regex find-and-replace for complex patterns.

Step 3: Introduce Nesting Where Appropriate

Nesting is powerful but should be used judiciously. Introduce it where it improves readability without creating overly specific selectors.

Good Candidates for Nesting

Before - Flat CSS

/* Original CSS */
.nav { }
.nav .nav-list { }
.nav .nav-item { }
.nav .nav-link { }
.nav .nav-link:hover { }
.nav .nav-link.active { }

.card { }
.card .card-header { }
.card .card-body { }
.card .card-footer { }

After - Strategic Nesting

// Good nesting depth
.nav {
  // Nav styles

  .nav-list {
    // List styles
  }

  .nav-item {
    // Item styles
  }

  .nav-link {
    // Link base styles

    &:hover {
      // Hover state
    }

    &.active {
      // Active state
    }
  }
}

.card {
  // Card base styles

  .card-header {
    // Header styles
  }

  .card-body {
    // Body styles
  }

  .card-footer {
    // Footer styles
  }
}
Warning: Don't nest just because you can. Only nest when it improves code organization and doesn't create overly specific selectors (keep depth to 3-4 levels maximum).

Step 4: Create Partials and Split the File

Large monolithic CSS files are hard to maintain. Split your SCSS into logical partials based on functionality.

Planning Your Partial Structure

Partial Organization Strategy

styles/
├── main.scss              # Main entry file
├── abstracts/
│   ├── _variables.scss    # All variables
│   ├── _mixins.scss       # All mixins
│   └── _functions.scss    # All functions
├── base/
│   ├── _reset.scss        # CSS reset
│   ├── _typography.scss   # Typography rules
│   └── _global.scss       # Global styles
├── layout/
│   ├── _header.scss       # Header styles
│   ├── _footer.scss       # Footer styles
│   ├── _sidebar.scss      # Sidebar styles
│   └── _grid.scss         # Grid system
├── components/
│   ├── _buttons.scss      # Button styles
│   ├── _cards.scss        # Card styles
│   ├── _forms.scss        # Form styles
│   ├── _modals.scss       # Modal styles
│   └── _navigation.scss   # Navigation styles
└── pages/
    ├── _home.scss         # Home page specific
    ├── _about.scss        # About page specific
    └── _contact.scss      # Contact page specific

Splitting Your Monolithic File

Process for Splitting

// 1. Identify logical sections in your CSS
/* Navigation styles - lines 1-150 */
/* Button styles - lines 151-250 */
/* Form styles - lines 251-400 */
/* Card styles - lines 401-500 */

// 2. Create partial files
components/_navigation.scss
components/_buttons.scss
components/_forms.scss
components/_cards.scss

// 3. Copy styles to appropriate partials
// (Keep original file as backup until migration is complete)

// 4. Create main.scss to import all partials
@use 'abstracts/variables';
@use 'abstracts/mixins';
@use 'base/reset';
@use 'base/typography';
@use 'layout/header';
@use 'layout/footer';
@use 'components/navigation';
@use 'components/buttons';
@use 'components/forms';
@use 'components/cards';

// 5. Compile and test
// 6. Compare output with original CSS
// 7. Delete original monolithic file when confident

Step 5: Introduce Mixins for Repeated Patterns

Look for repeated style patterns and extract them into reusable mixins.

Identifying Repeated Patterns

Before - Repeated Code

/* Original CSS with repeated patterns */
.box-1 {
  display: flex;
  justify-content: center;
  align-items: center;
}

.box-2 {
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-content {
  display: flex;
  justify-content: center;
  align-items: center;
}

.button-primary {
  padding: 10px 20px;
  border-radius: 4px;
  border: none;
  cursor: pointer;
  transition: all 0.3s ease;
}

.button-secondary {
  padding: 10px 20px;
  border-radius: 4px;
  border: none;
  cursor: pointer;
  transition: all 0.3s ease;
}

Creating Mixins

abstracts/_mixins.scss - Extracted Mixins

// Center content with flexbox
@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

// Button base styles
@mixin button-base {
  padding: 10px 20px;
  border-radius: 4px;
  border: none;
  cursor: pointer;
  transition: all 0.3s ease;
}

// Responsive breakpoint
@mixin respond-to($breakpoint) {
  @if $breakpoint == tablet {
    @media (min-width: 768px) {
      @content;
    }
  } @else if $breakpoint == desktop {
    @media (min-width: 1024px) {
      @content;
    }
  }
}

Using Mixins

After - Using Mixins

@use 'abstracts/mixins' as *;
@use 'abstracts/variables' as *;

.box-1 {
  @include flex-center;
}

.box-2 {
  @include flex-center;
}

.modal-content {
  @include flex-center;
}

.button-primary {
  @include button-base;
  background-color: $color-primary;
  color: $color-white;
}

.button-secondary {
  @include button-base;
  background-color: $color-secondary;
  color: $color-text;
}

.responsive-box {
  width: 100%;

  @include respond-to(tablet) {
    width: 50%;
  }

  @include respond-to(desktop) {
    width: 33.333%;
  }
}

Step 6: Add @use/@forward Module System

If your project still uses @import, migrate to the modern @use and @forward system.

Migrating from @import to @use

Before - Using @import

// main.scss (old style)
@import 'variables';
@import 'mixins';
@import 'base/reset';
@import 'components/buttons';

// All variables and mixins are global
.box {
  background: $primary-color; // Works because @import is global
}

After - Using @use

// main.scss (new style)
@use 'variables' as *;      // Import all without namespace
@use 'mixins' as *;
@use 'base/reset';
@use 'components/buttons';

// Or use with namespace
@use 'variables' as vars;
@use 'mixins' as mix;

.box {
  background: vars.$primary-color;
  @include mix.flex-center;
}

Using @forward to Re-export

abstracts/_index.scss - Forwarding Module

// Forward all abstract partials through a single entry point
@forward 'variables';
@forward 'mixins';
@forward 'functions';

// Now you can import all abstracts at once
// In other files:
@use 'abstracts' as *; // Gets variables, mixins, and functions

Dealing with Vendor Prefixes

Modern build tools handle vendor prefixes automatically, so you should remove them from your SASS and let tools like Autoprefixer handle them.

Before Migration

CSS with Manual Prefixes

/* Old CSS with vendor prefixes */
.box {
  -webkit-transform: rotate(45deg);
  -moz-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  -o-transform: rotate(45deg);
  transform: rotate(45deg);

  -webkit-transition: all 0.3s ease;
  -moz-transition: all 0.3s ease;
  -ms-transition: all 0.3s ease;
  -o-transition: all 0.3s ease;
  transition: all 0.3s ease;
}

After Migration

SASS without Prefixes (Autoprefixer handles it)

// Clean SCSS - let Autoprefixer add prefixes
.box {
  transform: rotate(45deg);
  transition: all 0.3s ease;
}

// Compiles to (with Autoprefixer):
// .box {
//   -webkit-transform: rotate(45deg);
//   transform: rotate(45deg);
//   -webkit-transition: all 0.3s ease;
//   transition: all 0.3s ease;
// }

Configuring Autoprefixer

postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')({
      overrideBrowserslist: [
        'last 2 versions',
        '> 1%',
        'not dead'
      ]
    })
  ]
};

Common Pitfalls During Migration

Be aware of these common mistakes when migrating to SASS.

Pitfall 1: Excessive Nesting

Problem and Solution

// Bad: Too much nesting
.header {
  .nav {
    .menu {
      .item {
        .link {
          color: blue; // 5 levels deep!
        }
      }
    }
  }
}

// Good: Flatten structure
.header { }
.nav { }
.menu-item { }
.menu-link {
  color: blue;
}

Pitfall 2: Not Using Variables Consistently

Problem and Solution

// Bad: Inconsistent variable usage
.button {
  background: $primary-color;
  color: #ffffff;           // Hardcoded!
  padding: 10px 20px;       // Hardcoded!
  border-radius: $border-radius;
}

// Good: Use variables everywhere
.button {
  background: $primary-color;
  color: $color-white;
  padding: $spacing-sm $spacing-base;
  border-radius: $border-radius;
}

Pitfall 3: Creating Too Many Variables

Problem and Solution

// Bad: Variable for everything
$header-padding-top: 20px;
$header-padding-right: 15px;
$header-padding-bottom: 20px;
$header-padding-left: 15px;
$button-padding-top: 10px;
$button-padding-right: 20px;
// ... 50 more padding variables

// Good: Use meaningful scale
$spacing-xs: 5px;
$spacing-sm: 10px;
$spacing-md: 15px;
$spacing-lg: 20px;
$spacing-xl: 30px;

.header {
  padding: $spacing-lg $spacing-md;
}

.button {
  padding: $spacing-sm $spacing-lg;
}

Pitfall 4: Not Testing After Each Step

Warning: Always test after each migration step. Don't rename files, extract variables, add nesting, and create mixins all at once. Make incremental changes and verify the output matches the original.

Recommended Testing Process

# After each migration step:

1. Compile SCSS to CSS
   npm run build:css

2. Compare output with original CSS
   diff original.css dist/output.css

3. Visual regression testing
   - Open website in browser
   - Check all pages and components
   - Verify responsive behavior
   - Test interactive elements

4. Automated testing (if available)
   npm run test

5. Only proceed to next step when current step is verified

Migration Timeline Example

6-Week Migration Plan for Medium Project

Week 1: Assessment & Setup
- Audit existing CSS
- Set up SASS build process
- Rename .css to .scss
- Test compilation

Week 2: Extract Variables
- Create _variables.scss
- Replace colors
- Replace spacing values
- Replace typography values

Week 3: Introduce Nesting
- Identify components for nesting
- Apply strategic nesting
- Test and verify output

Week 4: Create Partials
- Plan file structure
- Split monolithic files
- Create @use imports
- Test all pages

Week 5: Add Mixins & Functions
- Identify repeated patterns
- Create mixins
- Create utility functions
- Refactor to use mixins

Week 6: Polish & Optimize
- Remove vendor prefixes
- Set up Autoprefixer
- Optimize output
- Final testing
- Documentation

Exercise 1: Migrate a Simple CSS File

Take this CSS and migrate it through all steps:

/* styles.css */
.header {
  background-color: #3498db;
  padding: 20px;
  font-family: Arial, sans-serif;
}

.header .logo {
  color: #ffffff;
  font-size: 24px;
}

.header .nav {
  display: flex;
}

.header .nav .nav-item {
  padding: 10px 15px;
  color: #ffffff;
}

.button {
  background-color: #3498db;
  color: #ffffff;
  padding: 10px 20px;
  border-radius: 4px;
  border: none;
}

.button:hover {
  background-color: #2980b9;
}

Complete steps 1-5: Rename to .scss, extract variables, add nesting, create partials, and introduce mixins.

Exercise 2: Refactor Vendor Prefixes

Remove vendor prefixes and set up Autoprefixer:

.animated-box {
  -webkit-transform: translateX(100px) rotate(45deg);
  -moz-transform: translateX(100px) rotate(45deg);
  -ms-transform: translateX(100px) rotate(45deg);
  transform: translateX(100px) rotate(45deg);

  -webkit-animation: slide 1s ease-in-out;
  -moz-animation: slide 1s ease-in-out;
  animation: slide 1s ease-in-out;
}

@-webkit-keyframes slide { }
@-moz-keyframes slide { }
@keyframes slide { }

Remove all vendor prefixes, set up PostCSS with Autoprefixer, and verify the output includes correct prefixes.

Exercise 3: Migrate @import to @use

Convert this project structure from @import to @use/@forward:

// main.scss
@import 'variables';
@import 'mixins';
@import 'base';
@import 'components/buttons';
@import 'components/forms';

// Other files also use @import
// Components need access to variables and mixins

Convert all @import statements to @use, create an index file with @forward for abstracts, and update all component files to use the module system.