Master css-complexity-analyzer: Tame Your CSS Complexity
The Problem That Keeps CSS Maintainable
Hey there, fellow developer! Ever inherited a CSS codebase where selectors look like .header .nav .menu .item .link:hover span.title? You know the pain—selectors so specific they override everything, maintenance becomes a nightmare, and nobody dares touch the styles anymore. That's where css-complexity-analyzer comes in, giving you hard numbers on your CSS health so you can refactor before it's too late.
What is css-complexity-analyzer?
css-complexity-analyzer is your CSS health inspector that parses your stylesheets and spits out 150+ metrics including selector complexity, specificity scores, duplication rates, and rule bloat. Unlike basic linters, it quantifies how bad your CSS is with stats like average selector complexity (2.0 vs 5.2) and specificity heatmaps. Think of it as CSS's equivalent of cyclomatic complexity for JavaScript—objective numbers to guide your refactoring.
Built on proven analyzers like @projectwallace/css-analyzer, it powers tools used by teams at scale to keep CSS lean and performant.
Getting Started
npm install --save-dev css-complexity-analyzer
# or yarn
yarn add --dev css-complexity-analyzerBasic usage is dead simple:
// analyze.ts
import { analyzeCSS } from 'css-complexity-analyzer';
const css = `
.header .nav .menu .item.active { /* nightmare selector */
padding: 20px;
}
.btn { /* clean selector */
padding: 10px;
}
`;
const results = await analyzeCSS(css);
console.log('Average selector complexity:', results.complexity.mean);
console.log('Highest specificity:', results.specificity.max);
// Output: Average selector complexity: 3.2, Highest specificity: [0,4,2]Core Feature 1: Selector Complexity Scoring
The killer feature? Selector complexity scores that tell you exactly how many browser decisions each selector requires. Complexity > 3? Time to refactor.
// complexity-example.ts
import { analyzeCSS } from 'css-complexity-analyzer';
const messyCSS = `
.main .content .article .header ul li a.active span.title {
color: red;
}
`;
const result = await analyzeCSS(messyCSS);
console.log('Complexity breakdown:', {
min: result.complexity.min, // 1
max: result.complexity.max, // 8 😱
mean: result.complexity.mean, // 4.5 🚨
offenders: result.selectors.slice(0, 3) // Worst 3 selectors
});Why this matters: Browsers evaluate selectors right-to-left. .title span.active a li ul.header forces 8 DOM checks, while .title needs just a single check. High complexity = slow rendering.
Core Feature 2: Specificity Heatmaps
Get a visual (data) breakdown of your specificity wars:
// specificity-example.ts
import { analyzeCSS } from 'css-complexity-analyzer';
const cssWithSpecificityWars = `
/* ID overrides everything */
#hero { color: blue; }
/* Class soup */
.sidebar .content .post .title { color: green; }
/* Ultra-specific monster */
article section:nth-child(3) .card:hover .btn-primary { color: red !important; }
`;
const stats = await analyzeCSS(cssWithSpecificityWars);
console.table(stats.specificity.sum); // [0, 5, 3] = BAD
console.log('Specificity offenders:', stats.offenders?.specificity || []);Pro tip: Aim for specificity sums under [0,2,2]. Anything higher means override hell awaits.
Core Feature 3: Duplication Detection
Find copy-pasted rules killing your bundle size:
// duplication-example.ts
import { analyzeCSS } from 'css-complexity-analyzer';
const duplicatedCSS = `
.btn-primary { padding: 10px 20px; border-radius: 4px; }
.btn-secondary { padding: 10px 20px; border-radius: 4px; }
/* Same padding/border 47 more times... */
`;
const analysis = await analyzeCSS(duplicatedCSS);
console.log('Duplicated properties:', analysis.duplicatedProperties);
console.log('Duplicated selectors:', analysis.duplicatedSelectors);
// Reveals: 47 padding instances, 12 border-radius defsReal-World Example: Pre-commit CSS Audit
Here's a production-ready Git hook that blocks bad CSS:
// scripts/css-audit.ts
import { analyzeCSS } from 'css-complexity-analyzer';
import { readFileSync } from 'fs';
import { globSync } from 'glob';
type AuditResult = {
pass: boolean;
message: string;
metrics: any;
};
const MAX_COMPLEXITY = 3.5;
const MAX_SPECIFICITY = 15; // [0,3,2] = 15
async function auditCSS(): Promise<AuditResult> {
const cssFiles = globSync('src/**/*.css');
let totalCSS = '';
for (const file of cssFiles) {
totalCSS += readFileSync(file, 'utf8');
}
const results = await analyzeCSS(totalCSS);
const tooComplex = results.complexity.mean > MAX_COMPLEXITY;
const specificityFail = results.specificity.sum.reduce((a: number, b: number) => a + b, 0) > MAX_SPECIFICITY;
if (tooComplex || specificityFail) {
return {
pass: false,
message: `🚨 CSS audit failed!\nComplexity: ${results.complexity.mean.toFixed(1)} (max ${MAX_COMPLEXITY})\nSpecificity sum: ${results.specificity.sum.reduce((a: number, b: number) => a + b, 0)} (max ${MAX_SPECIFICITY})`,
metrics: results
};
}
return {
pass: true,
message: `✅ CSS healthy! Complexity: ${results.complexity.mean.toFixed(1)}`,
metrics: results
};
}
// Usage: npx tsx scripts/css-audit.ts
auditCSS().then(console.log);Add to package.json:
{
"scripts": {
"lint:css": "tsx scripts/css-audit.ts"
}
}Tips & Gotchas
✅ Do: Run weekly on your main CSS bundle ✅ Do: Set complexity thresholds in CI (like Constyble) ✅ Do: Focus on complexity.mean > 3.0 first
❌ Don't: Ignore !important counts—3+ is a red flag ❌ Don't: Let duplicatedProperties > 20—extract to custom properties ❌ Gotcha: Ignores CSS-in-JS. Pre-process with css extraction first
TypeScript tip: Results are Record<string, any>—use Zod for validation:
import { z } from 'zod';
const CSSMetrics = z.object({
complexity: z.object({ mean: z.number() }),
specificity: z.object({ sum: z.array(z.number()) })
});When to Use It (vs Alternatives)
Tool | Best For | vs css-complexity-analyzer
- **css-complexity-analyzer** | Production metrics + CLI | 150+ metrics, TypeScript-ready[1]
- `analyze-css` | Quick audits | Fewer metrics, no complexity scores[2]
- `parker` | Basic stats | No selector complexity[8]
- `wallace-cli` | Terminal beauty | Less programmatic API[6]
Use this when: You need programmatic access + comprehensive metrics for CI/CD.
Skip when: One-off analysis (use Project Wallace online).
Limitations
- No CSS-in-JS support (needs extraction)
- PostCSS-only (Sass/Less need compilation first)
- Large CSS bundles (>10MB) can timeout
Next steps: Hook it into your PR workflow, set complexity thresholds, and watch your CSS stay maintainable forever! 🚀



