Tailwind CSS in Production · Responsive, Accessible and Fast

The 1.2 MB Stylesheet

Page loads slowed after a stylesheet grew past a megabyte: a broad safelist, scanning dependencies and the Play CDN in production.

Create a free account to save this page, take notes and track your progress. Sign in

Scenario

Example team: to "fix" missing dynamic classes, a developer safelisted every colour and shade with variants. Another added @source "../node_modules"; to pick up a UI library. A landing page still loads the Play CDN script.

What you see in production

The main stylesheet is 1.2 MB (180 KB compressed); Largest Contentful Paint on mobile gets noticeably worse. The landing page also flashes unstyled content while the CDN script compiles styles in the browser.

The naive code

@import "tailwindcss";
@source "../node_modules";
@source inline("{hover:,focus:,}{bg,text,border}-{red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}");

Reproduce it

How a broad safelist multiplies:

How many classes does that safelist generate?Try it yourself
variants = ["", "hover:", "focus:"]
properties = ["bg", "text", "border"]
colors = ["red", "orange", "amber", "yellow", "lime", "green", "emerald", "teal", "cyan", "sky", "blue",
          "indigo", "violet", "purple", "fuchsia", "pink", "rose"]
shades = [50] + list(range(100, 1000, 100)) + [950]

safelisted = len(variants) * len(properties) * len(colors) * len(shades)
used_in_app = 140
print(f"safelisted classes: {safelisted:,}")
print(f"classes the app actually uses: {used_in_app}")
print(f"~{safelisted * 60 / 1024:,.0f} KB of CSS for the safelist alone (about 60 bytes per rule)")

Output

safelisted classes: 1,683
classes the app actually uses: 140
~99 KB of CSS for the safelist alone (about 60 bytes per rule)

Root cause

Tailwind's CSS is small because it contains only classes found in your sources. Broad safelists and scanning large directories (dependencies, build output) defeat that, and the Play CDN compiles at runtime in every visitor's browser, which is meant only for prototypes.

The fix

  • Replace fragment-built class names with complete-class maps, then delete the broad safelist; safelist only what data really needs.
  • Point @source at the specific library path that contains classes (or the library's documented source path), and exclude generated folders with @source not "./dist";.
  • Use the Vite plugin, PostCSS plugin or CLI in production; remove the CDN script.
  • Track stylesheet size in CI (fail the build above a budget).

Trade-offs

  • Narrow sources can miss a class in an unusual file type; visual tests catch that.
  • A size budget needs occasional adjustment as the product grows.

Detect it in production

  • Bundle-size reporting for CSS per deploy; Lighthouse/Core Web Vitals on key pages.
  • A check that the Play CDN URL never appears in production HTML.

Key points

  • Generated CSS size follows what you scan and safelist.
  • Keep safelists tiny, sources specific, and the Play CDN out of production.

Practice questions

Why is the Play CDN not for production, and what do you use instead?

Answer

It downloads a compiler and generates CSS in each visitor's browser at runtime, causing extra JavaScript, a flash of unstyled content and no caching of a static stylesheet. Use @tailwindcss/vite, @tailwindcss/postcss or the CLI to build a static CSS file.

How do you control which files Tailwind scans in v4?

Answer

Detection is automatic (it ignores .gitignore'd files and binaries). Add paths with @source "…", exclude with @source not "…", and safelist strings with @source inline("…").

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?