Skip to content

Tailwind Performance

Tailwind's just-in-time engine already produces small CSS files by default, but a few common mistakes can bloat output or slow builds. This lesson covers the practices that keep Tailwind CSS fast in production.

How Does Tailwind Keep CSS Small?

Tailwind's build engine scans your content files for class name strings and generates CSS only for classes it actually finds, a process often called "tree-shaking" or "purging". Classes never used anywhere in your codebase never make it into the final stylesheet.

This means final production CSS is typically just a few kilobytes after compression, dramatically smaller than shipping the entire framework, but only if the scanner can correctly find every class name you actually use.

// tailwind.config.js
content: [
  "./src/**/*.{astro,html,js,jsx,ts,tsx}",
],

Accurate content paths are the single biggest factor in both correctness (no missing classes) and final bundle size (no unnecessary scanning).

Performance-Related Configuration

content: ["./src/**/*.{ext}"]  // accurate scan paths
safelist: [...]                // rarely needed, forces classes to always generate
  • Accurate content globs ensure only used classes are generated, nothing more, nothing less.
  • Avoid safelist unless a class is truly generated dynamically and can't be detected by the scanner.
  • Production builds automatically minify the final CSS output.
  • Tailwind v4's Rust-based engine is dramatically faster to build than the v3 JavaScript-based engine.

Performance Best Practices Cheatsheet

Key practices for keeping Tailwind builds fast and output small.

Practice Why It Matters
Accurate content globs Ensures used classes are found and unused ones are excluded
Avoid string concatenation for classes The scanner can't detect dynamically built class names
Use a classnames/clsx map for variants Keeps full class names visible to the scanner as static strings
Minimize safelist usage Forces classes to generate regardless of usage, growing output
Use production build mode Enables minification of the final CSS
Prefer official plugins over custom CSS Plugins integrate with tree-shaking correctly
Keep JIT engine up to date Newer versions include build performance improvements
Split large monorepos' content paths Avoids scanning unrelated packages unnecessarily

Why Dynamic Class Names Break Detection

Tailwind's scanner looks for complete, literal class name strings in your source files. If a class name is built dynamically through string concatenation, the scanner never sees the final string and won't generate the corresponding CSS.

// Broken: scanner can't see "text-red-500" or "text-blue-500" as complete strings
const color = "red";
<p class={`text-${color}-500`}>Text</p>

// Works: both full class names appear literally in the source
const classes = { red: "text-red-500", blue: "text-blue-500" };
<p class={classes[color]}>Text</p>

The second approach keeps every possible full class name as a literal string somewhere the scanner can find.

When (Rarely) to Use safelist

safelist forces specific classes to always generate, even if the scanner can't find them, useful only for truly dynamic content, like CMS-supplied class names, where the actual strings genuinely don't exist anywhere in your source code.

// tailwind.config.js
safelist: [
  "bg-red-500",
  "bg-green-500",
  { pattern: /bg-(red|green|blue)-(400|500|600)/ },
],

Overusing safelist patterns can significantly grow your CSS output, since it disables tree-shaking for everything matching the pattern.

Tailwind v4's Performance Improvements

Tailwind v4 rewrote its core engine in Rust, resulting in dramatically faster full and incremental builds compared to the JavaScript-based v3 engine, especially noticeable in large codebases with thousands of files to scan.

Common Mistakes

  • Building class names through string concatenation, causing the scanner to silently miss those classes.
  • Overusing safelist patterns, unintentionally including large numbers of unused classes in the final build.
  • Pointing content globs at overly broad directories (like the entire repository root in a monorepo), slowing down scans unnecessarily.
  • Not testing a production build before shipping, only ever viewing the unminified development output.
  • Assuming any missing style is a Tailwind bug rather than checking for one of the detection issues above first.

Key Takeaways

  • Tailwind generates CSS only for classes it can detect as literal strings in your content files.
  • Dynamically concatenated class names break detection; use a static class map instead.
  • safelist should be reserved for genuinely dynamic, undetectable class names, not used as a general fallback.
  • Accurate content globs are the single most impactful performance and correctness setting.
  • Tailwind v4's Rust-based engine offers significantly faster build times than v3.

Pro Tip

Whenever a class seems to be "missing" from your compiled CSS, check for dynamic string concatenation first, it's by far the most common cause, and the fix (a static class map) is usually a five-minute change.