Toolmingo
Guides12 min read

SCSS / Sass Cheat Sheet: The Complete Reference (2025)

A comprehensive SCSS and Sass cheat sheet covering variables, nesting, mixins, functions, extends, partials, modules, and control flow — with copy-ready examples.

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.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools