Python · SQL · Web Dev · Java · AI/ML tracks launching soon — your one platform for all of IT
Intermediate+150 XP

Intro to Sass — Variables, Nesting, Mixins

The CSS preprocessor that came before CSS variables — nesting, mixins, and why many real codebases still use it today.

35 min August 2026
// Part 01 — What a Preprocessor Actually Is

Sass Compiles to Plain CSS — Nothing More, Nothing Less

Sass (Syntactically Awesome StyleSheets) is a preprocessor — you write files in Sass's own extended syntax (.scss), and a build step compiles them into plain, ordinary CSS that ships to the browser. The browser itself has no idea Sass was ever involved; it only ever receives standard CSS.

A tiny Sass file...
$primary-color: #4285f4;

.button {
  background: $primary-color;
  padding: 12px 20px;
}
...compiles to exactly this plain CSS
.button {
  background: #4285f4;
  padding: 12px 20px;
}
// Part 02 — $variables vs CSS Custom Properties

Compile-Time vs Runtime — the Real Distinction

The Custom Properties module covered native --variable/var() CSS variables in depth. Sass's $variable syntax looks similar but works completely differently under the hood — the difference is genuinely important, not cosmetic.

Sass $variables — resolved at BUILD time, before the browser ever sees them
$spacing-unit: 8px;

.card {
  padding: $spacing-unit * 2;  // compiles to a fixed "padding: 16px;" — done, forever
}
CSS custom properties — resolved at RUNTIME, live in the browser
:root {
  --spacing-unit: 8px;
}
.card {
  padding: calc(var(--spacing-unit) * 2);
  /* Still "padding: 16px" visually — but the browser can genuinely
     recompute this if --spacing-unit changes later via JavaScript
     or a media query, with zero rebuild step involved */
}
🎯 Pro Tip
This is the entire practical decision between the two. A Sass variable is baked into the compiled CSS permanently at build time — it cannot respond to anything that happens in the browser afterward (a media query, a JS-driven theme toggle, a user preference). A CSS custom property genuinely lives in the browser and can be read, overridden, and reacted to at runtime. Many real projects use both together: Sass variables for values that truly never change after build (a fixed color palette used only inside Sass logic), custom properties for anything that needs to respond to runtime conditions (a dark-mode toggle, for example).
// Part 03 — Nesting

Writing Selectors That Mirror Your HTML Structure

