Skip to main content

🧩 Variables, Nesting, and Partials

These are the three Sass features you'll touch in almost every file: variables to store your design decisions, nesting to mirror your markup, and partials to break one huge stylesheet into tidy modules. Master these and you already have most of Sass's day-to-day value.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Declare and use Sass variables across every value type, including maps
  • Explain global vs local scope and use the !default flag for overridable defaults
  • Write clean nested rules and wield the & parent selector for states, modifiers, and BEM
  • Split code into partials and load them with modern @use and @forward
  • Organize a project with the 7-1 architecture pattern

Estimated Time: 30–40 minutes  β€’  Difficulty: Beginner–Intermediate

Hands-on: Refactor a repetitive block of CSS into scoped variables and nested BEM rules.

In This Lesson

Sass Variables

A Sass variable stores a value you want to reuse. You declare one with a $ prefix and reference it anywhere a value is expected. Change the declaration once and every use updates.

// Declare design decisions once
$primary-color: #3b82f6;
$secondary-color: #22c55e;
$base-font: 'Roboto', sans-serif;
$base-font-size: 16px;
$spacing-unit: 8px;

// Reuse them everywhere
body {
  font-family: $base-font;
  font-size: $base-font-size;
  color: $primary-color;
  margin: $spacing-unit * 2;
}

.button {
  background-color: $secondary-color;
  padding: $spacing-unit ($spacing-unit * 2);
}
πŸ’‘ Analogy β€” variables are a recipe's ingredient list. Instead of repeating "2 tablespoons of olive oil" in every step, the recipe just says "the olive oil." To swap in a different oil, you edit one line at the top β€” not every step. Sass variables work the same way for your colors, fonts, and sizes.

⚠️ Sass variables vs CSS custom properties

A Sass $variable is resolved at compile time and vanishes from the output. A CSS --custom-property lives in the browser at runtime and can be changed by the cascade or JavaScript. Use Sass variables for build-time authoring; use custom properties when a value must change live (like theme switching).

Variable Types & Maps

Sass variables can hold far more than colors. Each of these is a distinct value type Sass understands and can operate on:

TypeExample
Colors$primary: #3498db;
Numbers (with units)$base-size: 16px;
Strings$font-stack: 'Helvetica', Arial, sans-serif;
Booleans$enable-shadows: true;
Lists$widths: 1px, 2px, 3px;
Maps (key β†’ value)$breakpoints: ('sm': 576px, 'md': 768px);
null$border: null;

The map is the most powerful of these. It bundles related values under named keys β€” perfect for a breakpoint table or a color palette:

@use 'sass:map';

$breakpoints: (
  'small':  576px,
  'medium': 768px,
  'large':  992px,
  'xlarge': 1200px
);

// Look a value up by key
.container {
  max-width: map.get($breakpoints, 'large'); // 992px
}

πŸ“– Real-world tie-in: design tokens

Companies capture their brand as design tokens β€” named variables for every color, space, and size. Here's an Airbnb-flavored set:

// Brand colors
$rausch: #ff5a5f;   // primary
$babu:   #00a699;   // secondary
$hof:    #484848;   // text

// 8-point spacing scale
$space-xs: 4px;
$space-sm: 8px;
$space-md: 16px;
$space-lg: 24px;
$space-xl: 32px;

Scope & the !default Flag

Like variables in any programming language, Sass variables have scope:

  • Global β€” declared at the top level, outside any selector; usable everywhere.
  • Local β€” declared inside a selector's braces; usable only in that selector and its nested children.
$global-color: blue;      // global

.container {
  $local-padding: 20px;   // local to .container
  padding: $local-padding;
  color: $global-color;

  .child {
    // local variables reach into nested selectors
    margin: math.div($local-padding, 2);
    border: 1px solid $global-color;
  }
}

.another-element {
  // margin: $local-padding;  // ❌ error β€” out of scope
  color: $global-color;       // βœ… global is fine
}

The !default flag

Adding !default to a declaration means "use this value only if the variable hasn't already been set." It is how libraries expose customizable defaults β€” set the variable before loading the library and your value wins.

// _theme.scss (the library)
$primary-color: blue !default;

