PostCSS vs Lightning CSS vs cssnano
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.
Compare CSS tooling and build utilities on PkgPulse →
Compare LightningCSS and PostCSS package health on PkgPulse.