CSS Preprocessor

Sass

Sass is a CSS preprocessor that adds power and elegance to plain CSS. Here's what it is, how it works, and why developers use it to write better stylesheets.

intermediate16 min readUpdated Sep 15, 2026
style.scss
scss
/* style.scss */
$brand: #3498db;
$radius: 8px;

@mixin button($bg) {
  background: $bg;
  border-radius: $radius;
  padding: 8px 16px;
}

.button {
  @include button($brand);

  &:hover {
    filter: brightness(1.1);
  }
}
Full name
Syntactically Awesome Style Sheets
Created by
Hampton Catlin, 2006
Type
CSS preprocessor
Syntaxes
SCSS (.scss) and indented (.sass)
File extension
.scss or .sass

Why it matters

Why Sass matters

Modular organisation

Partials, modules and namespaces let you split large stylesheets into small, reusable files that stay easy to maintain as a project grows.

Powerful abstractions

Variables, mixins and functions eliminate repetition and let you build design systems, responsive helpers and themeable UI from a single source of truth.

Lean output

Sass compiles down to plain CSS with zero runtime cost — you get the developer experience of a programming language and a production stylesheet the browser understands.

The big picture

The three ideas behind Sass

Variables hold values, nesting mirrors structure, and mixins package reusable blocks of styles.

Variables

Store

A $name holds a value you reuse across the stylesheet.

Nesting

Structure

Nest selectors to mirror the HTML structure, using & for the parent reference.

Mixins & functions

Reuse

@mixin and @include package reusable declarations, while @function returns computed values.

Sass at a glance

The core features

Nesting

Mirror your HTML structure in CSS without repeating selectors.

Variables

Store colours, spacing and breakpoints in one place with $variables.

Mixins

Reusable chunks of styles you can parameterise and include anywhere.

Partials

Split your stylesheet into small files and import them as needed.

Loops

Generate repetitive styles with @for, @each and @while.

Built-in functions

Colour manipulation, string helpers, maths and more out of the box.

A short history

From preprocessor to modern tooling

  1. 2006

    Sass is born

    Hampton Catlin creates Sass to bring programming concepts like variables and nesting to CSS.

    06
  2. 2009

    SCSS syntax introduced

    A new syntax using CSS-compatible curly braces makes Sass accessible to any CSS author.

    09
  3. 2011

    Ruby Sass stabilises

    Sass becomes the most popular CSS preprocessor and the de facto tool for large stylesheets.

    11
  4. 2020

    Dart Sass takes over

    The original Ruby implementation is deprecated; Dart Sass becomes the canonical and only actively maintained version.

    20
  5. 2026

    Native CSS catches up

    CSS custom properties and nesting reach broad browser support, but Sass still leads with mixins, loops and functions.

    26

The complete guide

Sass: Everything you need to know

What is Sass?

Sass stands for Syntactically Awesome Style Sheets. It is a CSS preprocessor — a language that extends plain CSS with programming features and compiles down to standard CSS that browsers can understand. If you have ever found yourself repeating colour values, duplicating media queries or wading through hundreds of lines of flat CSS, Sass is the tool that solves those problems.

Created by Hampton Catlin in 2006, Sass was the first CSS preprocessor to gain widespread adoption. It introduced concepts from general-purpose programming languages — variables, nesting, functions, loops and modules — into the world of stylesheets. Today it is the foundation of projects like Bootstrap, Foundation and countless design systems.

The best way to think about Sass is as CSS with a toolbox. You write .scss or .sass files using Sass features, run them through a compiler, and get back plain .css files the browser can load. There is no runtime cost. The power exists entirely in your development workflow.

Sass vs CSS: what changes?

Plain CSS is a declarative language — you describe how elements should look, but you cannot create abstractions, reuse logic or organise code beyond putting things in separate files. Sass addresses every one of those limitations:

Capability Plain CSS Sass
Variables Custom properties (--var) $variable with full scoping
Nesting Basic (now in CSS) Mature, with parent selectors (&)
Reuse Copy-paste or utility classes Mixins, functions and extends
Organisation @import only @use, @forward, partials
Logic None @if, @each, @for, @while
Maths calc() only Full arithmetic on any values
Colours Fixed values lighten, darken, mix and more

Sass vs SCSS syntax

Sass supports two syntaxes:

SCSS (.scss) — the most common. It uses curly braces and semicolons, just like CSS. Any valid CSS file is automatically valid SCSS. This makes adoption seamless:

/* style.scss */
$primary: #3498db;

.button {
  background: $primary;
  color: white;

  &:hover {
    background: darken($primary, 10%);
  }
}