body { color: $primary-color; }
// main.scss (your override)
@use 'theme' with ($primary-color: red);
// body ends up red, because you configured it before the default applied

πŸ’‘ Why !default matters

This single flag is what makes frameworks like Bootstrap themeable. You override just the variables you care about and inherit sensible defaults for the rest.

Nesting

Nesting lets you place selectors inside one another, mirroring your HTML's shape. It removes the repeated ancestor prefixes that plain CSS forces on you.

Plain CSS

nav { background: #333; }
nav ul { margin: 0; padding: 0; list-style: none; }
nav ul li { display: inline-block; }
nav ul li a { color: white; padding: 10px 15px; display: block; }
nav ul li a:hover { background: #555; }

SCSS with nesting

nav {
  background: #333;

  ul {
    margin: 0;
    padding: 0;
    list-style: none;

    li {
      display: inline-block;

      a {
        color: white;
        padding: 10px 15px;
        display: block;

        &:hover { background: #555; }
      }
    }
  }
}

⚠️ Don't over-nest

Deep nesting produces bloated, over-specific selectors like nav ul li a span that are painful to override and tightly coupled to your markup. Keep nesting to 3 levels or fewer. This is often called the "Inception rule" β€” if you're more than a few dreams deep, come back up.

The & Parent Selector & BEM

Inside a nested block, the ampersand & stands for the compiled selector of its parent. It is the key to writing pseudo-classes, modifiers, and state classes without breaking out of the nesting.

.button {
  padding: 10px 15px;
  background: blue;
  color: white;

  &:hover { background: darkblue; }        // .button:hover

  &::before {                              // .button::before
    content: "β†’";
    margin-right: 5px;
  }

  &.button--large {                        // .button.button--large
    padding: 15px 25px;
  }

  .dark-theme & {                           // .dark-theme .button
    background: navy;
  }
}

Notice the last rule: putting & at the end flips the relationship, letting a themed ancestor restyle the element.

BEM pairs beautifully with &

The BEM (Block__Element--Modifier) methodology and the & selector are a natural fit β€” you can build every element and modifier name by concatenating onto the block:

.card {
  border-radius: 4px;
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);

  &__header {                       // .card__header
    padding: 16px;
    border-bottom: 1px solid #eee;
  }

  &__body { padding: 16px; }        // .card__body

  &--featured {                     // .card--featured
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    transform: scale(1.05);
  }
}

Compiles to clean, flat BEM CSS:

.card { border-radius: 4px; background: white; box-shadow: 0 2px 4px rgba(0,0,0,.1); }
.card__header { padding: 16px; border-bottom: 1px solid #eee; }
.card__body { padding: 16px; }
.card--featured { box-shadow: 0 4px 8px rgba(0,0,0,.2); transform: scale(1.05); }

Partials, @use & @forward

A partial is a Sass file whose name begins with an underscore, e.g. _variables.scss. The underscore signals that it shouldn't compile to its own CSS file β€” it exists to be loaded into others.

Modern loading: @use

The current way to pull a partial in is @use. It loads the file as a module with its own namespace, so members are accessed as namespace.$member. This prevents the name collisions that plagued the old @import rule.

// _colors.scss
$primary: blue;
$secondary: green;
// main.scss
@use 'colors';

.button {
  background-color: colors.$primary;   // namespaced access
  color: white;
}

// Rename the namespace if you like:
@use 'colors' as c;
.link { color: c.$secondary; }

⚠️ @import is deprecated

You'll still see @import 'variables'; in older code and tutorials. It works today but is on track for removal from Sass. It also dumps everything into one global namespace and can load a file multiple times. Prefer @use and @forward in new projects.

Re-exporting: @forward

Where @use loads a module for the current file, @forward makes another file's members available to whoever loads this file. It's how you build a single tidy entry point for a folder of partials:

// abstracts/_index.scss  β€” one door into the whole folder
@forward 'variables';
@forward 'functions';
@forward 'mixins';
// main.scss
@use 'abstracts';

body {
  color: abstracts.$text-color;
  font-family: abstracts.$font-stack;
}

The 7-1 Pattern

A widely used architecture for scaling Sass is the 7-1 pattern: seven folders of partials, all funneled into one main.scss entry file.

graph TD A[main.scss] --> B[abstracts/] A --> C[base/] A --> D[components/] A --> E[layout/] A --> F[pages/] A --> G[themes/] A --> H[vendors/] B --> B1[_variables Β· _functions Β· _mixins] C --> C1[_reset Β· _typography] D --> D1[_buttons Β· _cards Β· _forms] E --> E1[_header Β· _footer Β· _grid] F --> F1[_home Β· _about] G --> G1[_default Β· _dark] H --> H1[_bootstrap]
FolderHolds
abstracts/Variables, functions, mixins β€” no CSS output
base/Resets, typography, base element styles
components/Buttons, cards, forms β€” reusable UI
layout/Header, footer, grid, navigation
pages/Page-specific styles
themes/Alternate visual themes
vendors/Third-party library styles
// main.scss β€” assemble the whole system
@use 'abstracts';
@use 'vendors/bootstrap';
@use 'base/reset';
@use 'base/typography';
@use 'layout/header';
@use 'layout/grid';
@use 'components/buttons';
@use 'components/cards';
@use 'pages/home';
@use 'themes/default';

πŸ’‘ Real-world proof

Bootstrap's own source is organized almost exactly this way β€” a scss/ folder of partials (_variables.scss, _mixins.scss, _buttons.scss, …) gathered into a single bootstrap.scss. The 7-1 pattern isn't academic; it's how shipped frameworks stay maintainable.

Hands-on Exercise

πŸ‹οΈ Refactor Repetitive CSS into Scoped SCSS

Objective: Take a repetitive CSS card and rebuild it with variables, nesting, and the & BEM pattern.

Starting CSS:

.alert { padding: 16px; border-radius: 8px; background: #eff6ff; color: #1e3a8a; }
.alert-title { font-weight: 700; margin-bottom: 8px; }
.alert.alert--danger { background: #fef2f2; color: #991b1b; }
.alert.alert--danger .alert-title { color: #7f1d1d; }

Instructions:

  1. Declare variables for the padding, radius, and the two color pairs.
  2. Nest alert-title inside .alert using the BEM element pattern (&__title).
  3. Add the danger variant with a modifier (&--danger).
  4. Compile and confirm the output matches the original CSS's behavior.
πŸ’‘ Hint

Rename alert-title to the BEM form alert__title so &__title concatenates correctly. Inside &--danger, you can re-nest &__title to restyle the title for that variant.

βœ… Example solution
$alert-pad: 16px;
$alert-radius: 8px;
$info-bg: #eff6ff;
$info-fg: #1e3a8a;
$danger-bg: #fef2f2;
$danger-fg: #991b1b;

.alert {
  padding: $alert-pad;
  border-radius: $alert-radius;
  background: $info-bg;
  color: $info-fg;

  &__title {
    font-weight: 700;
    margin-bottom: 8px;
  }

  &--danger {
    background: $danger-bg;
    color: $danger-fg;

    .alert__title { color: #7f1d1d; }
  }
}

One block now describes the whole component, its title, and its danger variant β€” far easier to maintain than four flat rules.

🎯 Quick Quiz

Question 1: Inside .button { &:hover { … } }, what does the & compile to?

Question 2: Why is @use preferred over @import in modern Sass?

Question 3: What does the !default flag do?

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Variables ($name) store reusable values of every type β€” including powerful maps β€” resolved at compile time.
  • Variables have global and local scope; !default makes them overridable for themeable libraries.
  • Nesting mirrors HTML and cuts repetition β€” but stay within ~3 levels.
  • The & parent selector drives states, modifiers, and clean BEM.
  • Partials loaded with modern @use / @forward give a modular, namespaced architecture like the 7-1 pattern.

πŸ“š Further Reading

πŸš€ What's Next?

You can now store and organize values. Next, you'll make them do work: the following lesson covers mixins, functions, and operations β€” reusable parameterized blocks, value-returning functions, and math that powers spacing scales and utility generators.

πŸŽ‰ Solid foundation!

Variables, nesting, and partials are the backbone of every Sass project. Everything from here builds on them.