CSS SASS
SASS Tutorial
SASS is the most popular CSS preprocessor. It lets you write cleaner, more maintainable stylesheets using variables, nesting, reusable mixins and functions — then compiles down to plain CSS that every browser understands. This tutorial walks through every core feature with side-by-side SCSS and compiled CSS.
What is SASS (and SCSS)?
SASS stands for Syntactically Awesome Style Sheets. It is a preprocessor: you write in an enhanced syntax with extra features, and a compiler turns it into ordinary CSS. The browser never sees SASS — it only ever runs the compiled .css file.
SASS ships with two syntaxes. The older indented syntax uses file extension .sass and no braces or semicolons. The newer, far more common syntax is SCSS (.scss), which is a strict superset of CSS — every valid CSS file is already valid SCSS, so you can rename a .css file to .scss and start adding features gradually. This tutorial uses SCSS throughout.
Because SCSS is a superset of CSS, migrating is painless: rename styles.css to styles.scss and it still compiles. Then introduce variables and nesting one piece at a time.
How SASS compiles to CSS
You run a compiler (the Dart Sass CLI, or a build tool like Vite, Webpack or Parcel) that reads your .scss files and outputs .css. A typical command with the Dart Sass CLI looks like this:
# Install once (Node projects):
npm install -D sass
# Compile a single file
npx sass src/styles.scss dist/styles.css
# Watch for changes and recompile automatically
npx sass --watch src/styles.scss:dist/styles.cssVariables
SASS variables start with a dollar sign. They store colors, sizes, fonts or any value so you can reuse and change them from one place. Note that CSS now also has native custom properties (--name); SASS variables are resolved at compile time, while CSS variables live in the browser at runtime.
$brand: #0f766e;
$space: 16px;
$font-stack: system-ui, Arial, sans-serif;
.button {
background: $brand;
padding: $space;
font-family: $font-stack;
}.button {
background: #0f766e;
padding: 16px;
font-family: system-ui, Arial, sans-serif;
}Nesting
Nesting lets you write child selectors inside their parent, mirroring your HTML structure. The ampersand refers to the parent selector — handy for states like :hover and BEM-style modifiers. Keep nesting shallow (2–3 levels) to avoid overly specific, brittle CSS.
.card {
padding: 16px;
border-radius: 8px;
.title { font-weight: 700; }
&:hover { box-shadow: 0 6px 20px rgba(0,0,0,.12); }
&--featured { border: 2px solid #0f766e; }
}.card { padding: 16px; border-radius: 8px; }
.card .title { font-weight: 700; }
.card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.12); }
.card--featured { border: 2px solid #0f766e; }Partials and @use / @import
A partial is a small SCSS file whose name starts with an underscore (for example _buttons.scss). Partials are not compiled on their own; they are pulled into other files. The modern way to load a partial is @use, which namespaces its variables and mixins and only loads each file once. The old @import is deprecated in Dart Sass — prefer @use in new projects.
// _variables.scss
$brand: #0f766e;
$radius: 8px;
// main.scss
@use "variables" as v;
.badge {
background: v.$brand;
border-radius: v.$radius;
}With @use you access members through a namespace (v.$brand). Use @use "variables" as *; to import members without a prefix, but namespacing avoids naming collisions in larger codebases.
Mixins: @mixin and @include
A mixin is a reusable block of declarations you define once with @mixin and drop in anywhere with @include. Mixins can take arguments, including default values, which makes them perfect for repeated patterns like flex centering or media queries.
@mixin flex-center($dir: row) {
display: flex;
align-items: center;
justify-content: center;
flex-direction: $dir;
}
.hero { @include flex-center(column); }
.toolbar { @include flex-center; }.hero {
display: flex; align-items: center; justify-content: center;
flex-direction: column;
}
.toolbar {
display: flex; align-items: center; justify-content: center;
flex-direction: row;
}Functions
SASS ships built-in functions for color, math and strings, and you can write your own with @function that return a value. Common built-ins include lighten(), darken(), mix(), percentage() and math.div(). Custom functions keep computed values readable and reusable.
@use "sass:math";
@function rem($px) {
@return math.div($px, 16) * 1rem;
}
.title {
font-size: rem(24); // 1.5rem
color: darken(#0f766e, 10%);
}.title {
font-size: 1.5rem;
color: #0a5049;
}Inheritance with @extend and placeholders
@extend lets one selector inherit the styles of another, so shared rules are written once. Pair it with a placeholder selector (starting with %) that is never output on its own — only the selectors that extend it appear in the compiled CSS.
%alert-base {
padding: 12px 16px;
border-radius: 6px;
font-weight: 600;
}
.alert-success { @extend %alert-base; background: #dcfce7; color: #166534; }
.alert-error { @extend %alert-base; background: #fee2e2; color: #991b1b; }.alert-success, .alert-error {
padding: 12px 16px;
border-radius: 6px;
font-weight: 600;
}
.alert-success { background: #dcfce7; color: #166534; }
.alert-error { background: #fee2e2; color: #991b1b; }Prefer mixins over @extend when the shared code takes arguments or when you want to avoid the long, grouped selector lists that @extend can generate. @extend is best for simple, argument-free shared bases.
Operators and math
SASS supports arithmetic (+, -, *, /, %) and comparison operators. Division now uses the math.div() function to avoid ambiguity with the CSS slash. Math is handy for building spacing scales and fluid grids at compile time.
@use "sass:math";
$gutter: 24px;
.col-4 { width: math.div(100%, 3); } // 33.3333%
.stack > * + * { margin-top: math.div($gutter, 2); } // 12px
.wide { width: 200px + 50px; } // 250pxControl flow: @if, @each, @for
SASS can generate repetitive CSS with loops and conditionals. @each iterates over a list or map, @for runs a counted loop, and @if branches — all at compile time, so the output is still plain static CSS.
$sizes: (sm: 4px, md: 8px, lg: 16px);
@each $name, $value in $sizes {
.p-#{$name} { padding: $value; }
}.p-sm { padding: 4px; }
.p-md { padding: 8px; }
.p-lg { padding: 16px; }Quick reference
| Feature | Syntax | Purpose |
|---|---|---|
| Variable | $name: value; | Store and reuse a value |
| Nesting | .a { .b { } } | Write nested selectors |
| Parent ref | &:hover { } | Reference the enclosing selector |
| Partial | _file.scss | A reusable, non-compiled fragment |
| Load | @use "file"; | Import a partial (modern) |
| Mixin | @mixin / @include | Reusable, parameterised block |
| Function | @function / @return | Return a computed value |
| Inheritance | @extend %base; | Share a set of declarations |
SASS is optional sugar on top of CSS — everything it does compiles to standard CSS. Learn plain CSS first, then reach for SASS to keep large stylesheets organised and DRY.
