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
- Sass/SCSS: Most popular, rich features, large community
- Variables: Store reusable values ($primary or @primary)
- Nesting: Mirror HTML structure, don't exceed 3 levels
- Mixins: Reusable style blocks with optional parameters
- Functions: Perform calculations and return values
- Partials: Organize code with @import or @use
- PostCSS: Plugin-based tool for transforming CSS
- Build tools: Integrate with Webpack, Vite, or standalone CLI