Indented syntax (.sass) — the original. It uses indentation and newlines instead of braces and semicolons, making it more concise but less familiar:

/* style.sass */
$primary: #3498db

.button
  background: $primary
  color: white

  &:hover
    background: darken($primary, 10%)

SCSS is the syntax you will encounter in virtually every modern project. Unless you have a specific reason to choose indented Sass, use .scss.

Why Sass exists

Solving real problems is why Sass became the industry standard. The core issues it addresses:

Repetition. Without variables, a single colour change means Find-and-Replace across every file. Sass variables let you change a value once and update it everywhere.

Flat structure. Deeply nested components produce long, repetitive selectors like .card .card-header .card-title. Sass nesting mirrors the HTML hierarchy in your styles.

No reuse. CSS has no concept of a reusable block of styles. A button component with 10 variants means 10 nearly identical blocks. Sass mixins and extends eliminate the duplication.

No logic. Generating a colour palette, alternating row colours or creating responsive breakpoints from a list requires external tools. Sass gives you loops and conditionals right in the stylesheet.

Organisation. Importing dozens of CSS files with @import creates a flat global namespace where names inevitably collide. Sass modules (@use, @forward) solve this with namespacing.

Variables

Variables are the simplest and most impactful Sass feature. A variable stores a value you can reuse throughout your stylesheet:

/* style.scss */
$primary: #3498db;
$secondary: #2ecc71;
$font-stack: 'Helvetica Neue', Arial, sans-serif;
$spacing-unit: 8px;

body {
  font-family: $font-stack;
}

.container {
  padding: $spacing-unit * 3;
}

.btn-primary {
  background: $primary;
  color: white;
}

Change $primary once at the top and every element that references it updates automatically. Sass variables are compile-time constants — they do not exist in the output CSS. This is different from CSS custom properties (--var), which live in the browser at runtime. Use Sass variables for values that do not need to change dynamically.

Variables have scope. A variable declared inside a rule or mixin is local to that block. Use !global to override a global variable from within a local scope, though this is rarely necessary:

/* style.scss */
$color: red; // global

.box {
  $color: blue; // local — only exists inside .box
  color: $color;
}

// $color is still red here

Nesting

Nesting lets you write child selectors inside their parent, mirroring the HTML structure and keeping related styles grouped together:

/* style.scss */
.nav {
  display: flex;
  gap: $spacing-unit * 2;

  &__item {
    padding: $spacing-unit;

    &--active {
      font-weight: bold;
      border-bottom: 2px solid $primary;
    }

    &:hover {
      background: rgba($primary, 0.1);
    }
  }

  &__logo {
    margin-right: auto;
  }
}

This compiles to:

