SCSS / Sass Cheat Sheet: The Complete Reference (2025)
Sass (Syntactically Awesome Style Sheets) is the most popular CSS preprocessor. SCSS is the modern syntax — valid CSS with superpowers. This cheat sheet covers everything you need, from variables to modules, with copy-ready examples.
Quick Reference
| Feature | SCSS Example | What it does |
|---|---|---|
| Variable | $color: #3498db; |
Store a reusable value |
| Nesting | .nav { a { … } } |
Mirror HTML structure |
| Mixin | @mixin flex-center { … } |
Reusable style block |
| Include | @include flex-center; |
Apply a mixin |
| Extend | @extend %card; |
Share a selector's styles |
| Function | @function rem($px) { … } |
Return a computed value |
| Partial | _variables.scss |
File not compiled standalone |
| Import | @use 'variables'; |
Load a module |
| Each | @each $c in $colors { … } |
Loop over a list |
| If | @if $theme == dark { … } |
Conditional styles |
Installation and Setup
# Install via npm (recommended)
npm install -D sass
# Compile once
npx sass src/styles/main.scss dist/styles/main.css
# Watch mode
npx sass --watch src/styles/main.scss dist/styles/main.css
# Watch a directory
npx sass --watch src/styles:dist/styles
# Compressed output
npx sass --style=compressed src/main.scss dist/main.css
Vite / webpack — zero config: Both auto-process .scss files when sass is installed as a dev dependency.
Variables
// ─── Define ───────────────────────────────────────────────────────────────
$primary: #3498db;
$font-base: 16px;
$line-height: 1.5;
$max-width: 1200px;
$font-stack: 'Inter', system-ui, sans-serif;
$shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
// ─── Use ──────────────────────────────────────────────────────────────────
body {
font-family: $font-stack;
font-size: $font-base;
line-height: $line-height;
}
.container {
max-width: $max-width;
margin: 0 auto;
}
// ─── Default values (can be overridden by the consumer) ───────────────────
$accent: #e74c3c !default;
// ─── Interpolation (embed variable inside a string) ───────────────────────
$side: left;
.widget {
border-#{$side}: 3px solid $primary;
margin-#{$side}: 1rem;
}
Nesting
// ─── Basic nesting ─────────────────────────────────────────────────────────
nav {
background: $primary;
ul {
list-style: none;
padding: 0;
}
li {
display: inline-block;
}
a {
color: white;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
// ─── & parent selector ─────────────────────────────────────────────────────
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
&:hover { opacity: 0.85; }
&:focus { outline: 2px solid $primary; }
&:disabled { opacity: 0.5; cursor: not-allowed; }
&--primary { background: $primary; color: white; }
&--danger { background: #e74c3c; color: white; }
.icon + & { margin-left: 0.5rem; } // "preceded by icon"
}
// ─── Nesting depth rule of thumb ───────────────────────────────────────────
// Keep nesting ≤ 3 levels deep — deeper creates specificity wars.
Partials and Modules (@use / @forward)
File structure
src/styles/
├── _variables.scss ← token values
├── _mixins.scss ← reusable patterns
├── _reset.scss ← normalize
├── _typography.scss
├── _components/
│ ├── _button.scss
│ └── _card.scss
└── main.scss ← entry point, @use everything
@use (modern — Sass 1.23+)
// _variables.scss
$primary: #3498db;
$gap: 1rem;
// main.scss
@use 'variables'; // access as variables.$primary
@use 'variables' as v; // access as v.$primary
@use 'variables' as *; // access directly: $primary
// Access namespaced members
.hero {
color: variables.$primary;
gap: variables.$gap;
}
@forward (re-export from an index file)
// styles/_index.scss
@forward 'variables';
@forward 'mixins';
@forward 'reset';
// Consumer
@use 'styles'; // gets everything from _index.scss
@import (legacy — avoid in new projects)
@import 'variables'; // no namespace, pollutes global scope
Mixins
// ─── Define ───────────────────────────────────────────────────────────────
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
// With arguments and defaults
@mixin respond($breakpoint: md) {
$map: (sm: 576px, md: 768px, lg: 1024px, xl: 1280px);
@media (min-width: map.get($map, $breakpoint)) {
@content;
}
}
@mixin truncate($lines: 1) {
@if $lines == 1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
border: 0;
}
// ─── Use ──────────────────────────────────────────────────────────────────
.hero { @include flex-center; }
.card h3 { @include truncate(2); }
.sr-only { @include visually-hidden; }
.sidebar {
width: 100%;
@include respond(lg) {
width: 300px;
}
}
// ─── @content block ───────────────────────────────────────────────────────
@mixin hover-lift {
transition: transform 0.2s;
&:hover {
transform: translateY(-2px);
@content; // caller can inject extra hover styles
}
}
.card {
@include hover-lift {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
}
Functions
@use 'sass:math';
@use 'sass:color';
@use 'sass:string';
// ─── Custom functions ──────────────────────────────────────────────────────
@function rem($px, $base: 16) {
@return math.div($px, $base) * 1rem;
}
@function strip-unit($n) {
@return math.div($n, ($n * 0 + 1));
}
@function clamp-fluid($min, $max, $min-vw: 320, $max-vw: 1280) {
$slope: math.div($max - $min, $max-vw - $min-vw);
$intercept: $min - $slope * $min-vw;
@return clamp(#{$min}px, #{$intercept}px + #{$slope * 100}vw, #{$max}px);
}
// ─── Use ──────────────────────────────────────────────────────────────────
h1 { font-size: rem(32); } // → 2rem
p { font-size: rem(16); } // → 1rem
h1 {
font-size: clamp-fluid(24, 48); // fluid between 320–1280px
}
// ─── Built-in color functions ──────────────────────────────────────────────
$brand: #3498db;
.light { background: color.scale($brand, $lightness: 40%); }
.dark { background: color.scale($brand, $lightness: -30%); }
.muted { background: color.adjust($brand, $saturation: -50%); }
.fade { background: color.change($brand, $alpha: 0.5); }
$mixed: color.mix($brand, white, 20%); // 20% brand, 80% white
// ─── String functions ──────────────────────────────────────────────────────
$len: string.length("hello"); // 5
$upper: string.to-upper-case("hello"); // "HELLO"
$idx: string.index("hello", "ll"); // 3
Extend and Placeholders
// ─── Placeholder (%) — not output until extended ────────────────────────
%card-base {
background: white;
border-radius: 8px;
box-shadow: $shadow;
padding: 1.5rem;
}
.product-card {
@extend %card-base;
display: grid;
}
.profile-card {
@extend %card-base;
text-align: center;
}
// ─── Extend a real selector ────────────────────────────────────────────────
.error { color: red; }
.critical-error {
@extend .error;
font-weight: bold;
}
// ─── Extend vs Mixin ──────────────────────────────────────────────────────
// Extend: groups selectors → smaller CSS, but can't accept arguments
// Mixin: duplicates CSS → larger output, but accepts arguments
// Rule of thumb: use placeholders for shared cosmetic styles,
// mixins for parameterised or behavioural patterns.
Control Flow
@if / @else if / @else
@mixin theme-colors($theme: light) {
@if $theme == light {
background: white;
color: #333;
} @else if $theme == dark {
background: #1a1a2e;
color: #eee;
} @else {
@error "Unknown theme '#{$theme}'. Expected light or dark.";
}
}
.page { @include theme-colors(dark); }
@each
$sizes: sm 0.75rem, md 1rem, lg 1.25rem, xl 1.5rem;
@each $name, $size in $sizes {
.text-#{$name} { font-size: $size; }
}
// Maps
$colors: (primary: #3498db, success: #2ecc71, danger: #e74c3c);
@each $name, $value in $colors {
.bg-#{$name} { background-color: $value; }
.text-#{$name} { color: $value; }
.border-#{$name} { border-color: $value; }
}
@for
// Exclusive (does not include $end)
@for $i from 1 through 12 {
.col-#{$i} {
width: math.percentage(math.div($i, 12));
}
}
// Gap utilities
@for $i from 1 to 9 {
.gap-#{$i} { gap: #{$i * 0.25}rem; }
}
@while
$i: 1;
@while $i <= 5 {
.z-#{$i * 10} { z-index: $i * 10; }
$i: $i + 1;
}
Maps
@use 'sass:map';
$breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 1024px,
xl: 1280px,
2xl: 1536px,
);
$spacing: (
0: 0,
1: 0.25rem,
2: 0.5rem,
4: 1rem,
8: 2rem,
16: 4rem,
);
// Access
.hero { padding: map.get($spacing, 8); }
// Check existence
@if map.has-key($breakpoints, xl) { … }
// Merge two maps
$extended: map.merge($breakpoints, (3xl: 1920px));
// Loop
@each $name, $value in $breakpoints {
@if $value != 0 {
@media (min-width: $value) {
.container-#{$name} { max-width: $value; }
}
}
}
// Nested map access (deep get helper)
$tokens: (
color: (primary: #3498db, secondary: #2ecc71),
font: (base: 1rem, lg: 1.25rem),
);
@function token($keys...) {
$map: $tokens;
@each $key in $keys {
$map: map.get($map, $key);
}
@return $map;
}
h1 { color: token(color, primary); }
Lists
@use 'sass:list';
$font-stack: 'Inter', 'Helvetica', sans-serif;
$shadows: 2px 2px 4px black, 0 0 8px rgba(0,0,0,0.3);
// Functions
$len: list.length($font-stack); // 3
$first: list.nth($font-stack, 1); // 'Inter'
$idx: list.index($font-stack, sans-serif); // 3
$app: list.append($font-stack, monospace, $separator: comma);
$join: list.join((a b), (c d)); // a b c d
$zip: list.zip((1 2 3), (a b c)); // 1 a, 2 b, 3 c
Operators
@use 'sass:math';
// Arithmetic
$width: math.div(600px, 960px) * 100%; // 62.5%
$margin: 1rem + 8px; // compiled to calc() by Sass
$double: $font-base * 2;
// Comparison
@if $font-base > 14px { … }
@if $theme == 'dark' { … }
@if $debug != false { … }
// Logical
@if $size == sm or $size == xs { … }
@if not $ie-support { … }
// String concatenation
$class: 'btn-' + $variant; // 'btn-primary'
@error, @warn, @debug
// Stop compilation with a message
@mixin set-size($size) {
$valid: sm, md, lg;
@if not list.index($valid, $size) {
@error "Invalid size '#{$size}'. Must be one of #{$valid}.";
}
font-size: map.get((sm: 0.875rem, md: 1rem, lg: 1.25rem), $size);
}
// Non-fatal warning
@mixin deprecated-flex-center {
@warn "flex-center is deprecated. Use the 'center' utility class.";
@include flex-center;
}
// Print value during development
$computed: math.div(960px, 16px);
@debug "Computed columns: #{$computed}"; // prints to terminal
Real-World Patterns
Design Token System
// _tokens.scss
$color-brand-50: #eff6ff;
$color-brand-500: #3b82f6;
$color-brand-900: #1e3a8a;
$space-1: 0.25rem;
$space-2: 0.5rem;
$space-4: 1rem;
$space-8: 2rem;
$space-16: 4rem;
$radius-sm: 4px;
$radius-md: 8px;
$radius-full: 9999px;
$shadow-sm: 0 1px 2px rgba(0,0,0,.05);
$shadow-md: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -1px rgba(0,0,0,.06);
Responsive Mixin
// _mixins.scss
@use 'sass:map';
$_bp: (xs: 0, sm: 576px, md: 768px, lg: 1024px, xl: 1280px);
@mixin bp($size, $direction: up) {
$val: map.get($_bp, $size);
@if $direction == up {
@media (min-width: $val) { @content; }
} @else if $direction == down {
@media (max-width: ($val - 0.02px)) { @content; }
} @else if $direction == only {
$keys: map.keys($_bp);
$idx: list.index($keys, $size);
$next: list.nth($keys, $idx + 1);
$max: map.get($_bp, $next) - 0.02px;
@media (min-width: $val) and (max-width: $max) { @content; }
}
}
// Usage
.sidebar {
display: none;
@include bp(lg) { display: block; width: 280px; }
}
Utility Class Generator
// Generate .mt-{0,1,2,4,8,16}, .mb-…, .p-…, etc.
$props: (mt: margin-top, mb: margin-bottom, ml: margin-left, mr: margin-right,
pt: padding-top, pb: padding-bottom, pl: padding-left, pr: padding-right,
p: padding, m: margin);
$scale: (0: 0, 1: 0.25rem, 2: 0.5rem, 4: 1rem, 8: 2rem, 16: 4rem);
@each $abbr, $prop in $props {
@each $key, $val in $scale {
.#{$abbr}-#{$key} { #{$prop}: $val; }
}
}
Dark-Mode via CSS Custom Properties
// _variables.scss
:root {
--color-bg: #ffffff;
--color-text: #1a1a1a;
--color-card: #f8f9fa;
}
[data-theme='dark'] {
--color-bg: #0f172a;
--color-text: #e2e8f0;
--color-card: #1e293b;
}
// Components use custom properties, not Sass variables
.card {
background: var(--color-card);
color: var(--color-text);
}
Component File Structure
// _button.scss
@use '../tokens' as t;
@use '../mixins' as m;
$_sizes: (
sm: (padding: 0.375rem 0.75rem, font-size: 0.875rem),
md: (padding: 0.5rem 1rem, font-size: 1rem),
lg: (padding: 0.75rem 1.5rem, font-size: 1.125rem),
);
.btn {
@include m.flex-center;
border-radius: t.$radius-md;
font-weight: 600;
transition: opacity 0.2s, transform 0.2s;
cursor: pointer;
border: none;
&:hover:not(:disabled) { opacity: 0.85; }
&:active:not(:disabled) { transform: scale(0.98); }
&:disabled { opacity: 0.5; cursor: not-allowed; }
@each $name, $vals in $_sizes {
&--#{$name} {
padding: map.get($vals, padding);
font-size: map.get($vals, font-size);
}
}
}
Sass vs SCSS
| Feature | SCSS | Sass (indented) |
|---|---|---|
| File extension | .scss |
.sass |
| Braces | Required {} |
Omitted (uses indentation) |
| Semicolons | Required ; |
Omitted |
| Valid CSS | Yes — any CSS file is valid SCSS | No |
| Mixins | @mixin name { } |
=name |
| Include | @include name |
+name |
| Popularity | Dominant | Legacy |
Use SCSS. It's a superset of CSS, so migration is gradual and safe.
@use vs @import
@use (modern) |
@import (legacy) |
|
|---|---|---|
| Namespaced | Yes (module.$var) |
No (global) |
| Loaded once | Yes | No (risk of duplicates) |
| Private members | $_private |
Not supported |
!default overrides |
Yes, via with |
Yes |
| Deprecated | No | Yes (Sass 1.80+) |
// Override !default variable on load
@use 'bootstrap' with (
$primary: #9b59b6,
$body-bg: #f0f0f0,
);
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
@import in new code |
Duplicates, no namespace, deprecated | Use @use / @forward |
| Nesting > 3 levels | High specificity, hard to override | Flatten selectors, use BEM |
| Sass vars for theming | Not runtime-switchable | Use CSS custom properties for dynamic values |
/ for division |
Ambiguous in CSS; deprecated | Use math.div($a, $b) |
@extend across files |
Creates unexpected selector groupings | Prefer %placeholder or @mixin |
$var: value !global in mixins |
Leaks into global scope | Return value from a function instead |
No @use 'sass:math' |
math.div() undefined |
Add the @use at file top |
color.lighten() |
Deprecated in Sass 1.x | Use color.scale() or color.adjust() |
Sass vs CSS Variables
Sass Variables ($) |
CSS Custom Properties (--) |
|
|---|---|---|
| Scope | Compile time | Runtime |
| JavaScript access | No | getComputedStyle / style.setProperty |
| Dark mode (media) | Needs @mixin |
Native |
| Browser support | Compiled away — 100% | IE11 not supported |
| Calculation | Sass math | calc() |
| Best for | Tokens used at build time | Themes, component variants |
Best practice: Use Sass variables for breakpoints, spacing scales, and build-time constants. Use CSS custom properties for anything that changes at runtime (theme, user preference).
CLI Reference
# Compile
sass input.scss output.css
# Watch
sass --watch input.scss:output.css
# Watch directory
sass --watch src/styles:dist/styles
# Compressed output (production)
sass --style=compressed --no-source-map input.scss output.css
# Source maps
sass --source-map input.scss output.css # inline
sass --source-map-urls=absolute input.scss out.css
# Load path (for @use without relative paths)
sass --load-path=node_modules input.scss out.css
# Quiet mode (suppress deprecation warnings)
sass --quiet-deps input.scss out.css
FAQ
Q: Sass vs SCSS — which should I use?
A: SCSS. It's a superset of CSS (any .css file is valid .scss), so onboarding is easy. Sass indented syntax is rarely used in new projects.
Q: Do I need a build tool?
A: No. npm install -D sass gives you a standalone CLI. Vite, webpack, and Next.js process .scss files automatically once sass is installed.
Q: Should I use Sass variables or CSS custom properties?
A: Both — for different purposes. Sass $variables for breakpoints, modular scale, and build-time tokens. CSS --custom-properties for themes and anything toggled at runtime.
Q: Why is color.lighten() deprecated?
A: It adjusts HSL lightness, which produces perceptually inconsistent results. color.scale($color, $lightness: 20%) scales relative to the remaining range and is more predictable.
Q: How do I share variables across a large project?
A: Put tokens in _tokens.scss (or _variables.scss), use @forward from a barrel _index.scss, and @use 'styles' in every consuming file.
Q: What's the difference between @mixin and @extend?
A: Mixins copy their CSS into each caller (larger output, accepts arguments). @extend groups selectors (smaller output, no arguments). Use placeholders (%name) with extend to avoid extending concrete classes across files.