DEV Community

Cover image for css-ghost: find and delete orphaned CSS selectors with zero config
Roland Levy
Roland Levy

Posted on

css-ghost: find and delete orphaned CSS selectors with zero config

You've been there. You rename a component, move a file, or quietly migrate from one styling approach to another – and the old CSS stays behind. Nobody complains, the build doesn't break, and the dead selectors quietly accumulate.

css-ghost is a small Node.js CLI tool I built to solve that problem. Point it at a folder and it tells you every CSS class defined in your style files that isn't referenced anywhere in your components.

css-ghost terminal demo


What it does

css-ghost scans your style files (.css, .module.css, .scss, .sass, .less, MUI makeStyles, vanilla-extract), extracts every class selector, then cross-references them against every component file it can find (.js, .jsx, .ts, .tsx, .vue, .svelte).

Anything defined but never actually used gets reported as an orphan – with its line number and the full CSS rule printed inline, so you can see what it does before deciding to delete it.

It also catches orphaned CSS custom properties (--my-token: value defined but never consumed via var(--my-token) anywhere in the codebase), reported in a separate 🔮 section.


Run it now

No install required:

npx css-ghost ./src
Enter fullscreen mode Exit fullscreen mode

Or install globally for repeated use:

npm install -g css-ghost
css-ghost ./src
Enter fullscreen mode Exit fullscreen mode

Interactive folder picker

Run css-ghost with no arguments and an interactive directory picker opens. Navigate with ↑↓ (or j/k), press Enter or → to go into a folder, ← to go back up, [a] to add the current directory to the scan, then [d] when done:

css-ghost
Enter fullscreen mode Exit fullscreen mode

Useful when you're not sure which subfolder to target, or want to scan multiple directories in one pass.


Dynamic class detection

If a component accesses classes dynamically – styles[variable], styles[\${variant}] – css-ghost can't resolve the class name statically. Rather than silently skip those files or report false positives, it flags them with a ⚠️ warning per file:

✅ src/components/Alert/Alert.module.css

   ⚠️  Dynamic class references detected – results may be incomplete.
   Dynamic references in: src/components/Alert.jsx

   👻 .hidden (line 30)
      .hidden { display: none; }
Enter fullscreen mode Exit fullscreen mode

All statically resolvable patterns are still analysed: string literals, ternaries, cx({ active: isOn }) object keys, array literals, and both branches of ternaries.


Deleting orphans

Once you're happy with the report, pass --delete to remove every flagged selector from its source file:

css-ghost ./src --delete
Enter fullscreen mode Exit fullscreen mode

css-ghost prints the full report first, lists what it's about to delete, then asks for confirmation. Pass --yes to skip the prompt in CI:

css-ghost ./src --delete --yes
Enter fullscreen mode Exit fullscreen mode

Tip: commit or stash first. There's no undo.


Other useful flags

Flag What it does
--ghost-trail Show the last git commit that touched each orphan
--strict Exit with code 1 when orphans exist (CI / pre-commit)
--install-hook ./src Write a pre-commit hook that blocks commits with orphans
--output <path> Custom path for the markdown report
--ai Append a plain-English AI summary (needs free OpenRouter key)

Ghost trail

Pass --ghost-trail to fetch the last git commit that changed each orphan's occurrence count:

css-ghost ./src --ghost-trail
Enter fullscreen mode Exit fullscreen mode
   👻 .iconRight (line 28)
      🕵️  b5c6efb · Roland Levy · 3 weeks ago – "Refactor Button component"
      .iconRight {
        margin-left: 6px;
      }
Enter fullscreen mode Exit fullscreen mode

Handy for tracking down when and why a class went dead.


Pre-commit hook

css-ghost --install-hook ./src
Enter fullscreen mode Exit fullscreen mode

Writes .git/hooks/pre-commit (executable, chmod 755) that blocks commits whenever orphaned selectors exist. Remove it with rm .git/hooks/pre-commit.


How is it different from PurgeCSS?

PurgeCSS is a build-time bundler – it hooks into webpack or PostCSS to strip unused CSS from your production output. It's designed for frameworks like Tailwind or Bootstrap where you use a fraction of the utility classes.

css-ghost is a source code auditor – it finds dead selectors in your actual codebase, with no build step and no config file.

PurgeCSS css-ghost
Works on source files ❌ Build output only ✅
Zero config, no build step ❌ ✅
CSS custom property orphans ❌ ✅
Git history per orphan ❌ ✅
Pre-commit hook ❌ ✅
Deletes dead code for you ❌ ✅
Safe with dynamic classes ❌ Silently removes them ✅ Flags with ⚠️

The two tools are complementary – PurgeCSS for trimming framework output at build time, css-ghost for auditing your own hand-written CSS throughout development.


The source is on GitHub and the package is on npm. PRs and issues welcome.

Top comments (0)