π§© 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
!defaultflag 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
@useand@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:
| Type | Example |
|---|---|
| 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.
| Folder | Holds |
|---|---|
| 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:
- Declare variables for the padding, radius, and the two color pairs.
- Nest
alert-titleinside.alertusing the BEM element pattern (&__title). - Add the danger variant with a modifier (
&--danger). - 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;
!defaultmakes 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/@forwardgive 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.