/* compiled.css */
.nav { display: flex; gap: 16px; }
.nav__item { padding: 8px; }
.nav__item--active { font-weight: bold; border-bottom: 2px solid #3498db; }
.nav__item:hover { background: rgba(52, 152, 219, 0.1); }
.nav__logo { margin-right: auto; }

The & parent selector is essential. It references the parent selector, so &__item inside .nav becomes .nav__item. Without &, Sass treats the ampersand as the parent context. You can also use & for pseudo-classes and pseudo-elements instead of nesting them as separate blocks.

While native CSS now supports basic nesting, Sass nesting is more mature — it supports & for arbitrary parent references, works everywhere and has been battle-tested for over a decade.

Partials and imports

Partials are Sass files that are meant to be imported, not compiled on their own. Name them with an underscore prefix — _variables.scss, _buttons.scss, _mixins.scss — and Sass will skip them during direct compilation:

// _variables.scss
$primary: #3498db;
$spacing: 8px;
$font-stack: 'Helvetica Neue', Arial, sans-serif;

// _buttons.scss
@use 'variables' as *;

.button {
  padding: $spacing * 2;
  background: $primary;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

// main.scss
@use 'variables' as *;
@use 'buttons';
@use 'layout';
@use 'typography';

The @use rule imports a file as a module. You then access its members through a namespace, or use as * to bring everything into scope. This keeps your global namespace clean and prevents naming collisions between files.

Modules: @use and @forward

The @import rule is deprecated. It merged all files into one global scope, meaning a $primary in one file could silently overwrite a $primary in another. The modern replacements are @use and @forward.

@use

@use imports a module and gives it a namespace:

/* style.scss */
@use 'variables';

.button {
  background: variables.$primary;
  padding: variables.$spacing * 2;
}

You can alias the namespace or bring all members into scope:

/* style.scss */
@use 'variables' as v;
@use 'variables' as *; // no namespace needed

@forward

@forward re-exports a module’s members so that files importing the current module get access to them. It is used to create barrel files — a single entry point that exposes multiple modules:

// _index.scss
@forward 'variables';
@forward 'mixins';
@forward 'functions';

// main.scss
@use 'index' as *; // gets everything from variables, mixins and functions

Use @forward with show and hide to control which members are re-exported:

/* style.scss */
@forward 'variables' show $primary, $secondary;
@forward 'mixins' hide $internal-helper;

Mixins

Mixins are reusable blocks of styles you can parameterise and include anywhere. Think of them as functions that output CSS declarations:

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

@mixin respond-to($breakpoint) {
  @if $breakpoint == 'tablet' {
    @media (min-width: 768px) { @content; }
  } @else if $breakpoint == 'desktop' {
    @media (min-width: 1024px) { @content; }
  }
}

.hero {
  @include flex-center;
  min-height: 100vh;

  @include respond-to('desktop') {
    min-height: 80vh;
  }
}

.card {
  @include flex-center;
  padding: 1rem;
}

Mixins can have default parameters, accept any number of arguments and use @content to accept a block of styles from the caller. They are the primary abstraction mechanism in Sass — design systems, responsive helpers and cross-browser compatibility layers are all built on mixins.

Functions

Sass functions compute and return a value. They are similar to mixins but instead of outputting CSS they produce a value you can assign:

/* functions.scss */
@function spacing($n) {
  @return $n * 8px;
}

@function shade($color, $percentage) {
  @return mix(black, $color, $percentage);
}

.hero {
  padding: spacing(4);       // 32px
  background: shade(#3498db, 20%);
}

.sidebar {
  padding: spacing(2);       // 16px
}

Sass ships with many built-in functions for colours (lighten, darken, saturate, adjust-hue), strings (str-length, str-insert), maths (abs, min, max, round) and lists (length, nth, append, index). You can write your own for project-specific logic.

Extends and inheritance

The @extend rule lets one selector inherit the styles of another. Sass merges the two selectors into a single rule in the output CSS:

/* style.scss */
%message {
  padding: 1rem;
  border-radius: 4px;
  margin-bottom: 1rem;
}

.success {
  @extend %message;
  background: #d4edda;
  color: #155724;
}

.error {
  @extend %message;
  background: #f8d7da;
  color: #721c24;
}

.warning {
  @extend %message;
  background: #fff3cd;
  color: #856404;
}

Compiles to:

/* compiled.css */
.success, .error, .warning {
  padding: 1rem;
  border-radius: 4px;
  margin-bottom: 1rem;
}

.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
.warning { background: #fff3cd; color: #856404; }

Placeholders (selectors starting with %) are not output to CSS on their own — they only appear when extended. This avoids empty rules in your compiled output.

Use @extend for shared structural patterns. For anything requiring parameters, use a mixin instead — @extend cannot accept arguments.

Operators

Sass supports the standard arithmetic operators for working with numbers:

/* style.scss */
$column-count: 12;
$gutter: 24px;
$gap: $gutter / 2;            // 12px

$base: 16px;
$heading-1: $base * 2;         // 32px
$heading-2: $base * 1.5;       // 24px

$full-width: 100%;
$sidebar: $full-width / 3;     // 33.333%

// Remainder
$remainder: 17 % 5;            // 2

Sass also supports string interpolation, comparison operators and boolean operators:

/* style.scss */
$primary: #3498db;
$dark: darken($primary, 20%);
$is-dark: true;

@mixin theme-bg {
  @if $is-dark {
    background: $dark;
  } @else {
    background: $primary;
  }
}

Be aware that division (/) is now treated as a CSS fraction in some contexts (e.g. font: 16px/1.5). Wrap divisions in math.div() to avoid ambiguity, or use calc() for dynamic division.

Control flow

Sass includes conditionals and loops that generate CSS at compile time:

@if / @else

/* style.scss */
$theme: 'light';

@mixin theme-bg {
  @if $theme == 'light' {
    background: white;
    color: black;
  } @else if $theme == 'dark' {
    background: #1a1a2e;
    color: white;
  } @else {
    background: #f0f0f0;
    color: #333;
  }
}

@each

Iterates over a list or map:

/* style.scss */
$colors: (
  primary: #3498db,
  secondary: #2ecc71,
  danger: #e74c3c,
);

@each $name, $color in $colors {
  .text-#{$name} {
    color: $color;
  }

  .bg-#{$name} {
    background: $color;
  }
}

@for

Generates a range of selectors:

/* style.scss */
@for $i from 1 through 12 {
  .col-#{$i} {
    width: ($i / 12) * 100%;
  }
}

@for $i from 1 through 5 {
  .mt-#{$i} {
    margin-top: $i * 8px;
  }
}

