Javascript is required
Best practicesPublished 2026-07-28Updated 2026-08-086 min read

PurgeCSS: Unused Styles, Safelists and Minification

With the adoption of CSS frameworks like Tailwind CSS or Bootstrap, unused styles can dominate production bundle sizes. PurgeCSS eliminates dead CSS through tree-shaking, but ignoring its regex token extraction mechanism risks stripping dynamically concatenated class names. This article explains PurgeCSS mechanics, safelists, and pipeline integration.

By: Tiny
CSS OptimizationPurgeCSSMinificationPerformance

1. Problem: accumulation of unused CSS and dynamic class pitfalls

Unused styles increase network transfer and browser parsing costs in large apps. PurgeCSS scans source templates to extract tokens and match CSS selectors. However, for dynamic class constructions like const cls = 'status-' + type, static regex scanning misses runtime values and strips rules like .status-active.

2. Minimal reproduction: dynamic class stripped after tree-shaking

When source code builds class names dynamically, PurgeCSS extracts individual string tokens. Without a Safelist, the target CSS rule is purged from the production bundle.

<!-- Dynamic class logic in source -->
<script>
  const type = 'danger';
  const className = 'alert-' + type;
</script>

/* PurgeCSS keeps .alert or .danger tokens if present, but fails to match .alert-danger */
.alert-danger { background-color: #f8d7da; color: #721c24; }

3. Root cause: limitations of literal extractors

PurgeCSS defaults to regex string extraction (such as /[A-Za-z0-9_-]+/g). It performs simple text parsing without executing JavaScript runtime logic, failing to evaluate variable concatenation or conditional results.

4. Recommendation: safelist protection combined with minification and caching

1. Explicitly configure Safelists with exact strings, regex prefixes (e.g., /^alert-/), or deep matching. 2. Write explicit class mapping dictionaries in JS instead of string splitting. 3. Pipeline synergy: PurgeCSS removes dead rules -> Minifier shrinks bytes -> Assets served with ContentHash and HTTP immutable caching.

5. Complete code: selector extractor and safelist filter simulation

This TypeScript code demonstrates how to extract source tokens and evaluate selector retention using a Safelist configuration.

interface PurgeOptions {
  safelist: (string | RegExp)[];
}

function extractTokens(sourceCode: string): Set<string> {
  const words = sourceCode.match(/[A-Za-z0-9_-]+/g) ?? [];
  return new Set(words);
}

function isSelectorRetained(selector: string, usedTokens: Set<string>, options: PurgeOptions): boolean {
  const classNames = selector.match(/\.[A-Za-z0-9_-]+/g)?.map((c) => c.slice(1)) ?? [];
  if (classNames.length === 0) return true;

  return classNames.every((className) => {
    const inSafelist = options.safelist.some((rule) => {
      if (typeof rule === "string") return rule === className;
      return rule.test(className);
    });
    if (inSafelist) return true;

    return usedTokens.has(className);
  });
}

const htmlSource = '<div class="btn main-title">Hello</div>';
const tokens = extractTokens(htmlSource);
const options: PurgeOptions = { safelist: [/^alert-/] };

console.log(isSelectorRetained(".btn", tokens, options)); // true (matched template token)
console.log(isSelectorRetained(".alert-danger", tokens, options)); // true (matched safelist regex)
console.log(isSelectorRetained(".card-hidden", tokens, options)); // false (unreferenced, purged)

6. Incorrect approaches

Placing concatenated string names in code comments to trick extractors. Adding an entire UI framework to a safelist, rendering tree-shaking useless. Enabling PurgeCSS in dev environments causing hot-reload style loss.

7. Boundaries: CSS variables, pseudo-elements, and dynamic DOM

CSS custom properties (--theme-color) and global classes dynamically attached to document.body by 3rd-party SDKs cannot be statically extracted and must be added to a safelist manually.

8. Verification

Run visual regression against the production artifact. Cover dynamic class names, conditional branches, rich text, and third-party components, and assert critical selectors remain in the final CSS.

9. FAQ

How does PurgeCSS differ from CSS Minify? PurgeCSS deletes unreferenced rules entirely; CSS Minify removes whitespace and comments from retained code.

Does Safelist support regular expressions? Yes, regex rules are recommended for matching dynamic class prefixes like /^btn-/.

How does Tailwind CSS v3+ handle unused styles? Tailwind v3+ uses a JIT engine to generate classes on demand, eliminating the need for standalone PurgeCSS steps.

10. Summary

PurgeCSS tree-shaking and CSS Minification complement each other. Understanding extractor lexical boundaries and configuring safelists ensures maximum byte savings without sacrificing dynamic styles.

Sources and further reading

Specifications and references for checking the article’s technical details.

Related articles

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool