Skip to content
Home
CSS Preprocessors: Sass, Less, and Stylus

CSS Preprocessors: Sass, Less, and Stylus

7 min read

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 CSS

All 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-loader

Rename 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 variables

The 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 @extend or CSS custom properties instead)
  • Using purging to 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

  1. Use a naming convention: BEM with SCSS nesting (block__element--modifier)
  2. Limit nesting depth: 3 levels max — deeper generates overly specific selectors
  3. Avoid over-mixing: Do not create mixins for every repeated property
  4. Use variables for design tokens: Colors, spacing, fonts, breakpoints
  5. Organize by component: One partial per component (_card.scss, _navbar.scss)
  6. 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

#web-dev#css#preprocessors#sass#less