@while

Loops while a condition is true:

/* style.scss */
$columns: 12;
$i: $columns;

@while $i > 0 {
  .col-#{$i} {
    width: ($i / $columns) * 100%;
  }
  $i: $i - 1;
}

Maps and lists

Sass has first-class data structures for collections.

Lists are ordered, comma or space separated:

/* style.scss */
$breakpoints: 480px, 768px, 1024px, 1280px;

@each $bp in $breakpoints {
  @media (min-width: $bp) {
    .container {
      max-width: $bp;
    }
  }
}

// Lists can be nested
$margins: (8px, 16px, 24px, 32px);

Maps are key-value pairs, similar to objects or dictionaries:

/* style.scss */
$theme: (
  primary: #3498db,
  secondary: #2ecc71,
  bg: white,
  text: #333,
);

@each $key, $value in $theme {
  --color-#{$key}: #{$value};
}

You can access map values with map-get, merge with map-merge, check existence with map-has-key and iterate with @each.

Sass built-in functions

Sass ships with a rich standard library:

/* style.scss */
// Colour manipulation
$primary: #3498db;

.lighter { color: lighten($primary, 20%); }
.darker { color: darken($primary, 15%); }
.desaturated { color: desaturate($primary, 30%); }
.hue-shifted { color: adjust-hue($primary, 30deg); }

// Colour functions
$alpha: rgba($primary, 0.5);   // semi-transparent
$contrast: contrast-color($primary);  // contrast-safe colour

// Maths
$rounded: round(3.14);         // 3
$limited: clamp(1, 10, 5);     // 5
$half: math.div(100%, 2);      // 50%

// Strings
$name: to-upper-case('sass');   // SASS
$length: str-length('hello');   // 5
$joined: str-insert('hello', ' world', 6); // 'hello world'

// Lists
$items: append(1px, 2px, 3px, 4px);
$first: nth($items, 1);        // 1px
$count: length($items);        // 4

These functions are available globally in SCSS and through @use 'sass:color', @use 'sass:math', @use 'sass:string', @use 'sass:list' and @use 'sass:map'.

How to use Sass

Dart Sass

Dart Sass is the canonical and only actively maintained Sass implementation. Install it with:

npm install sass

Then compile files directly:

npx sass src/styles.scss dist/styles.css

Build tool integration

In most modern projects Sass is handled by your bundler. Vite, webpack, Parcel and esbuild all support Sass out of the box:

Vite — just install sass and import .scss files:

import './styles.scss';

webpack — add sass-loader:

module.exports = {
  module: { rules: [{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }] }
};

CLI compilation

# Watch mode — recompile on every save
npx sass --watch src:dist

# One compile
npx sass src/styles.scss dist/styles.css

# Output compressed CSS
npx sass --style=compressed src/styles.scss dist/styles.css

Best practices

  • Use SCSS syntax. It is CSS-compatible, widely adopted and far more readable than the indented syntax.
  • Use @use and @forward, never @import. The import system is deprecated and causes global scope pollution.
  • Organise with partials. One concern per file: _variables.scss, _mixins.scss, _buttons.scss, _layout.scss.
  • Namespace your modules. Use @use 'module' as var or let the default namespace (module.$var) clarify where values come from.
  • Use placeholders for shared patterns. %placeholder selectors only emit CSS when extended, keeping output lean.
  • Write parameterised mixins over extends. Mixins are more flexible and produce explicit, predictable CSS.
  • Keep business logic out of stylesheets. Sass functions and loops belong in your build pipeline, not in your component logic.
  • Lint your Sass. Use stylelint with a Sass plugin to catch deprecations and enforce conventions.
  • Output compressed CSS for production. Use --style=compressed or let your bundler minify.
  • Avoid deeply nested selectors. Two to three levels is enough. Deeper nesting usually signals a naming or structure problem.

Importing stylesheets

@use is the modern module system with explicit namespaces. @import is deprecated and leaks globals.

Prefer
@use "variables" as v;

.button {
  background: v.$brand;
}
Avoid
@import "variables";

.button {
  background: $brand;
}

Reusing declarations

A mixin takes parameters and can be included anywhere. Duplicating the block drifts out of sync.

Prefer
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

.hero {
  @include flex-center;
}
Avoid
.hero {
  display: flex;
  align-items: center;
  justify-content: center;
}
.footer {
  display: flex;
  align-items: center;
  justify-content: center;
}

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Sass / SCSS?

Our interactive tutorial walks you through Sass / SCSS step by step — with quizzes and real code you can run in the browser.