SASS/SCSS

SASS vs Modern CSS: When to Use What

20 min Lesson 30 of 30

Introduction: The Evolving CSS Landscape

CSS has evolved dramatically in recent years, incorporating many features that were once exclusive to preprocessors like SASS. Native CSS now supports custom properties (variables), nesting, color manipulation functions, and more. This raises an important question: when should you use SASS, and when is vanilla CSS sufficient? In this comprehensive final lesson, we'll compare modern CSS features with SASS capabilities, explore their strengths and limitations, and help you make informed decisions for your projects.

Note: The goal isn't to declare a "winner" but to understand the strengths of each approach so you can choose the right tool for each situation.

CSS Custom Properties vs SASS Variables

Both CSS custom properties and SASS variables store reusable values, but they work in fundamentally different ways.

SASS Variables

SASS Variables (Compile-Time)

// Defined at compile time
$primary-color: #3498db;
$spacing-base: 16px;

.button {
  background: $primary-color;
  padding: $spacing-base;
}

// After compilation:
// .button {
//   background: #3498db;
//   padding: 16px;
// }

// Can be used in calculations
$container-width: 1200px;
$column-count: 12;
$column-width: $container-width / $column-count; // 100px

// Scoped to blocks
.component {
  $local-color: red; // Only available inside .component
  color: $local-color;
}

CSS Custom Properties

CSS Custom Properties (Runtime)

/* Defined in CSS and available at runtime */
:root {
  --primary-color: #3498db;
  --spacing-base: 16px;
}

.button {
  background: var(--primary-color);
  padding: var(--spacing-base);
}

/* Can be changed with JavaScript */
document.documentElement.style.setProperty('--primary-color', '#e74c3c');

/* Can be scoped to elements */
.dark-theme {
  --primary-color: #1a1a1a;
  --text-color: #ffffff;
}

/* Can have fallback values */
.box {
  color: var(--text-color, black); /* Falls back to black */
}

/* Inherit through the cascade */
.parent {
  --spacing: 20px;
}

.child {
  margin: var(--spacing); /* Inherits from parent */
}

Comparison

When to Use Each

// Use SASS variables when:
// ✓ Value is computed at build time (calculations, color functions)
// ✓ You need type checking and validation
// ✓ The value never changes at runtime
// ✓ You're building complex functions or loops with the values

$grid-columns: 12;
$gutter: 30px;
$column-width: calc(100% / $grid-columns - $gutter);

// Use CSS custom properties when:
// ✓ Value may change at runtime (themes, user preferences)
// ✓ You need JavaScript interaction
// ✓ You want values to inherit through the DOM
// ✓ You need responsive values that change per breakpoint

:root {
  --theme-primary: #3498db;
}

@media (prefers-color-scheme: dark) {
  :root {
    --theme-primary: #1a1a1a;
  }
}
Tip: The best approach is often a hybrid: use SASS variables to define your design system, then output CSS custom properties for runtime flexibility.

Hybrid Approach

SASS + CSS Custom Properties

// Define colors in SASS
$color-primary: #3498db;
$color-secondary: #2ecc71;
$color-danger: #e74c3c;

// Create a map
$colors: (
  'primary': $color-primary,
  'secondary': $color-secondary,
  'danger': $color-danger
);

// Generate CSS custom properties
:root {
  @each $name, $value in $colors {
    --color-#{$name}: #{$value};
  }
}

// Use in CSS
.button {
  background: var(--color-primary); // Runtime flexibility
}

// But compute shades in SASS
.button-light {
  background: lighten($color-primary, 20%); // Compile-time computation
}

CSS Nesting vs SASS Nesting

Native CSS nesting is now supported in modern browsers, but it differs from SASS nesting.

SASS Nesting (Established)

SASS Nesting Syntax

.card {
  padding: 20px;

  .card-title {
    font-size: 24px;
  }

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

  &--featured {
    border: 2px solid gold;
  }
}

// Compiles to:
// .card { padding: 20px; }
// .card .card-title { font-size: 24px; }
// .card:hover { box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
// .card--featured { border: 2px solid gold; }

Native CSS Nesting (New)

Native CSS Nesting Syntax

/* Native CSS nesting requires & for type selectors */
.card {
  padding: 20px;

  /* Must use & before element selectors */
  & .card-title {
    font-size: 24px;
  }

  /* Pseudo-classes work with or without & */
  &:hover {
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  }

  /* Compound selectors require & */
  &.featured {
    border: 2px solid gold;
  }
}

/* Syntax differences from SASS */
.parent {
  /* Native CSS - requires & */
  & .child { }

  /* SASS - & is optional for descendant selectors */
  .child { }
}

