⚙️ CSS Preprocessors

Sass, Less & PostCSS

CSS Preprocessors Overview

Extend CSS with variables, nesting, mixins, functions, and more. Preprocessors compile to standard CSS, adding powerful features to your workflow.

💻 Sass/SCSS

Variables

/* Sass variables (start with $) */
$primary-color: #007bff;
$secondary-color: #6c757d;
$font-stack: system-ui, sans-serif;
$spacing-unit: 1rem;

.button {
    background: $primary-color;
    font-family: $font-stack;
    padding: $spacing-unit;
}

/* CSS Variables vs Sass Variables */
/* Sass: compile-time, not dynamic */
$theme-color: blue;

/* CSS: runtime, can change with JavaScript */
:root {
    --theme-color: blue;
}

Nesting

/* Sass nesting */
.card {
    padding: 1rem;
    border-radius: 8px;
    
    &__title {
        font-size: 1.5rem;
        margin-bottom: 0.5rem;
    }
    
    &__content {
        color: #666;
    }
    
    &:hover {
        box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    }
    
    &--featured {
        border: 2px solid $primary-color;
    }
}

/* Compiles to */
.card {
    padding: 1rem;
    border-radius: 8px;
}
.card__title {
    font-size: 1.5rem;
    margin-bottom: 0.5rem;
}
.card__content {
    color: #666;
}
.card:hover {
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.card--featured {
    border: 2px solid #007bff;
}

/* Nested media queries */
.container {
    width: 100%;
    padding: 1rem;
    
    @media (min-width: 768px) {
        padding: 2rem;
    }
    
    @media (min-width: 1024px) {
        max-width: 1200px;
        margin: 0 auto;
    }
}

Mixins

/* Define reusable styles */
@mixin flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
}

@mixin button-variant($bg-color, $text-color) {
    background: $bg-color;
    color: $text-color;
    
    &:hover {
        background: darken($bg-color, 10%);
    }
}

/* Use mixins */
.centered-box {
    @include flex-center;
    height: 200px;
}

