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.
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:
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
@sourceat 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.