Chapter 1 of ?
css 8 min read

CSS3 Mastery — Chapter 22: SASS / SCSS Tutorial & CSS Architecture

Chapter 22: SASS / SCSS Tutorial & Modern CSS Architecture

SASS (Syntactically Awesome Style Sheets) supercharges stylesheet development with modern module systems (@use, @forward), SASS maps, control directives (@each, @for, @if), inheritance (@extend), custom functions (@function), parameterized mixins (@mixin + @content), and BEM (Block Element Modifier) architectural patterns for scalable design token systems.

22.1 SASS / SCSS Feature Reference Guide

SCSS Concept Syntax Pattern Purpose & Best Practice
Module System @use "sass:color"; @use "tokens" as t; Replaces legacy @import with scoped namespace imports.
Forwarding @forward "config/variables"; Aggregates and re-exports multiple sub-modules into a unified entry point.
Sass Maps $theme-colors: (primary: #6366f1, success: #10b981); Key-value data structures storing design system tokens.
Loops (@each) @each $name, $color in $theme-colors { ... } Dynamically iterates over Sass maps to generate utility classes.
Loops (@for) @for $i from 1 through 12 { .col-#{$i} { ... } } Generates grid column layout utility classes.
Mixins & Include @mixin flex-center($gap: 10px) { ... } @include flex-center(16px); Encapsulates reusable CSS code blocks with parameter defaults.
Extend / Placeholder %btn-base { ... } .btn-primary { @extend %btn-base; } Shares CSS rules between selectors without duplication.
Custom Functions @function px-to-rem($px) { @return math.div($px, 16) * 1rem; } Returns computed values; unlike mixins, functions return a single value.
Control Flow @if $theme == dark { color: #fff; } @else { color: #000; } Conditional logic for theme switching and responsive token generation.
Debugging @debug "Value: #{$size}"; / @warn "Deprecated!"; Prints runtime values to the Sass compiler console for debugging.

22.2 SASS Module System (@use & @forward)

Modern SASS deprecated global @import due to namespace pollution and double-compilation performance bugs. The modern @use and @forward module rules provide scoped namespaces:

/* _colors.scss */
$primary: #6366f1;

/* _index.scss (barrel file — re-exports all partials) */
@forward "colors";
@forward "typography";

/* main.scss */
@use "tokens" as t; /* loads _index.scss implicitly */
.btn { background: t.$primary; } /* Access via scoped namespace 't' */

@import is Deprecated

@import was officially removed in Dart Sass 2.0. Always use @use for importing modules and @forward for re-exporting. Files prefixed with _ (partials) are never compiled to standalone CSS files.


22.3 Sass Maps & Dynamic Utility Generation Loops (@each, @for)

Storing design tokens inside Sass maps allows generating entire utility class libraries (e.g. .bg-primary, .text-success, .col-6) automatically using @each and @for loops:

<!DOCTYPE html>
<html>
<head>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
    h4 { color: #f59e0b; margin-top: 0; }

    /* Compiled SCSS Output from @each $name, $color loop over $theme-colors map:
       $theme-colors: (
         "indigo": #6366f1,
         "cyan": #06b6d4,
         "emerald": #10b981,
         "rose": #f43f5e
       );
       @each $name, $color in $theme-colors {
         .badge-#{$name} { background: rgba($color, 0.2); color: lighten($color, 20%); border: 1px solid $color; }
       }
    */
    .badge-indigo  { background: rgba(99, 102, 241, 0.2); color: #a5b4fc; border: 1px solid #6366f1; }
    .badge-cyan    { background: rgba(6, 182, 212, 0.2);   color: #38bdf8; border: 1px solid #06b6d4; }
    .badge-emerald { background: rgba(16, 185, 129, 0.2);  color: #34d399; border: 1px solid #10b981; }
    .badge-rose    { background: rgba(244, 63, 94, 0.2);   color: #fb7185; border: 1px solid #f43f5e; }

    .badge { padding: 8px 14px; border-radius: 6px; font-weight: bold; font-size: 12px; display: inline-block; margin-right: 8px; }

    /* Compiled SCSS Output from @for $i from 1 through 4 loop:
       @for $i from 1 through 4 {
         .col-#{$i} { width: calc(100% / 4 * #{$i}); }
       }
    */
    .col-1 { width: 25%; } .col-2 { width: 50%; } .col-3 { width: 75%; } .col-4 { width: 100%; }
    .col-demo { background: #1e293b; border: 1px solid #334155; border-radius: 6px; padding: 8px; font-size: 11px; color: #64748b; text-align: center; margin-top: 8px; }
  </style>
</head>
<body>
  <h4>Sass Map @each Utility Generation Output</h4>
  <p style="font-size:12px;color:#64748b;">Badges generated dynamically from Sass token map iteration:</p>

  <span class="badge badge-indigo">Indigo Token</span>
  <span class="badge badge-cyan">Cyan Token</span>
  <span class="badge badge-emerald">Emerald Token</span>
  <span class="badge badge-rose">Rose Token</span>

  <h4 style="margin-top:24px;">@for Loop Grid Column Output</h4>
  <div class="col-1 col-demo">.col-1 (25%)</div>
  <div class="col-2 col-demo">.col-2 (50%)</div>
  <div class="col-3 col-demo">.col-3 (75%)</div>
  <div class="col-4 col-demo">.col-4 (100%)</div>
</body>
</html>

22.4 BEM Architecture (Block Element Modifier) & SCSS Nesting

BEM stands for Block, Element, Modifier. In SCSS, the parent reference selector & allows nesting elements (&__element) and modifiers (&--modifier) seamlessly:

<!DOCTYPE html>
<html>
<head>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
    h4 { color: #10b981; }

    /* SCSS Source:
       .card {
         background: #1e293b; border-radius: 12px; padding: 20px;
         &--featured { border: 2px solid #f59e0b; background: rgba(245,158,11,0.15); }
         &__title { color: #fbbf24; margin: 0 0 6px; }
         &__body { color: #94a3b8; font-size: 13px; margin: 0; }
       }
    */
    .card { background: #1e293b; border-radius: 12px; padding: 20px; max-width: 340px; margin: 0 auto 16px; text-align: center; }
    .card--featured { border: 2px solid #f59e0b; background: rgba(245,158,11,0.15); }
    .card__title { color: #fbbf24; margin: 0 0 6px; }
    .card__body { color: #94a3b8; font-size: 13px; margin: 0; }
  </style>
</head>
<body>
  <h4 style="text-align:center;">BEM & SCSS Parent Selector (&) Demo</h4>
  <div class="card">
    <h4 class="card__title">.card (Base Block)</h4>
    <p class="card__body">Default card using BEM block + element classes.</p>
  </div>
  <div class="card card--featured">
    <h4 class="card__title">.card--featured (Modifier)</h4>
    <p class="card__body">Styled using BEM SCSS nested modifier pattern!</p>
  </div>
</body>
</html>

22.5 @extend & Placeholder Selectors (%)

@extend allows one selector to inherit all CSS rules from another selector. Placeholder selectors (prefixed with %) are "ghost" selectors that only exist in SCSS — they never compile to CSS on their own, making them the ideal base for shared styles:

Pattern SCSS Source Compiled CSS Output When to Use
@extend a class .btn-primary { @extend .btn; color: #fff; } .btn, .btn-primary { /* shared rules */ } .btn-primary { color: #fff; } When base class already exists in CSS output
Placeholder % %btn-base { padding: 10px 20px; } .btn-primary { @extend %btn-base; } .btn-primary { padding: 10px 20px; } — no %btn-base in output When base is a shared abstract — never rendered directly
@extend vs @mixin @extend shares selector groups; @mixin copies declarations inline @extend produces grouped selectors; @mixin duplicates declarations Use @extend for identical static rules; @mixin for parameterized output
/* SCSS Source */
%alert-base {
  padding: 14px 18px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.alert-success {
  @extend %alert-base;
  background: rgba(16, 185, 129, 0.15);
  border: 1px solid #10b981;
  color: #34d399;
}

.alert-danger {
  @extend %alert-base;
  background: rgba(239, 68, 68, 0.15);
  border: 1px solid #ef4444;
  color: #f87171;
}

/* -------- Compiled CSS Output -------- */
/* %alert-base does NOT appear — only the concrete selectors: */
.alert-success, .alert-danger {
  padding: 14px 18px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.alert-success { background: rgba(16,185,129,0.15); border: 1px solid #10b981; color: #34d399; }
.alert-danger  { background: rgba(239,68,68,0.15);  border: 1px solid #ef4444;  color: #f87171; }

Key Rule: Never @extend Across Media Queries

@extend cannot be used inside @media blocks to extend selectors defined outside that block. This causes a Sass compiler error. Use @mixin + @include inside media queries instead.


22.6 Advanced Mixins — @content Blocks & Responsive Breakpoint Patterns

The @content directive inside a @mixin acts as a slot — it allows the caller to pass an entire block of CSS rules into the mixin at the point of @include. This is the cornerstone pattern for building responsive breakpoint mixins:

/* SCSS Source — Responsive Breakpoint Mixin Library */
$breakpoints: (
  "sm":  576px,
  "md":  768px,
  "lg":  1024px,
  "xl":  1280px
);

/* @content mixin — caller passes their CSS block */
@mixin respond-to($bp) {
  $size: map.get($breakpoints, $bp);
  @if $size == null {
    @warn "Unknown breakpoint: #{$bp}. Valid keys: sm, md, lg, xl.";
  } @else {
    @media (min-width: $size) {
      @content; /* <-- caller's CSS block inserted here */
    }
  }
}

/* Usage */
.hero-title {
  font-size: 1.5rem;        /* mobile-first: base */
  @include respond-to("md") {
    font-size: 2.5rem;      /* tablet+ override */
  }
  @include respond-to("xl") {
    font-size: 4rem;        /* desktop+ override */
  }
}

/* -------- Compiled CSS Output -------- */
.hero-title { font-size: 1.5rem; }
@media (min-width: 768px) { .hero-title { font-size: 2.5rem; } }
@media (min-width: 1280px) { .hero-title { font-size: 4rem; } }
@content with Arguments (Sass 1.15+)

Modern Sass allows passing arguments back to the @content block using @content($arg) and consuming them with @include mixin using ($arg) { ... }. This enables building context-aware hover/focus state mixins.

@mixin on-event($self: false) {
  @if $self {
    &, &:hover, &:focus, &:active { @content; }
  } @else {
    &:hover, &:focus, &:active { @content; }
  }
}

.btn-primary {
  background: #6366f1;
  @include on-event() {
    background: #4f46e5;
    transform: translateY(-1px);
  }
}

22.7 SCSS Custom Functions (@function)

SCSS @function directives return computed values (unlike @mixin which outputs CSS declarations). Functions are ideal for unit conversions, token lookups, and mathematical calculations:

/* SCSS Source — Custom Function Library */
@use "sass:math";
@use "sass:color";

/* ── Unit Conversion: px → rem ── */
$base-font-size: 16px;

@function px-to-rem($px) {
  @return math.div($px, $base-font-size) * 1rem;
}

/* ── Color Utility: Tint (mix color with white) ── */
@function tint($color, $percentage) {
  @return color.mix(white, $color, $percentage);
}

/* ── Color Utility: Shade (mix color with black) ── */
@function shade($color, $percentage) {
  @return color.mix(black, $color, $percentage);
}

/* ── Z-Index Manager: Fetch from named layer map ── */
$z-layers: (
  "base":    1,
  "dropdown": 100,
  "sticky":  200,
  "modal":   300,
  "toast":   400
);

@function z($layer) {
  @if map.has-key($z-layers, $layer) {
    @return map.get($z-layers, $layer);
  }
  @warn "Unknown z-index layer: #{$layer}";
  @return null;
}

/* ── Usage Examples ── */
h1      { font-size: px-to-rem(32px); }    /* → 2rem */
p       { font-size: px-to-rem(14px); }    /* → 0.875rem */

.badge  { background: tint(#6366f1, 80%); color: shade(#6366f1, 30%); }

.modal  { z-index: z("modal"); }           /* → 300 */
.sticky { z-index: z("sticky"); }          /* → 200 */

@function vs @mixin — When to Use Each

  • Use @function when you need to return a single computed value (a number, color, string) to be used inside a property value.
  • Use @mixin when you need to output a block of CSS declarations or multiple properties at once.
  • Functions cannot contain @include statements or output CSS rules — only @return.

22.8 Control Flow — @if / @else, @while, @debug & @warn

SCSS includes a full set of control flow directives for conditional logic and iteration, as well as compiler-level diagnostics:

Directive Syntax Behaviour
@if / @else if / @else @if $size == "large" { font-size: 2rem; } @else { font-size: 1rem; } Conditional branching. Evaluates boolean SCSS expressions.
@while $i: 1; @while $i <= 5 { .mt-#{$i} { margin-top: #{$i * 4}px; } $i: $i + 1; } Repeats until condition is false. Use @for when loop count is known.
@debug @debug "Color value: #{$primary}"; Prints a debug message to the Sass compiler stdout. Removed from compiled CSS.
@warn @warn "Using deprecated mixin. Use respond-to() instead."; Prints a warning to compiler stderr. Used for deprecation notices.
@error @error "Unknown theme: #{$theme}. Expected light or dark."; Stops compilation with a fatal error message.
/* SCSS Source — Conditional Theming with @if/@else */
$themes: ("light", "dark", "high-contrast");

@mixin theme-tokens($theme) {
  @if $theme == "dark" {
    --bg: #0f172a;
    --text: #f8fafc;
    --border: #334155;
    --accent: #6366f1;
  } @else if $theme == "light" {
    --bg: #ffffff;
    --text: #0f172a;
    --border: #e2e8f0;
    --accent: #4f46e5;
  } @else if $theme == "high-contrast" {
    --bg: #000000;
    --text: #ffffff;
    --border: #ffffff;
    --accent: #ffff00;
  } @else {
    @error "Unknown theme '#{$theme}'. Valid themes: #{$themes}";
  }
  @debug "Compiling theme: #{$theme}"; /* printed to terminal only */
}

/* Generate theme attribute selectors */
[data-theme="dark"]           { @include theme-tokens("dark"); }
[data-theme="light"]          { @include theme-tokens("light"); }
[data-theme="high-contrast"]  { @include theme-tokens("high-contrast"); }

/* @while loop — generate spacing scale */
$step: 1;
@while $step <= 8 {
  .mt-#{$step} { margin-top: #{$step * 4}px; }
  .mb-#{$step} { margin-bottom: #{$step * 4}px; }
  .p-#{$step}  { padding: #{$step * 4}px; }
  $step: $step + 1;
}
/* Output: .mt-1 { margin-top: 4px; } ... .p-8 { padding: 32px; } */

22.9 Real-World SCSS Folder Architecture — The 7-1 Pattern

The 7-1 Pattern (7 folders, 1 main file) is the industry-standard SCSS project architecture. All partials are aggregated into a single main.scss entry point that compiles to one styles.css bundle:

7-1 SCSS Architecture
styles/
├── abstracts/ /* Variables, functions, mixins, placeholders */
│ ├── _variables.scss /* Design tokens: colors, spacing, fonts */
│ ├── _functions.scss /* px-to-rem(), tint(), shade(), z() */
│ ├── _mixins.scss /* respond-to(), on-event(), flex-center() */
│ └── _index.scss /* @forward all abstracts (barrel file) */
├── base/ /* Reset, typography, root variables */
│ ├── _reset.scss
│ └── _typography.scss
├── components/ /* Reusable UI components */
│ ├── _button.scss
│ ├── _card.scss
│ └── _badge.scss
├── layout/ /* Structural page regions */
│ ├── _header.scss
│ ├── _sidebar.scss
│ └── _grid.scss
├── pages/ /* Page-specific overrides */
│ ├── _home.scss
│ └── _dashboard.scss
├── themes/ /* Light / dark / high-contrast tokens */
│ ├── _light.scss
│ └── _dark.scss
├── vendors/ /* Third-party CSS overrides (Bootstrap, etc.) */
│ └── _bootstrap-overrides.scss
└── main.scss /* Entry point — @use all _index.scss barrels */
/* main.scss — Entry Point */
@use "abstracts";    /* loads abstracts/_index.scss → forwards vars, fns, mixins */
@use "base";
@use "components";
@use "layout";
@use "pages";
@use "themes";
@use "vendors";

/* Compile: npx sass styles/main.scss public/styles.css --watch */
/* Output:  public/styles.css + public/styles.css.map (source map) */

SCSS Compiler Commands

  • npx sass styles/main.scss public/styles.css --watch — compile and watch for changes
  • npx sass styles/main.scss public/styles.css --style=compressed — minified production output
  • npx sass --no-source-map styles/main.scss public/styles.css — skip source map generation
  • npx sass --load-path=node_modules styles/main.scss public/styles.css — allow importing from node_modules

22.10 Interactive Sandbox — SCSS Compiled Output Explorer

About This Sandbox

SCSS must be compiled server-side (via Dart Sass CLI or a build tool like Vite/Webpack). The sandbox below runs plain CSS — it shows what your SCSS compiles to. The original SCSS source is displayed in comments above each compiled rule, so you can compare source → output side by side.

The sandbox demonstrates compiled CSS output from the key SCSS patterns covered in this chapter — BEM modifier classes, @extend placeholder inheritance, and a px-to-rem() function result:

Live Playground — Chapter 22: SCSS Compiled Output
Done with this chapter?
Mark it complete to track your progress and unlock your certificate.
Next Up

Learner Reviews

Write a Review
Share your experience to help other learners.
Your Rating *