.btn-primary {
    @include button-variant(#007bff, white);
}

.btn-danger {
    @include button-variant(#dc3545, white);
}

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

.sidebar {
    width: 100%;
    
    @include respond-to(tablet) {
        width: 250px;
    }
}

Functions

/* Built-in functions */
$base-color: #007bff;

.element {
    background: $base-color;
    border: 1px solid darken($base-color, 10%);
    color: lighten($base-color, 40%);
}

/* Custom functions */
@function px-to-rem($px) {
    @return $px / 16px * 1rem;
}

.text {
    font-size: px-to-rem(20px);  /* 1.25rem */
    padding: px-to-rem(24px);    /* 1.5rem */
}

/* Color functions */
.colors {
    /* Lighten/darken */
    color: lighten(#007bff, 20%);
    background: darken(#007bff, 20%);
    
    /* Saturate/desaturate */
    border-color: saturate(#007bff, 20%);
    
    /* Adjust hue */
    box-shadow: 0 0 10px adjust-hue(#007bff, 90deg);
    
    /* Mix colors */
    background: mix(#007bff, white, 50%);
    
    /* Transparency */
    background: rgba(#007bff, 0.5);
    border-color: transparentize(#007bff, 0.5);
}

Partials & Import

/* Organize code into partials */
/* _variables.scss */
$primary-color: #007bff;
$spacing-unit: 1rem;

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

/* _buttons.scss */
.btn {
    padding: $spacing-unit;
    border-radius: 4px;
}

/* main.scss */
@import 'variables';
@import 'mixins';
@import 'buttons';

/* Modern: @use instead of @import */
/* _variables.scss */
$primary-color: #007bff;

/* main.scss */
@use 'variables' as vars;

.button {
    background: vars.$primary-color;
}

Operators & Control Directives

/* Math operators */
$base-spacing: 1rem;

.element {
    padding: $base-spacing * 2;        /* 2rem */
    margin: $base-spacing / 2;         /* 0.5rem */
    width: 100% / 3;                   /* 33.333% */
}

/* @if / @else */
@mixin theme-colors($theme) {
    @if $theme == dark {
        background: #1a1a1a;
        color: white;
    } @else if $theme == light {
        background: white;
        color: #333;
    } @else {
        background: gray;
        color: black;
    }
}

.dark-mode {
    @include theme-colors(dark);
}

/* @for loop */
@for $i from 1 through 5 {
    .m-#{$i} {
        margin: #{$i * 0.5}rem;
    }
}

/* Generates:
.m-1 { margin: 0.5rem; }
.m-2 { margin: 1rem; }
.m-3 { margin: 1.5rem; }
.m-4 { margin: 2rem; }
.m-5 { margin: 2.5rem; }
*/

/* @each loop */
$colors: (primary: #007bff, secondary: #6c757d, danger: #dc3545);

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

/* @while loop */
$i: 1;
@while $i <= 4 {
    .col-#{$i} {
        width: 25% * $i;
    }
    $i: $i + 1;
}

🎨 Less

/* Less is similar to Sass with some differences */

/* Variables (use @) */
@primary-color: #007bff;
@spacing-unit: 1rem;

/* Nesting */
.card {
    padding: @spacing-unit;
    
    &__title {
        font-size: 1.5rem;
    }
    
    &:hover {
        box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    }
}

/* Mixins */
.flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
}

.centered-box {
    .flex-center();  /* Call mixin */
}

/* Parametric mixins */
.button-variant(@bg, @text) {
    background: @bg;
    color: @text;
    
    &:hover {
        background: darken(@bg, 10%);
    }
}

.btn-primary {
    .button-variant(#007bff, white);
}

/* Operations */
.element {
    width: 100% / 3;
    padding: @spacing-unit * 2;
}

/* Functions */
.colors {
    background: lighten(@primary-color, 20%);
    border: 1px solid darken(@primary-color, 10%);
}

/* Guards (conditionals) */
.mixin(@a) when (@a > 10) {
    background: blue;
}

.mixin(@a) when (@a <= 10) {
    background: red;
}

/* Import */
@import "variables.less";
@import "mixins.less";

🔧 PostCSS

/* PostCSS is a tool for transforming CSS with plugins */

/* Autoprefixer - adds vendor prefixes */
/* Input */
.box {
    display: flex;
    transition: all 0.3s;
}

/* Output */
.box {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}

/* postcss-preset-env - use future CSS today */
/* Input */
.element {
    color: color-mod(blue alpha(50%));
    font-size: clamp(1rem, 5vw, 3rem);
}

/* cssnano - minification */
/* Input */
.box {
    margin: 10px 10px 10px 10px;
    color: #ff0000;
}

/* Output */
.box{margin:10px;color:red}

/* postcss-nested - Sass-like nesting */
.card {
    padding: 1rem;
    
    &__title {
        font-size: 1.5rem;
    }
    
    @media (min-width: 768px) {
        padding: 2rem;
    }
}

/* PostCSS config (postcss.config.js) */
module.exports = {
    plugins: [
        require('postcss-import'),
        require('postcss-nested'),
        require('autoprefixer'),
        require('cssnano')
    ]
}

/* Tailwind CSS uses PostCSS */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
    .btn-primary {
        @apply bg-blue-500 text-white px-4 py-2 rounded;
    }
}

🎯 Comparison

/* Feature comparison */

/* Sass/SCSS */
// Most popular
// Two syntaxes: .sass (indented) and .scss (CSS-like)
// Rich feature set
// Large community
// npm install sass

/* Less */
// Similar to Sass
// Easier learning curve
// Used by Bootstrap 3 (moved to Sass in v4)
// npm install less

/* PostCSS */
// Not a preprocessor, but a tool
// Plugin-based architecture
// Can do everything others do + more
// Future CSS features today
// npm install postcss

/* Native CSS (Modern browsers) */
// Variables (custom properties)
// Nesting (upcoming)
// calc(), min(), max(), clamp()
// No build step needed

💡 Practical Examples

/* Complete Sass component */
/* _variables.scss */
$primary: #007bff;
$secondary: #6c757d;
$spacing: 1rem;
$border-radius: 4px;

/* _mixins.scss */
@mixin button-base {
    display: inline-block;
    padding: $spacing;
    border: none;
    border-radius: $border-radius;
    cursor: pointer;
    transition: all 0.3s;
}

@mixin button-variant($bg) {
    @include button-base;
    background: $bg;
    color: white;
    
    &:hover {
        background: darken($bg, 10%);
        transform: translateY(-2px);
        box-shadow: 0 4px 8px rgba(0,0,0,0.2);
    }
    
    &:active {
        transform: translateY(0);
    }
    
    &:disabled {
        background: lighten($bg, 20%);
        cursor: not-allowed;
        
        &:hover {
            transform: none;
            box-shadow: none;
        }
    }
}

/* _buttons.scss */
.btn {
    @include button-base;
}

.btn-primary {
    @include button-variant($primary);
}

.btn-secondary {
    @include button-variant($secondary);
}

/* Generate size variants */
$sizes: (
    sm: 0.5rem,
    md: 1rem,
    lg: 1.5rem
);

@each $name, $size in $sizes {
    .btn-#{$name} {
        padding: $size;
        font-size: #{$size * 0.875};
    }
}

/* Responsive grid system */
$breakpoints: (
    sm: 576px,
    md: 768px,
    lg: 1024px,
    xl: 1440px
);

@mixin respond-above($breakpoint) {
    @if map-has-key($breakpoints, $breakpoint) {
        $value: map-get($breakpoints, $breakpoint);
        @media (min-width: $value) {
            @content;
        }
    }
}

.container {
    width: 100%;
    padding: $spacing;
    
    @include respond-above(md) {
        padding: $spacing * 2;
    }
    
    @include respond-above(lg) {
        max-width: 1200px;
        margin: 0 auto;
    }
}

/* Generate utility classes */
$spacings: (0, 1, 2, 3, 4, 5);

@each $size in $spacings {
    .m-#{$size} { margin: #{$size * 0.5}rem; }
    .mt-#{$size} { margin-top: #{$size * 0.5}rem; }
    .mr-#{$size} { margin-right: #{$size * 0.5}rem; }
    .mb-#{$size} { margin-bottom: #{$size * 0.5}rem; }
    .ml-#{$size} { margin-left: #{$size * 0.5}rem; }
    .p-#{$size} { padding: #{$size * 0.5}rem; }
}

/* Theme system */
$themes: (
    light: (
        bg: white,
        text: #333,
        border: #ddd
    ),
    dark: (
        bg: #1a1a1a,
        text: #f0f0f0,
        border: #333
    )
);

@each $theme-name, $theme-colors in $themes {
    .theme-#{$theme-name} {
        background: map-get($theme-colors, bg);
        color: map-get($theme-colors, text);
        border-color: map-get($theme-colors, border);
    }
}

/* main.scss */
@import 'variables';
@import 'mixins';
@import 'buttons';

🚀 Setup & Usage

/* Sass Setup */
# Install Sass
npm install -g sass

# or as dev dependency
npm install --save-dev sass

# Compile single file
sass input.scss output.css

# Watch for changes
sass --watch input.scss:output.css

# Watch directory
sass --watch scss:css

# Compressed output
sass --style=compressed input.scss output.css

/* package.json scripts */
{
    "scripts": {
        "sass": "sass scss:css",
        "sass:watch": "sass --watch scss:css",
        "sass:build": "sass --style=compressed scss:css"
    }
}

/* Less Setup */
# Install Less
npm install -g less

# or as dev dependency
npm install --save-dev less

# Compile
lessc styles.less styles.css

# Minified
lessc --clean-css styles.less styles.css

/* PostCSS Setup */
# Install PostCSS and plugins
npm install --save-dev postcss postcss-cli autoprefixer

# postcss.config.js
module.exports = {
    plugins: [
        require('autoprefixer'),
        require('cssnano')
    ]
}

# Run PostCSS
postcss input.css -o output.css

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

// Vite (supports Sass out of the box)
npm install --save-dev sass

// Import in JS
import './styles.scss';

/* VS Code Extensions */
// Live Sass Compiler
// Sass (.sass only)
// SCSS IntelliSense

💡 Best Practices

/* 1. Organize files logically */
/* Use 7-1 pattern or similar structure */

/* 2. Don't nest too deeply */
/* Bad - hard to read, high specificity */
.nav {
    ul {
        li {
            a {
                span {
                    color: blue;
                }
            }
        }
    }
}

/* Good - max 3 levels */
.nav {
    a {
        color: blue;
    }
}

/* 3. Use variables for everything reusable */
$primary-color: #007bff;
$spacing-unit: 1rem;
$border-radius: 4px;

/* 4. Create reusable mixins */
@mixin truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 5. Avoid @extend (use mixins or utility classes) */
/* @extend can create unpredictable output */

/* 6. Use placeholder selectors for shared styles */
%button-base {
    padding: 1rem;
    border-radius: 4px;
}

.btn-primary {
    @extend %button-base;
    background: blue;
}

/* 7. Comment complex mixins */
/// Generates responsive breakpoint mixins
/// @param {String} $breakpoint - Breakpoint name
@mixin respond-above($breakpoint) { }

/* 8. Use functions for calculations */
@function rem($px) {
    @return $px / 16px * 1rem;
}

/* 9. Keep compiled CSS readable during development */
sass --watch --style=expanded input.scss:output.css

/* 10. Minify for production */
sass --style=compressed input.scss:output.css

🎯 Key Takeaways