CSS Minification Guide: CSSO, Unused CSS, Builds

CSS Minification Guide: CSSO, Unused CSS, Builds

How to minify CSS in 2026: CSSO (and friends), unused-CSS removal, Vite/Webpack, vs an online minifier. Honest limits before you chase bytes.

29.10.2025
12 min read
Share this article:
CSS
Minification
Performance
Optimization
CSSO
Tutorial

Why minify CSS in 2026?

Minifying CSS is not the same job as deleting unused rules, extracting critical CSS, or enabling gzip/Brotli. Minification rewrites the stylesheet you already ship into a shorter form: comments and whitespace go away, colors and zeros get shorter, adjacent duplicate selectors can merge. Class names stay the contract with your HTML — unlike JavaScript mangling. You can try that step in the browser with FastMinify’s online CSS minifier (CSSO, no upload). Production apps still belong in the build. This guide covers CSSO as it actually works, unused-CSS tools that replaced PurifyCSS, Webpack/Vite, and when a paste box is enough.

Handwritten sheets often shrink about 20–40% of original bytes before gzip or Brotli (same order of magnitude as our CSS tool docs — not a ranking guarantee)
Smaller render-blocking CSS can help LCP; minification alone will not fix a 2 MB hero image or a late font
Unused-CSS removal (PurgeCSS, Tailwind content scan) is a different, often larger, win on framework CSS
Online CSSO is for a file or a few files; the build repeats the same pipeline on every deploy
Compression on the wire is still a server/CDN layer — see gzip/Brotli after you minify

Minify vs unused CSS vs compression

Three layers, three tools

A “CSS went from 1.8 MB to 420 KB” story is almost never minify-only: that drop is unused-CSS removal on a framework dump, then minify, then gzip. Treat the numbers you see in marketing posts as mixed pipelines. FastMinify’s minifier does not scan your HTML. For the conceptual split with JavaScript tree-shaking, see tree shaking vs minification.

CSS code on screen showing performance optimization
What actually moves Core Web Vitals

Google’s ranking signal is INP, not FID. Minified CSS can reduce download and parse of render-blocking stylesheets, which may support LCP. It does not rename classes, extract above-the-fold rules, or fix layout shift from unsized images. Measure with Lighthouse or CrUX — do not promise a fixed millisecond or conversion lift. After minify, still enable compression: Apache and Nginx GZIP/Brotli guide.

LCP: smaller blocking CSS can help when the LCP element is styled by that file — not when the bottleneck is a video or a late image
INP (not FID): CSS minify is a weak lever; long tasks are usually JavaScript
CLS: minify does not reserve image/font space; it can even shuffle cascade if you enable aggressive CSSO restructure
On the wire: gzip/Brotli still applies after minify — pair with server compression, not instead of it

Setting up CSSO (the minifier FastMinify uses)

Install and minify with real CSSO options

CSSO (CSS Optimizer) parses with CSSTree, then cleans, compresses, and optionally restructures rulesets. FastMinify’s Minify CSS page runs CSSO in your browser: Compression Level maps to restructure/merge; Aggressive can change source order. The snippets below are the public CSSO API — not invented flags.

Installation

npm install --save-dev csso csso-cli

API (Node)

const csso = require('csso'); const result = csso.minify(css, { restructure: true, comments: false }); console.log(result.css);

CLI

npx csso src/styles.css --output dist/styles.min.css # Safer preview (no structural merge): # npx csso src/styles.css --no-restructure -o dist/styles.min.css
usage data is a whitelist, not PurgeCSS

CSSO’s `usage` option lets you list tags, ids and classes that exist in markup so unused selectors can be dropped. You must maintain that list. It is not a scan of `*.html` / `*.jsx`. Dynamic class names built in JavaScript will disappear if you omit them. Prefer PurgeCSS or Tailwind’s content paths for real unused-CSS removal; keep `usage` for small, known surfaces.

CSSO configuration with documented minify options

Configuration

const cssoConfig = { restructure: true, comments: false, usage: { tags: ['div', 'span', 'a', 'img'], ids: ['header', 'footer', 'main'], classes: ['btn', 'card', 'nav'] } };

Usage

const fs = require('fs'); const csso = require('csso'); function minifyCSS(inputPath, outputPath) { const css = fs.readFileSync(inputPath, 'utf8'); const result = csso.minify(css, cssoConfig); fs.writeFileSync(outputPath, result.css); const pct = ((css.length - result.css.length) / css.length * 100).toFixed(1); console.log(`${inputPath} -> ${outputPath} (${pct}% smaller source)`); }

Unused CSS: PurgeCSS, Tailwind, CSSO usage

PurgeCSS (not PurifyCSS)

PurifyCSS is a stale project. In 2026, scan content with PurgeCSS (or Tailwind’s built-in content / `@source` scan). FastMinify does not run this step. After you drop dead rules, minify the remainder with CSSO or cssnano — or paste a chunk into the online CSS minifier to sanity-check a file.

Installation

npm install --save-dev purgecss

PurgeCSS (v8 API)

