Sass / SCSS
Sass (Syntactically Awesome Style Sheets) is a CSS preprocessor: you write stylesheets in Sass syntax, and a compiler turns them into plain CSS. It added features CSS lacked for years — variables, nesting, reusable mixins, functions, loops, and modules — and powered frameworks like Bootstrap and countless design systems.
Sass has two syntaxes. SCSS (.scss) is a superset of CSS with braces and semicolons, so any valid CSS file is valid SCSS; it's what almost everyone uses. The original indented syntax (.sass) omits braces. The official implementation today is Dart Sass; older LibSass and Ruby Sass are deprecated.
Modern CSS now has custom properties, native nesting, color-mix(), and cascade layers, so you need Sass less than before. It remains valuable for design-token maps, generating utility classes, mixins, and large codebases already built on it.
TL;DR
- Write SCSS (
.scss); compile with Dart Sass (sassnpm package) or your bundler. - Use
@useand@forwardfor modules;@importis deprecated. - Variables (
$name) are compile-time; CSS custom properties (--name) are runtime — often use both. - Mixins reuse declaration blocks; functions compute values; maps and
@eachgenerate classes from tokens. - Keep nesting shallow (two or three levels) to avoid specificity bloat.
- Consider whether native CSS or a utility framework like Tailwind covers your needs first.
Quick Example
A small token module, a mixin, and a component using both:
Compile: npx sass src/styles/main.scss dist/main.css --style=compressed --source-map.
Core Concepts
Variables vs Custom Properties
A common pattern: define tokens as Sass maps, then emit them as custom properties for runtime theming (such as dark mode).
Nesting and the Parent Selector
Nesting mirrors HTML structure; & refers to the parent selector (&:hover, &--active, .theme-dark &). Native CSS now supports nesting too, with slightly different rules.
Modules: @use and @forward
@use "tokens" loads a module once and namespaces its members (tokens.$brand), preventing global name collisions. @forward re-exports members so a single entry point can expose a library's public API. Private members start with - or _. The old @import loads everything globally, can duplicate output, and is being removed from Dart Sass.
Mixins, Functions, and Placeholders
- Mixins (
@mixin/@include) output declarations, optionally with arguments and@contentblocks. - Functions (
@function/@return) compute values; built-in modules includesass:math,sass:color,sass:map,sass:list, andsass:string. - Placeholder selectors (
%namewith@extend) share rules without outputting the placeholder, but@extendcan create surprising selector combinations — prefer mixins in most cases.
Control Flow
@if/@else, @each, @for, and @while generate CSS programmatically — ideal for utility classes, theme variants, and grid systems.
Best Practices
Organize With Partials and an Entry File
Use partials (_buttons.scss) grouped into folders like abstracts/, base/, components/, and layouts/, and a single entry file that @uses them.
Keep Nesting Shallow
Deep nesting creates long, highly specific selectors that are hard to override. Limit to two or three levels and use BEM-style naming.
Separate Tokens From Components
Keep colors, spacing, and breakpoints in a token module (ideally generated from design tokens) so components never hard-code values.
Emit Custom Properties for Theming
Compile-time variables can't switch themes at runtime. Output themeable values as CSS custom properties.
Migrate From @import
Use the official sass-migrator tool to convert to @use/@forward before @import is removed.
Let the Bundler Compile
Vite, webpack, and Next.js compile .scss imports automatically once the sass package is installed.
Common Mistakes
Overusing @extend
Extending across files can generate enormous selector lists and unexpected cascade behavior.
Using Sass Variables for Runtime Themes
Dark mode built on $variables requires duplicating the entire stylesheet. Use custom properties.
Mirroring the DOM With Nesting
Nesting every level of the HTML ties styles to markup structure and inflates specificity.
Depending on Deprecated Implementations
node-sass (LibSass) is deprecated and lacks new features. Use Dart Sass (sass or sass-embedded).
Division With /
In modern Sass, / is a separator, not division. Use math.div($a, $b).
Comparison
FAQ
What's the difference between Sass and SCSS?
They're two syntaxes of the same language. SCSS uses CSS-like braces and semicolons and accepts any valid CSS. The older indented Sass syntax uses indentation instead. SCSS is far more common.
Do I still need Sass with modern CSS?
Often not for small projects — native CSS has variables, nesting, and powerful functions. Sass remains useful for token maps, generated utilities, mixins, and large existing codebases.
Why is @import deprecated?
It loads everything into a global namespace, can duplicate CSS, and makes dependencies unclear. The @use and @forward module system fixes these problems.
Can I use Sass with Tailwind?
You can, but it's usually unnecessary. Tailwind's configuration and CSS features cover most needs, and mixing preprocessors adds complexity.
Related Topics
- CSS — The language Sass compiles to
- Tailwind CSS — A utility-first alternative
- CSS-in-JS — Component-scoped styling in JavaScript
- Design Tokens — Shared values Sass maps often represent
- Build Tools & Bundlers — Where Sass compilation happens