Plain CSS — repeating the parent selector on every rule
.card { padding: 16px; }
.card .title { font-weight: 700; }
.card .title:hover { color: #4285f4; }
.card .footer { border-top: 1px solid #ddd; }
The same rules, nested in Sass
.card {
  padding: 16px;

  .title {
    font-weight: 700;

    &:hover {
      color: #4285f4;
    }
  }

  .footer {
    border-top: 1px solid #ddd;
  }
}

The & symbol refers to the immediate parent selector — &:hover compiles to .title:hover, not a new descendant selector. Nesting genuinely mirrors the visual/structural relationship in your HTML, which can make a stylesheet easier to navigate.

⚠️ Important
Over-nesting is a real, common problem, not just a style preference. Nesting five or six levels deep produces extremely long, extremely high-specificity compiled selectors — genuinely hard to override later, and directly working against the CSS Architecture module's advice to keep specificity low and predictable. A common guideline: avoid nesting more than 2-3 levels deep in real production Sass.
// Part 04 — Mixins

Reusable Blocks of Styles, With Parameters

A mixin is a named, reusable block of CSS declarations — optionally accepting arguments — that gets pasted inline wherever it's included, similar in spirit to a function.

Defining and using a mixin
@mixin flex-center($direction: row) {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: $direction;
}

.hero {
  @include flex-center;
}

.sidebar {
  @include flex-center($direction: column);
}
What both compile to — completely ordinary CSS
.hero {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: row;
}

.sidebar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}

The $direction: row default parameter value means @include flex-center; with no arguments still works, falling back to row — exactly the same default-parameter idea that shows up in most programming languages.

// Part 05 — Partials and @use

Splitting a Large Stylesheet Into Organized Files

Splitting variables/mixins into their own files, then combining them
// _variables.scss
$primary-color: #4285f4;
$spacing-unit: 8px;

// _mixins.scss
@mixin flex-center { display: flex; align-items: center; justify-content: center; }

// main.scss
@use 'variables' as v;
@use 'mixins' as m;

.card {
  padding: v.$spacing-unit * 2;
  @include m.flex-center;
}

Files prefixed with an underscore (_variables.scss) are partials — they are never compiled to their own separate CSS output file, only ever pulled into another file via @use. This is directly the same organizational instinct as the CSS Architecture module's advice on splitting large stylesheets into logical files.

// Part 06 — Why Sass Still Matters Despite Native CSS Variables

Compile-Time Logic Native CSS Still Cannot Do

Native CSS has genuinely closed much of the historical gap Sass filled — custom properties cover many of the old variable use cases, and nesting itself is now landing natively in CSS in modern browsers. What native CSS still cannot do, and what keeps Sass relevant in many real production codebases: mixins with real parameterized logic, @if/@each control-flow directives for generating repetitive CSS programmatically, and mathematical operations resolved entirely at build time with zero runtime cost.

Something only a preprocessor can do — generating a whole utility class set with a loop
@each $size in (4, 8, 12, 16, 24, 32) {
  .p-#{$size} { padding: #{$size}px; }
}
// Generates six complete, separate CSS rules — .p-4, .p-8, .p-12, etc. —
// from six lines of Sass, with no runtime cost or JavaScript involved

The #{$size} syntax above is Sass's interpolation — it drops a variable's value directly into a selector name or property value at compile time, something plain CSS custom properties cannot do at all (a custom property can only be used as a value, never spliced into a selector or property name itself).

// Part 07 — Real World
💼 What This Looks Like at Work

A Utility Class System Generated in 20 Lines, at a Chicago Design Agency

Scenario — Design agency, Chicago · Design system build

A team building a shared design system needs a full spacing utility class set — margin and padding classes for every direction (top/right/bottom/left/all) across a defined spacing scale. Written by hand in plain CSS, that's dozens of nearly-identical rules to maintain.

The entire spacing utility system, generated with Sass loops
$spacing-scale: (0, 4, 8, 12, 16, 24, 32, 48, 64);
$directions: (t: top, r: right, b: bottom, l: left);

@each $size in $spacing-scale {
  .p-#{$size} { padding: #{$size}px; }
  .m-#{$size} { margin: #{$size}px; }

  @each $short, $full in $directions {
    .p#{$short}-#{$size} { padding-#{$full}: #{$size}px; }
    .m#{$short}-#{$size} { margin-#{$full}: #{$size}px; }
  }
}

What this actually saved

Roughly 90 individual CSS rules get generated from these 12 lines of Sass — and changing the spacing scale later (adding a new value, removing one) is a single-line edit to $spacing-scale rather than manually adding or removing dozens of hand-written rules. The team's own note: "this is exactly the kind of repetitive, mechanical generation work that a preprocessor is genuinely still better at than native CSS today."

// Part 08 — Misconceptions

Four Misconceptions About Sass

"Sass $variables and CSS custom properties are basically interchangeable"
Sass variables are resolved at BUILD time and baked permanently into the compiled CSS — they cannot respond to anything happening in the browser afterward. CSS custom properties are resolved at RUNTIME and can be read/overridden live, by JavaScript or a media query, with no rebuild needed.
"Now that CSS has native nesting and custom properties, there is no real reason to still use Sass"
Native CSS has closed much of the historical gap, but mixins with real parameterized logic, @each/@if control-flow directives for generating repetitive rules programmatically, and build-time interpolation into selector names are still things only a preprocessor does.
"Nesting selectors as deeply as the HTML structure allows is always good practice"
Deep nesting compiles to very long, very high-specificity selectors that become genuinely hard to override later — a common guideline is to avoid nesting more than 2-3 levels deep in real production Sass.
"A Sass partial file (starting with an underscore) compiles to its own separate CSS file, just like a regular .scss file"
A partial is NEVER compiled to its own output file — it exists purely to be pulled into another file via @use, which is exactly what marks it as a partial in the first place.
// Part 09 — Interview Prep

5 Interview Questions — With Complete Answers

What is a CSS preprocessor, and what does Sass actually produce?
A preprocessor is a build-time tool that compiles an extended syntax into plain, standard CSS — the browser never sees or understands Sass itself, only the compiled CSS output. Sass adds variables, nesting, mixins, and control-flow directives on top of ordinary CSS syntax.
What is the fundamental difference between a Sass $variable and a native CSS custom property?
A Sass variable is resolved entirely at build/compile time and baked permanently into the output CSS — it has no existence at all once compiled. A CSS custom property genuinely exists in the browser at runtime and can be read, overridden, or reacted to after the page has loaded, without any rebuild.
What does the & symbol mean inside a nested Sass rule?
It refers to the immediate parent selector at that nesting level — &:hover inside a .title { } block compiles to .title:hover, not a new descendant selector.
What is a Sass mixin, and how is it different from a function in a general-purpose programming language?
A mixin is a named, reusable block of CSS declarations, optionally parameterized, that gets included (pasted inline) wherever @include references it. It is conceptually similar to a function, but its "return value" is always a block of CSS declarations, not an arbitrary computed value.
Given that CSS now has native custom properties and nesting, why do many real production codebases still use Sass?
For capabilities native CSS still lacks — mixins with real parameterized logic, @each/@if control-flow for programmatically generating repetitive rules (like a full spacing utility class system from a handful of lines), and compile-time interpolation into selector names, none of which native CSS custom properties can replicate.
// Common Mistakes

Sass Mistakes Beginners Make Constantly

Nesting selectors as deeply as the visual HTML hierarchy allows
Produces extremely long, extremely high-specificity compiled selectors that are genuinely difficult to override later — keep nesting to roughly 2-3 levels in real production code.
Using a Sass variable for a value that genuinely needs to change at runtime
A Sass variable is permanently baked into the compiled CSS — it cannot respond to a dark-mode toggle, a media query interaction, or anything else happening live in the browser. Use a CSS custom property for anything that needs runtime responsiveness.
Forgetting the underscore prefix on a partial file meant only to be imported elsewhere
Without the underscore, the file compiles to its own separate, likely unwanted, standalone CSS output file in addition to being pulled into whatever imports it.
Reaching for a complex @each/@if loop when a simple, explicit rule would be clearer
Generative Sass logic genuinely shines for large, repetitive rule sets (like a full spacing scale) — for a handful of one-off rules, plain explicit CSS is often more readable than an unnecessarily clever loop.
// Error Library

Issues You Will Hit With Sass — And Exactly Why

Error: Undefined variable.
Cause: A $variable is referenced before it is declared, or declared in a different partial that was never actually @use'd into the current file.
Fix: Confirm the variable is declared before use, and that the file declaring it is properly imported via @use at the top of the file that references it.
Error: Mixin doesn't exist.
Cause: A typo in the mixin name at the @include call site, or the file defining the @mixin was never @use'd into the current file.
Fix: Double check the exact mixin name spelling, and confirm the defining partial is imported.
The compiled CSS output is nothing like what was expected, with deeply nested, oddly-specific selectors
Cause: Over-nesting in the Sass source — each level of nesting compiles into an increasingly long, increasingly specific combined selector.
Fix: Flatten the nesting to 2-3 levels at most, using & only where it genuinely mirrors a real structural relationship (like a pseudo-class or a BEM modifier).

🎯 Key Takeaways

  • Sass compiles to plain CSS at build time — the browser never sees Sass syntax directly, only the compiled output.
  • The real distinction from CSS custom properties: Sass $variables are resolved at BUILD time and baked in permanently; custom properties are resolved at RUNTIME and can respond to live browser conditions.
  • Nesting mirrors your HTML structure but compiles to increasingly specific selectors as depth increases — keep it to roughly 2-3 levels in production code.
  • Mixins (@mixin/@include) are reusable, optionally parameterized blocks of CSS declarations, pasted inline wherever included.
  • Partial files (prefixed with an underscore) are never compiled to their own standalone output — they exist only to be pulled into another file via @use.
  • Sass remains genuinely useful today for what native CSS still cannot do: parameterized mixin logic, @each/@if-driven generation of repetitive rules, and compile-time interpolation into selector names.

What comes next

Phase 6 begins here — Production & Career Readiness, starting with responsive images and the performance techniques every real production page needs.

Module 37 → Responsive Images & Performance
Share

Discussion

0

Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.

Continue with GitHub
Loading...