import { PurgeCSS } from 'purgecss'; const [{ css }] = await new PurgeCSS().purge({ content: ['src/**/*.html', 'src/**/*.{js,jsx,ts,tsx}'], css: ['src/**/*.css'] });

Tailwind: content paths, don’t double-purge

// tailwind.config.js (v3) — list every template that can emit classes module.exports = { content: ['./src/**/*.{html,js,ts,jsx,tsx}'], theme: { extend: {} } }; /* Tailwind v4: prefer @source in CSS instead of a second PurgeCSS pass unless you know why you need both. Dynamic class strings still need a safelist. */

Webpack and Vite production CSS

Webpack: cssnano via CssMinimizerPlugin (not CSSO by default)

A production Webpack CSS pipeline usually extracts files with MiniCssExtractPlugin and minifies with css-minimizer-webpack-plugin. That plugin’s default engine is cssnano, not CSSO. You can still run CSSO in npm scripts or a custom minimizer. Vite minifies CSS in production through build.cssMinify — historically esbuild, with Lightning CSS on newer majors. Check the Vite docs for the version you ship; do not copy a “default minifier” from a blog post dated to another major. For WordPress themes without a JS build, see WordPress minification plugins.

Configuration Webpack

const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = { mode: 'production', module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader' ] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'styles.[contenthash].min.css' }) ], optimization: { minimizer: [ '...', new CssMinimizerPlugin() ] } };

package.json

{ "scripts": { "build": "webpack --mode=production", "minify:css": "csso src/styles.css --output dist/styles.min.css" }, "devDependencies": { "css-minimizer-webpack-plugin": "^8.0.0", "mini-css-extract-plugin": "^2.9.0", "csso-cli": "^4.0.0" } }

Critical CSS, selectors, online vs build

Critical CSS is a separate workflow

Inlining above-the-fold CSS and loading the rest asynchronously is not what a minifier does. Extract first, then minify the critical block. Full walkthrough: Critical CSS: extract, minify, inline.

Extract above-the-fold rules (critical, Penthouse, or your design-system subset)
Minify that block (CSSO online or the same engine as your build) before inline
Load the remainder with media=print onload or a non-blocking pattern you have tested
On WordPress, plugin “optimize CSS delivery” is this layer — not FastMinify
Selector depth vs file size

Deep selectors cost parse and specificity fights more than they cost kilobytes. Minify will not flatten `.container .row .card .title` for you. Prefer fewer, stable classes — then minify.

Before

/* Slow to maintain, not just “slow to parse” */ .container .row .col-md-6 .card .card-body .card-title { color: #333; } .btn-primary, .btn.btn-primary, button.btn-primary { background: blue; }

After

.card-title { color: #333; } .btn-primary { background: blue; }
When the online minifier is enough

Paste-and-minify is the right tool for a child-theme override, a one-off landing CSS file, or checking what CSSO does to a snippet before you change the build. It is the wrong tool as the only production pipeline for an app that ships on every commit. Trade-offs: online minifiers vs build tools. SCSS/LESS compile first — CSS preprocessor hub — then minify CSS.

Use Minify CSS for files you can paste; concat on that page if you merge a few sheets
Leave Aggressive off if your cascade depends on source order
Keep comments only when you accept a weaker size win (tool copies collapsed source)
Lock the same step in Webpack, Vite, or CI so production cannot skip minify

Measure CSS, don’t guess

What to look at

File size on disk is not bytes on the wire. Check the compressed transfer size in DevTools Network, Coverage for unused bytes, and field LCP — not a single invented “CSS performance score”.

CSS performance analysis dashboard showing optimization metrics and file size reduction
Chrome Coverage: unused CSS bytes in the page you actually loaded
DevTools Network: encoded vs decoded size after gzip/Brotli
Lighthouse: render-blocking CSS and LCP breakdown — not a CSS-only grade
WebPageTest: filmstrip + request waterfall for the stylesheet
CI: fail on regressions you defined

A Lighthouse score of “the whole page” is not a CSS budget. Prefer a max size on the extracted CSS artifact, then optionally a performance assertion on a stable URL.

Configuration GitHub Actions

name: CSS size check on: [push] jobs: css-budget: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' - run: npm ci && npm run build - name: Fail if production CSS exceeds budget run: node scripts/check-css-size.js

What to do next

Minify CSS with CSSO or cssnano in the build you already trust. Remove unused rules with PurgeCSS or Tailwind’s content scan — not with PurifyCSS, and not with FastMinify’s paste box. Use the online CSS minifier to inspect a file or a custom snippet. If the site is WordPress without a frontend build, start with plugin minification on staging rather than inventing a FastMinify plugin.

Keep CSSO (or cssnano / Lightning CSS) in production builds, not only in a browser tab
Treat unused CSS and critical CSS as extra pipelines with their own tests
Do not enable CSSO restructure on a cascade you have not regression-tested
Measure compressed transfer size and LCP, not bounce-rate folklore
WordPress: one minify plugin stack, CSS before JS, excludes before combine
Share this article
Share this article: