PostCSS vs Lightning CSS vs cssnano: CSS Processing and Minification (2026)
TL;DR
PostCSS is the CSS transformation framework — plugin-based architecture with 350+ plugins (Autoprefixer, Tailwind CSS, cssnano), the most widely used CSS processing tool. Lightning CSS is the Rust-based CSS parser/transformer — handles autoprefixing, nesting, minification, and bundling in a single tool, 100x faster than PostCSS. cssnano is the PostCSS-based CSS minifier — modular optimization presets, removes unused code, merges rules, the most popular CSS minifier. In 2026: PostCSS for plugin ecosystem, Lightning CSS for speed and all-in-one processing, cssnano for CSS-only minification.
Key Takeaways
- PostCSS: ~100M weekly downloads — plugin framework, powers Autoprefixer + Tailwind + cssnano
- Lightning CSS: ~15M weekly downloads — Rust-native, all-in-one (prefix + nest + minify + bundle)
- cssnano: ~25M weekly downloads — CSS minifier, PostCSS plugin, modular presets
- PostCSS is a framework; Lightning CSS and cssnano are tools
- Lightning CSS replaces PostCSS + Autoprefixer + cssnano in one tool
- Vite uses Lightning CSS as an optional CSS processor (replacing PostCSS)
PostCSS
PostCSS — CSS transformation framework:
Basic usage
import postcss from "postcss"
import autoprefixer from "autoprefixer"
import cssnano from "cssnano"
const css = `
.container {
display: flex;
user-select: none;
&:hover {
color: oklch(0.7 0.15 200);
}
}
`
const result = await postcss([
autoprefixer(),
cssnano({ preset: "default" }),
]).process(css, { from: "input.css", to: "output.css" })
console.log(result.css)
// → .container{display:flex;-webkit-user-select:none;user-select:none}
// → .container:hover{color:oklch(.7 .15 200)}
Popular plugins
// postcss.config.js
module.exports = {
plugins: [
// Autoprefixer — add vendor prefixes:
require("autoprefixer"),
// Nesting — CSS nesting support:
require("postcss-nesting"),
// Custom properties fallback:
require("postcss-custom-properties"),
// Import — inline @import:
require("postcss-import"),
// Preset Env — use future CSS today:
require("postcss-preset-env")({
stage: 2,
features: {
"nesting-rules": true,
"custom-media-queries": true,
},
}),
// Minification:
require("cssnano")({ preset: "default" }),
],
}
With Tailwind CSS
// postcss.config.js
module.exports = {
plugins: {
"tailwindcss": {},
"autoprefixer": {},
},
}
// Tailwind CSS is built as a PostCSS plugin:
// 1. PostCSS parses your CSS
// 2. Tailwind plugin generates utility classes
// 3. Autoprefixer adds vendor prefixes
// 4. cssnano minifies (in production)
Build tool integration
// Vite — PostCSS config auto-detected:
// Just create postcss.config.js and Vite uses it
// Webpack:
module.exports = {
module: {
rules: [{
test: /\.css$/,
use: [
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"autoprefixer",
"cssnano",
],
},
},
},
],
}],
},
}
// Next.js — postcss.config.js auto-detected
Writing a PostCSS plugin
import { Plugin } from "postcss"
const myPlugin: Plugin = {
postcssPlugin: "postcss-add-dark-mode",
Rule(rule) {
// Add dark mode variant for every color declaration:
rule.walkDecls("color", (decl) => {
const darkRule = rule.clone()
darkRule.selectors = darkRule.selectors.map(
(sel) => `.dark ${sel}`
)
darkRule.removeAll()
darkRule.append(decl.clone({ value: "white" }))
rule.parent?.insertAfter(rule, darkRule)
})
},
}
Lightning CSS
Lightning CSS — Rust-based CSS processor:
Basic usage
import { transform, bundle } from "lightningcss"
// Transform a single file:
const { code, map } = transform({
filename: "style.css",
code: Buffer.from(`
.container {
display: flex;
user-select: none;
&:hover {
color: oklch(0.7 0.15 200);
}
}
`),
minify: true,
sourceMap: true,
targets: {
chrome: 95 << 16, // Chrome 95+
firefox: 90 << 16, // Firefox 90+
safari: 15 << 16, // Safari 15+
},
})
console.log(code.toString())
// → .container{display:flex;-webkit-user-select:none;user-select:none}.container:hover{color:oklch(.7 .15 200)}
All-in-one processing
import { transform } from "lightningcss"
// Lightning CSS handles everything PostCSS + plugins does:
const { code } = transform({
filename: "style.css",
code: Buffer.from(css),
minify: true,
// Browser targets (replaces Autoprefixer):
targets: {
chrome: 90 << 16,
firefox: 88 << 16,
safari: 14 << 16,
},
// CSS Modules:
cssModules: true,
// Drafts (enable experimental features):
drafts: {
customMedia: true,
},
// Nesting (built-in, replaces postcss-nesting):
// Automatically lowered based on targets
// Custom properties (built-in):
// Automatically lowered based on targets
// Color functions (built-in):
// oklch, lab, lch converted based on targets
})
CSS bundling
import { bundle } from "lightningcss"
// Bundle CSS with @import resolution:
const { code, map } = bundle({
filename: "src/main.css",
minify: true,
targets: {
chrome: 95 << 16,
},
})
// main.css:
// @import "./reset.css";
// @import "./components/button.css";
// @import "./utilities.css";
//
// → All imports inlined and bundled into one file
CSS Modules
import { transform } from "lightningcss"
const { code, exports } = transform({
filename: "Button.module.css",
code: Buffer.from(`
.button {
background: blue;
color: white;
}
.primary {
composes: button;
background: green;
}
`),
cssModules: true,
minify: true,
})
console.log(exports)
// → {
// button: { name: "Button_button_abc123", composes: [] },
// primary: { name: "Button_primary_def456", composes: [{ type: "local", name: "Button_button_abc123" }] },
// }
With Vite
// vite.config.ts
export default {
css: {
transformer: "lightningcss", // Use Lightning CSS instead of PostCSS
lightningcss: {
targets: {
chrome: 95 << 16,
firefox: 90 << 16,
safari: 15 << 16,
},
drafts: {
customMedia: true,
},
},
},
build: {
cssMinify: "lightningcss", // Use Lightning CSS for minification too
},
}
// No postcss.config.js needed!
// Lightning CSS handles prefixing, nesting, minification
cssnano
cssnano — CSS minifier:
Basic usage
import postcss from "postcss"
import cssnano from "cssnano"
const css = `
.container {
margin: 10px 20px 10px 20px;
color: #ff0000;
font-weight: bold;
background-color: rgba(0, 0, 0, 0.5);
}
.unused { }
/* This is a comment */
`
const result = await postcss([
cssnano({ preset: "default" }),
]).process(css)
console.log(result.css)
// → .container{margin:10px 20px;color:red;font-weight:700;background-color:rgba(0,0,0,.5)}
// Comments removed, shorthand properties, color shortening, etc.
Presets
// Default preset — safe optimizations:
cssnano({ preset: "default" })
// Advanced preset — more aggressive (may break some CSS):
cssnano({ preset: "advanced" })
// Lite preset — minimal, fastest:
cssnano({ preset: "lite" })
// Custom configuration:
cssnano({
preset: ["default", {
discardComments: { removeAll: true },
normalizeWhitespace: true,
colormin: true,
minifyFontValues: true,
minifyGradients: true,
reduceTransforms: true,
svgo: true,
calc: true,
// Disable specific optimizations:
zindex: false, // Don't rebase z-index
discardUnused: false, // Keep unused @keyframes
}],
})
Optimizations explained
cssnano optimizations:
Color minification:
#ff0000 → red
#ffffff → #fff
rgba(0,0,0,0.5) → rgba(0,0,0,.5)
rgb(255,0,0) → red
Shorthand merging:
margin: 10px 20px 10px 20px → margin: 10px 20px
padding: 5px 5px 5px 5px → padding: 5px
Value normalization:
font-weight: bold → font-weight: 700
font-weight: normal → font-weight: 400
Duplicate removal:
Removes duplicate declarations and rules
Calc simplification:
calc(2 * 50px) → 100px
calc(100% - 0px) → 100%
SVG minification:
Optimizes inline SVG in background-image
Comment removal:
Strips all comments (configurable)
With build tools
// Vite — cssnano used automatically in production:
// vite.config.ts
export default {
// cssnano is the default CSS minifier in Vite
// Or use postcss.config.js:
// plugins: [cssnano({ preset: "default" })]
}
// Webpack:
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin({
minimizerOptions: {
preset: ["default", { discardComments: { removeAll: true } }],
},
}),
],
},
}
// Next.js — cssnano used automatically in production builds
Feature Comparison
| Feature | PostCSS | Lightning CSS | cssnano |
|---|---|---|---|
| Purpose | CSS framework | All-in-one processor | CSS minifier |
| Language | JavaScript | Rust (WASM/native) | JavaScript |
| Speed | Slow | Very fast (100x) | Medium |
| Autoprefixing | Via plugin | ✅ built-in | ❌ |
| CSS nesting | Via plugin | ✅ built-in | ❌ |
| Minification | Via cssnano plugin | ✅ built-in | ✅ |
| CSS Modules | Via plugin | ✅ built-in | ❌ |
| Bundling (@import) | Via plugin | ✅ built-in | ❌ |
| Plugin ecosystem | 350+ plugins | ❌ | Modular presets |
| Custom transforms | ✅ (plugin API) | ✅ (visitor API) | ❌ |
| Used by | Tailwind, Next.js | Vite (optional) | Webpack, Vite |
| Weekly downloads | ~100M | ~15M | ~25M |
When to Use Each
Use PostCSS if:
- Need Tailwind CSS or other PostCSS plugins
- Want the largest plugin ecosystem
- Need custom CSS transforms (write your own plugin)
- Speed is not a bottleneck in your build
Use Lightning CSS if:
- Want maximum build speed
- Need autoprefixing + nesting + minification in one tool
- Using Vite and want to replace PostCSS
- Don't need PostCSS-specific plugins (like Tailwind)
Use cssnano if:
- Need CSS minification only (not processing/transforms)
- Already using PostCSS and want to add minification
- Want modular, configurable optimization presets
- Using Webpack with css-minimizer-webpack-plugin
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on PostCSS v8.x, Lightning CSS v1.x, and cssnano v7.x.