Key Differences

SASS vs Native Nesting

// SASS: Implicit parent reference
.nav {
  .item {  // Works - becomes .nav .item
    color: blue;
  }
}

// Native CSS: Explicit parent reference required
.nav {
  & .item {  // Required & for descendant selectors
    color: blue;
  }
}

// SASS: Parent selector can be anywhere
.button {
  .theme-dark & {  // Works - becomes .theme-dark .button
    color: white;
  }
}

// Native CSS: Parent selector must be at start
.button {
  .theme-dark & {  // Not supported in native CSS yet
    color: white;
  }
}
Warning: Native CSS nesting browser support is still evolving. As of 2024, it's supported in Chrome 112+, Safari 16.5+, and Firefox 117+. Always check caniuse.com for current support and consider using SASS for better browser compatibility.

CSS @layer vs SASS Architecture

CSS cascade layers provide a new way to manage specificity and style organization.

CSS @layer

Native CSS Cascade Layers

/* Define layer order */
@layer reset, base, components, utilities;

@layer reset {
  * {
    margin: 0;
    padding: 0;
  }
}

@layer base {
  body {
    font-family: sans-serif;
  }
}

@layer components {
  .button {
    padding: 10px 20px;
  }
}

@layer utilities {
  .mt-4 {
    margin-top: 1rem;
  }
}

/* Layers cascade in defined order, regardless of source order */

SASS File Architecture

SASS Import Order (7-1 Pattern)

// main.scss
@use 'abstracts/variables';
@use 'abstracts/mixins';

@use 'base/reset';
@use 'base/typography';

@use 'components/buttons';
@use 'components/cards';

@use 'utilities/spacing';

/* Order is explicit via import sequence */

Comparison

Note: CSS @layer solves cascade and specificity problems at the CSS level, while SASS provides file organization and code structure. They serve different purposes and can be used together.

CSS color-mix() vs SASS Color Functions

Color manipulation is one area where both approaches have strengths.

SASS Color Functions

SASS Color Manipulation (Compile-Time)

$primary: #3498db;

.button {
  background: $primary;
  border: 1px solid darken($primary, 10%);

  &:hover {
    background: lighten($primary, 10%);
  }
}

// Advanced color functions
$color: #3498db;
$lighter: scale-color($color, $lightness: 30%);
$saturated: adjust-color($color, $saturation: 20%);
$transparent: rgba($color, 0.5);

