CSS Preprocessors: Sass, Less, and Stylus
CSS preprocessors extend CSS with programming features — variables, nesting, mixins, functions, and modular file organization — that compile down to standard CSS. They eliminate repetition, improve maintainability, and bring structure to stylesheets.
What Is a Preprocessor?
A preprocessor takes a custom syntax and compiles it to plain CSS:
Source (Sass/Less/Stylus) → Preprocessor → Output CSSAll three preprocessors offer similar features. The choice often comes down to ecosystem and team preference.
Sass (Syntactically Awesome Style Sheets)
Sass is the most widely used CSS preprocessor. Two syntaxes:
- SCSS (
.scss): Superset of CSS, most popular - Indented syntax (
.sass): Uses indentation instead of braces
Variables
$primary-color: #3498db;
$spacing-unit: 8px;
$font-stack: 'Inter', system-ui, sans-serif;
.header {
background-color: $primary-color;
padding: $spacing-unit * 2;
font-family: $font-stack;
---Nesting
.navbar {
background: #333;
padding: 1rem;
.nav-list {
display: flex;
list-style: none;
margin: 0;
}
.nav-item {
margin-right: 1rem;
a {
color: white;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
&--dark {
background: #111;
}
&__link {
font-weight: 600;
}
---Mixins
@mixin flex-center($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: center;
---
.card {
@include flex-center(column);
padding: 2rem;
---
.hero {
@include flex-center;
min-height: 80vh;
---Functions
@function rem($px, $base: 16px) {
@return ($px / $base) * 1rem;
---
@function contrast-color($bg) {
@if lightness($bg) > 50% {
@return #000;
} @else {
@return #fff;
}
---
.element {
font-size: rem(24);
color: contrast-color($primary-color);
---Partials and Imports
In modern Dart Sass, use @use and @forward instead of @import:
// _variables.scss
$primary: #3498db !default;
// main.scss
@use 'variables' as v;
@use 'mixins';
.header {
color: v.$primary;
@include mixins.card;
---Less
Less is the precursor to many Sass features. Variables use @ instead of $. Mixins use class selectors without a @mixin keyword.
@primary-color: #3498db;
@spacing: 8px;
.flex-center(@direction: row) {
display: flex;
flex-direction: @direction;
align-items: center;
justify-content: center;
---
.container {
.flex-center(column);
---Less integrates naturally with CSS syntax — valid CSS is valid Less. The .less extension is required, but the syntax is closer to standard CSS than SCSS.
Stylus
Stylus has the tersest syntax — braces, colons, and semicolons are all optional.
primary-color = #3498db
flex-center(direction = row)
display flex
flex-direction direction
align-items center
justify-content center
.container
flex-center(column)Stylus also supports transparent mixins (functions that look like properties) and interpolation for vendor prefixes. Its free-form syntax appeals to developers who value conciseness.
Comparison
| Feature | Sass (SCSS) | Less | Stylus | |
Beyond Variables and Nesting
Modern CSS preprocessors (Sass, Less) offer advanced features that significantly improve stylesheet maintainability:
Mixins with Logic
Sass mixins can include conditional logic and loops, making them far more powerful than simple property groups:
@mixin responsive-spacing($property, $min, $max) {
#{$property}: $min;
@media (min-width: 768px) {
#{$property}: calc(#{$min} + (#{$max} - #{$min}) * ((100vw - 768px) / (1440 - 768)));
}
@media (min-width: 1440px) {
#{$property}: $max;
}
---
.card {
@include responsive-spacing(padding, 1rem, 3rem);
---Placeholder Selectors
Placeholder selectors (%) define styles that are only output when extended, avoiding class duplication in compiled CSS:
%card-base {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
---
.product-card { @extend %card-base; background: white; }
.article-card { @extend %card-base; background: #f5f5f5; }Color Functions
Preprocessors provide powerful color manipulation functions that are not yet available in native CSS:
$primary: #3498db;
.button {
background: $primary;
&:hover {
background: darken($primary, 10%);
}
&:active {
background: darken($primary, 15%);
}
&--secondary {
background: lighten($primary, 30%);
color: $primary;
}
---Build Tool Integration
Modern build tools like Vite, Webpack, and Parcel handle Sass compilation seamlessly with minimal configuration:
# Vite — install the preprocessor, Vite handles compilation
npm install -D sass
# Webpack with sass-loader
npm install -D sass sass-loader css-loader style-loaderRename your .css files to .scss and the build pipeline processes them automatically. For production, all tools include minification and vendor prefixing through PostCSS autoprefixer.
Modular Architecture with Partials
Organize Sass files following the 7-1 architecture pattern popularized by Hugo Giraudel:
// abstracts/ — variables, functions, mixins
// vendors/ — third-party CSS
// base/ — reset, typography
// layout/ — grid, header, footer
// components/ — buttons, cards, modals
// pages/ — page-specific styles
// themes/ — theme variablesThe main file imports all partials in order, with variables first and overrides last:
@use 'abstracts/variables';
@use 'abstracts/functions';
@use 'abstracts/mixins';
@use 'vendors/normalize';
@use 'base/reset';
@use 'layout/grid';
@use 'components/button';
// ...The @use rule (Sass 3.x+) creates namespaced imports, avoiding the global namespace pollution of @import. This prevents naming conflicts and makes dependency tracking explicit.
Performance Implications
Preprocessors add a build step. For small projects, the build overhead is negligible. For large projects with thousands of partials and deep nesting, compile times can reach seconds. Consider:
- Limiting nesting depth to 3-4 levels maximum
- Avoiding mixins with large amounts of generated CSS (use
@extendor CSS custom properties instead) - Using
purgingto remove unused CSS in production (PurgeCSS, Tailwind’s built-in purging) - Precompiling critical CSS inline and deferring the rest
PostCSS and Autoprefixing
PostCSS transforms CSS with JavaScript plugins. Combined with Autoprefixer, it ensures cross-browser compatibility without manual vendor prefixes:
npm install -D postcss autoprefixer postcss-cli// postcss.config.js
module.exports = {
plugins: [
require("autoprefixer"),
require("postcss-preset-env")({ stage: 2 }),
],
---;PostCSS runs as part of the build pipeline in most modern toolchains (Vite, Webpack with postcss-loader). Autoprefixer uses data from Can I Use to determine which vendor prefixes are needed for the browser support targets defined in your project’s browserslist configuration.
CSS Next: Future Features Today
PostCSS plugins enable future CSS features today. postcss-preset-env converts modern CSS (custom properties, nesting, oklch() colors, :has() selector) to browser-compatible CSS. This allows you to write modern CSS while supporting older browsers. As browsers implement these features natively, you can drop the PostCSS transforms without changing your source CSS.
—|—|—|—|
| Variables | $var | @var | var (no prefix) |
| Nesting | & | & | & |
| Mixins | @mixin / @include | Class selectors | Direct calls |
| Functions | @function | Less.js functions | Native functions |
| Conditionals | @if / @else | Guards | if / unless |
| Loops | @each / @for | Not built-in | for / while |
| Extend | @extend | :extend() | @extend |
| Ecosystem | Largest | Moderate | Smallest |
Build Integration
CSS preprocessors require build tool integration. Webpack uses sass-loader, less-loader, or stylus-loader. Vite has built-in support — just install the preprocessor package. The @use directive replaces @import in modern Sass, providing better encapsulation and namespace control.
Preprocessor vs PostCSS
PostCSS transforms CSS with JavaScript plugins rather than extending CSS syntax. It can import files (postcss-import), add vendor prefixes (autoprefixer), nest selectors (postcss-nesting), and use future CSS features (postcss-preset-env). The choice between preprocessors and PostCSS depends on team preference — both handle variables, nesting, and mixins, but PostCSS uses standards-track syntax while preprocessors introduce custom syntax.
Best Practices
- Use a naming convention: BEM with SCSS nesting (
block__element--modifier) - Limit nesting depth: 3 levels max — deeper generates overly specific selectors
- Avoid over-mixing: Do not create mixins for every repeated property
- Use variables for design tokens: Colors, spacing, fonts, breakpoints
- Organize by component: One partial per component (
_card.scss,_navbar.scss) - Output sensible CSS: Review compiled CSS for selector bloat
Summary
Sass is the industry standard with the largest ecosystem. Less is simpler and integrates naturally with CSS syntax. Stylus offers the most concise syntax. All three provide variables, nesting, mixins, and modular organization. Choose Sass for broad compatibility and tooling, Less for simple projects, and Stylus if you prefer minimal syntax.
FAQ
What is the difference between SCSS and Sass? SCSS (Sassy CSS) uses curly braces and semicolons — it is a superset of CSS. Sass (the indented syntax) uses indentation instead of braces. SCSS is more popular because it is easier to adopt incrementally in existing CSS projects.
Should I use @import or @use in Sass? Use @use and @forward. The @import directive is deprecated in Dart Sass. @use provides namespace control, better encapsulation, and prevents variable name conflicts.
Can I use CSS preprocessors with CSS-in-JS? No. CSS preprocessors work with CSS files and compile to standard CSS. CSS-in-JS libraries handle variables, nesting, and logic through JavaScript — they do not need a preprocessing step.
What is the learning curve for each preprocessor? SCSS is easiest if you know CSS — valid CSS is valid SCSS. Less has the gentlest learning curve because it stays close to CSS syntax. Stylus has the steepest curve because its syntax is significantly different.
Related: CSS Variables Guide | Responsive Design
Related Concepts and Further Reading
Understanding css preprocessors requires familiarity with several interconnected ideas and principles that together form a complete picture. Exploring these related concepts deepens your knowledge and provides context that makes the core material more meaningful and applicable. Each concept builds on the others, creating a web of understanding that supports deeper learning and practical application. Taking time to explore how these elements connect reveals patterns that accelerate comprehension and retention of new information.
The relationship between css preprocessors and adjacent fields is worth particular attention. Many of the most important insights emerge at the boundaries between disciplines, where ideas from different areas combine to create new approaches and solutions that neither field could produce alone. Exploring these connections pays dividends in both breadth and depth of understanding, revealing patterns and principles that might otherwise remain hidden from view. Cross-disciplinary knowledge is increasingly valued as problems become more complex and interconnected.
For those looking to go beyond introductory material, several excellent resources provide deeper treatment of specific aspects of css preprocessors. Academic journals, industry publications, authoritative reference works, and online courses each offer different perspectives and levels of detail. The key is to match your reading to your current learning goals and build knowledge progressively, focusing on quality over quantity in your study materials. A well-chosen resource that matches your current level is worth more than dozens of resources that are too basic or too advanced.