// Mix colors
$purple: mix(#ff0000, #0000ff, 50%); // 50% red, 50% blue

Native CSS color-mix()

CSS color-mix() Function (Runtime)

:root {
  --primary: #3498db;
}

.button {
  background: var(--primary);

  /* Mix with black to darken */
  border: 1px solid color-mix(in srgb, var(--primary), black 10%);

  &:hover {
    /* Mix with white to lighten */
    background: color-mix(in srgb, var(--primary), white 10%);
  }
}

/* Can work with any color space */
.box {
  background: color-mix(in oklch, red, blue 50%);
}

/* Dynamic mixing with CSS variables */
:root {
  --color-a: red;
  --color-b: blue;
  --mix-percent: 30%;
}

.element {
  color: color-mix(in srgb, var(--color-a), var(--color-b) var(--mix-percent));
}

Key Differences

When to Use Each

// Use SASS color functions when:
// ✓ Building a static design system
// ✓ Need consistent compile-time color computation
// ✓ Want full control over color manipulation methods
// ✓ Need to support older browsers

$brand: #3498db;
$brand-light: lighten($brand, 20%);
$brand-dark: darken($brand, 20%);

// Use CSS color-mix() when:
// ✓ Colors need to change dynamically
// ✓ Working with user-defined colors
// ✓ Want runtime color computation
// ✓ Only targeting modern browsers

:root {
  --brand: #3498db;
}

.dynamic-button {
  --button-lightness: 20%; /* Can be changed via JS */
  background: color-mix(in srgb, var(--brand), white var(--button-lightness));
}

CSS Container Queries: What SASS Can't Do

Container queries are a revolutionary CSS feature with no SASS equivalent.

Native CSS Container Queries

/* Define a container */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

/* Query the container */
.widget {
  padding: 1rem;
}

@container sidebar (min-width: 400px) {
  .widget {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

/* This is impossible with SASS - SASS can only work with viewport media queries */
Note: Container queries respond to the size of a container element, not the viewport. This enables true component-based responsive design, which SASS cannot replicate because it compiles before the browser knows container sizes.

Features Only SASS Provides

Despite CSS's evolution, SASS still offers unique capabilities.

1. Mixins with Logic

Complex Mixins

@mixin respond-to($breakpoint, $property, $values...) {
  @if $breakpoint == 'mobile' {
    @media (max-width: 767px) {
      #{$property}: $values;
    }
  } @else if $breakpoint == 'tablet' {
    @media (min-width: 768px) and (max-width: 1023px) {
      #{$property}: $values;
    }
  } @else if $breakpoint == 'desktop' {
    @media (min-width: 1024px) {
      #{$property}: $values;
    }
  }
}

.box {
  @include respond-to('mobile', font-size, 14px);
  @include respond-to('desktop', font-size, 18px);
}

/* CSS has no equivalent for this kind of logic-driven output */

2. Loops and Iteration

Generating Utilities

// Generate spacing utilities
$spacings: (0, 4, 8, 12, 16, 20, 24, 32);

@each $size in $spacings {
  .mt-#{$size} { margin-top: #{$size}px; }
  .mb-#{$size} { margin-bottom: #{$size}px; }
  .ml-#{$size} { margin-left: #{$size}px; }
  .mr-#{$size} { margin-right: #{$size}px; }
}

// Generate grid columns
@for $i from 1 through 12 {
  .col-#{$i} {
    width: percentage($i / 12);
  }
}

/* Pure CSS cannot generate classes programmatically */

3. Functions with Complex Logic

Custom SASS Functions

@function strip-unit($value) {
  @return $value / ($value * 0 + 1);
}

@function rem($px) {
  @return #{strip-unit($px) / 16}rem;
}

@function tint($color, $percentage) {
  @return mix(white, $color, $percentage);
}

@function shade($color, $percentage) {
  @return mix(black, $color, $percentage);
}

.element {
  font-size: rem(18px); // 1.125rem
  background: tint(#3498db, 20%);
}

/* CSS calc() cannot perform this level of computation */

4. @extend and Placeholder Selectors

Placeholder Selectors

%button-base {
  display: inline-block;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
}

.btn-primary {
  @extend %button-base;
  background: blue;
  color: white;
}

.btn-secondary {
  @extend %button-base;
  background: gray;
  color: white;
}

// Compiles to:
// .btn-primary, .btn-secondary {
//   display: inline-block;
//   padding: 10px 20px;
//   border-radius: 4px;
//   cursor: pointer;
// }
// .btn-primary { background: blue; color: white; }
// .btn-secondary { background: gray; color: white; }

/* CSS has no equivalent - would require duplicate declarations */

Features Only CSS Provides

1. Runtime Variables (Custom Properties)

As discussed earlier, CSS custom properties can change at runtime.

2. Cascade Layers (@layer)

Control specificity without increasing selector complexity.

3. Container Queries

Component-responsive design based on container size, not viewport.

4. :has() Selector (Parent Selector)

CSS :has() Selector

/* Style parent based on children */
.card:has(.card-image) {
  display: grid;
  grid-template-columns: 200px 1fr;
}

/* Style based on sibling */
.menu-item:has(+ .menu-item--active) {
  border-right: none;
}

/* SASS cannot select parents - this is runtime behavior */

5. CSS Subgrid

CSS Subgrid

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}

.grid-item {
  display: grid;
  grid-template-columns: subgrid; /* Inherits parent grid */
}

/* SASS cannot create this relationship - it's a browser layout feature */

The Future of CSS Preprocessors

As CSS evolves, what role will preprocessors play?

Perspective: Preprocessors like SASS are not becoming obsolete. They are evolving to complement modern CSS rather than replace it. The future is in hybrid approaches that leverage the strengths of both.

Scenarios Where SASS Remains Essential

  • Build-time optimization: Generating utility classes, computing static values
  • Complex design systems: Using maps, loops, and functions to generate consistent styles
  • Legacy browser support: Polyfilling modern CSS features for older browsers
  • Developer experience: Mixins, partials, and module system for better code organization
  • Framework development: Bootstrap, Foundation, and others rely on SASS for their build systems

Scenarios Where Vanilla CSS Suffices

  • Simple projects: Small sites with minimal styling needs
  • Dynamic theming: User-customizable themes that change at runtime
  • Modern-only projects: Targeting only current browsers with native features
  • Progressive enhancement: Using modern CSS with graceful degradation

Recommended Hybrid Approach

The best modern approach combines SASS and CSS strengths:

Hybrid Strategy

// Use SASS for:
// 1. Design tokens and build-time computation
$color-primary: #3498db;
$spacing-unit: 8px;

// 2. Generate CSS custom properties
:root {
  --color-primary: #{$color-primary};
  --spacing-unit: #{$spacing-unit};

  // Generate spacing scale
  @for $i from 0 through 10 {
    --spacing-#{$i}: #{$i * $spacing-unit};
  }
}

// 3. Mixins for reusable patterns
@mixin card-shadow {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: box-shadow 0.3s;

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

// Use CSS for:
// 1. Runtime theming
.element {
  color: var(--color-primary); /* Can be changed with JS */
  margin: var(--spacing-4);
}

// 2. Container queries
@container (min-width: 400px) {
  .widget {
    display: grid;
  }
}

// 3. Modern selectors
.card:has(.card-image) {
  /* Parent selector based on children */
}

// Combine both:
.component {
  @include card-shadow; /* SASS mixin */
  background: var(--color-primary); /* CSS variable */

  @container (min-width: 600px) { /* CSS container query */
    padding: var(--spacing-6);
  }
}

Decision Matrix

Choosing Between SASS and CSS

┌─────────────────────────────┬──────────┬─────────┐
│ Feature                     │ SASS     │ CSS     │
├─────────────────────────────┼──────────┼─────────┤
│ Variables (static)          │ ✓ Better │ ○ Works │
│ Variables (dynamic)         │ ✗ No     │ ✓ Better│
│ Nesting                     │ ✓ Better │ ○ Works │
│ Color manipulation          │ ✓ Better │ ○ Works │
│ Mixins                      │ ✓ Only   │ ✗ No    │
│ Functions                   │ ✓ Only   │ ○ calc()│
│ Loops                       │ ✓ Only   │ ✗ No    │
│ @extend                     │ ✓ Only   │ ✗ No    │
│ Container queries           │ ✗ No     │ ✓ Only  │
│ Cascade layers              │ ✗ No     │ ✓ Only  │
│ :has() parent selector      │ ✗ No     │ ✓ Only  │
│ Runtime changes             │ ✗ No     │ ✓ Only  │
│ JavaScript integration      │ ✗ No     │ ✓ Only  │
│ Browser support (older)     │ ✓ Better │ ○ Varies│
└─────────────────────────────┴──────────┴─────────┘

✓ = Excellent support
○ = Partial/limited support
✗ = Not supported

Summary and Next Steps

You've now completed the SASS tutorial! Here's what you've learned:

  • SASS fundamentals: variables, nesting, mixins, functions
  • Advanced features: loops, conditionals, maps, modules
  • Architecture patterns: 7-1 structure, BEM, ITCSS
  • Build tools integration: npm, Webpack, Vite
  • Best practices and performance optimization
  • Migration strategies from vanilla CSS
  • Real-world project development
  • Modern CSS vs SASS: choosing the right tool

Continue Your Learning Journey

  1. Build Real Projects: Apply what you've learned to actual websites
  2. Explore CSS-in-JS: Learn styled-components, Emotion, or Tailwind CSS
  3. Master PostCSS: Understand the CSS plugin ecosystem
  4. Study Design Systems: Learn how companies build scalable UI architectures
  5. Keep Up with CSS: Follow CSS specifications and browser updates
  6. Contribute to Open Source: Join SASS-based projects on GitHub
Final Tip: The best developers are pragmatic. Don't be dogmatic about SASS or CSS - use the right tool for each situation. Sometimes that's SASS, sometimes it's vanilla CSS, and often it's a combination of both.

Exercise 1: Comparative Analysis

Create a small project that demonstrates the strengths of each approach:

  1. Build a theme switcher using CSS custom properties
  2. Generate utility classes using SASS loops
  3. Create a responsive component using CSS container queries
  4. Build a mixin library for common patterns in SASS
  5. Document when you chose each approach and why

Exercise 2: Migration Planning

Plan a migration strategy for an existing project:

  1. Choose a SASS-based project (or create one)
  2. Identify which SASS features can be replaced with modern CSS
  3. Identify which SASS features should remain
  4. Create a hybrid architecture that uses both
  5. Document the benefits and tradeoffs of your approach

Exercise 3: Build Your Personal Framework

Create your own utility framework combining SASS and CSS:

  1. Use SASS to generate a comprehensive utility class library
  2. Output CSS custom properties for theming
  3. Include mixins for common responsive patterns
  4. Add modern CSS features (container queries, :has(), @layer)
  5. Document your framework with examples
  6. Publish it to npm and share with the community

Congratulations!

You've completed the comprehensive SASS/SCSS tutorial! You now have the knowledge and skills to build professional, maintainable stylesheets using SASS, modern CSS, or a hybrid of both. Remember that the web continues to evolve, and the best approach is to stay curious, keep learning, and adapt your tools to the problems you're solving.

Thank you for taking this journey through SASS. Happy styling! 🎨