---
title: "How to Reduce CSS Size in a Project (Without Breaking UI)"
description: "Practical ways to shrink CSS bundles: Tailwind discipline, choosing tree-shakeable UI libs, safe purging, avoiding accidental global CSS, and setting team/AI conventions."
type: article
seo:
  keywords: css size, css bundle, tailwind purge, tailwind arbitrary classes, tree shaking, purgecss, nuxt css, performance
pubDate: "2026-04-28 12:00:00"
authors:
  - dubem
tools:
  - tailwind
  - nuxtjs
---

Big CSS bundles rarely come from one dramatic mistake. They’re usually the sum of small choices: styling “just this one thing” with arbitrary values, adopting a UI library that ships a huge stylesheet, or accidentally making most CSS global so every route downloads everything.

The goal isn’t “no CSS.” The goal is **ship only the CSS your users need for the routes they visit**, and do it in a way that doesn’t silently break production UI.

---

## Avoid Tailwind arbitrary classes (they add noise and complicate purging)

Tailwind stays small when your class names are **static** and reusable. Arbitrary values (`bg-[#0f172a]`, `w-[372px]`, `shadow-[...]`) tend to push you toward one-off styling and can force you to loosen purge rules when classes become dynamic.

What to do instead:

- Prefer **design tokens** in `tailwind.config` (`theme.extend`) for colors, spacing, radii, shadows, font sizes.
- Prefer **composed utilities** for repeated patterns:
  - shared static class strings (keep them literal, not generated)
  - small wrapper components (`<Button>`, `<Card>`, `<Badge>`) that encode a consistent style
  - Tailwind layers like `@layer components` for semantic classes
- If you must use an arbitrary value, keep it **rare, static, and reviewed**.

A useful rule: if you see the same arbitrary value twice, it probably wants to be a token.

---

## Avoid UI libraries that can’t tree-shake or purge CSS well

JavaScript tree-shaking is common. **CSS tree-shaking is not guaranteed.** Many UI libraries ship a single big stylesheet (or inject global styles) that you can’t easily split by route or prune by usage.

Before you adopt a UI library, verify:

- It supports **per-component imports** (for both JS and CSS), or a build step that outputs “used-only” CSS.
- It documents how to work with **purge** (Tailwind, PurgeCSS, or the framework’s native purge).
- It doesn’t require a giant **global CSS import** just to render a few components.

If you can’t answer “how does this library ensure unused CSS isn’t shipped?”, you’ll usually pay for it in bundle size.

---

## Use a CSS purge plugin, but treat it like a risky refactor

Purging is one of the highest-impact optimizations for CSS size—and one of the easiest ways to ship broken UI if your purge config misses a template source or dynamic class pattern.

Best practices:

- Run purge for **production builds** and ensure the tool scans *all* rendered templates (pages, components, content, emails, CMS templates).
- Prefer **static class strings** over generated class names.
- If you must safelist, do it **narrowly** (explicit list over broad regex).

How to validate you didn’t break anything:

- If you have screenshot tests, run them and review diffs for key routes.
- If you don’t, do a manual pass that specifically checks:
  - responsive variants (`sm:`, `md:`), hover/focus/active states
  - dark mode variants
  - modals, toasts, dropdowns, empty states, error states
  - rarely visited routes (settings/admin/onboarding)

Purging should reduce CSS. It shouldn’t reduce trust.

---

## Watch out for global CSS exports (they prevent page-level splitting)

One very common reason CSS stays large: your app ends up in a state where **most styles are global**, so every route loads nearly everything.

This often happens when:

- a root entry imports a “main.css” that imports lots of component/page CSS
- a UI library requires a global stylesheet
- framework limitations or dynamic routing patterns prevent clean CSS chunking

What to do:

- Keep global CSS **minimal**: tokens (CSS variables), tiny reset, base typography—nothing route-specific.
- Co-locate route-specific styling with the route/module so your bundler has a chance to **split by page**.
- Audit imports: if a stylesheet is imported by the root layout/app entry, it’s effectively global.

If you want small CSS, global CSS should be a deliberate choice, not an accident.

---

## Keep best practices consistent across your team (and AI agents)

CSS bloat is often a process problem: different people (and agents) produce different styling patterns, which increases unique utilities, safelists, and global overrides.

Team conventions that keep CSS lean:

- Define a “styling decision tree”:
  - utilities first → tokens second → components/layers for repetition → arbitrary values last
- Add lightweight PR checks:
  - flag new global CSS imports
  - flag broad purge safelists
  - track CSS output size (even just “before vs after” from build stats)
- Teach your AI agents the same rules:
  - default to tokens and reusable patterns
  - avoid arbitrary classes unless justified
  - never expand purge safelists casually
  - don’t introduce global CSS without a clear reason

If the rules aren’t shared, the CSS will grow back—quietly.

---

## Quick checklist

- Reduce arbitrary Tailwind values; add tokens.
- Choose UI libraries with proven CSS purging/tree-shaking.
- Purge in production builds, scan all templates, safelist narrowly.
- Keep global CSS minimal; avoid importing route styles globally.
- Standardize conventions for humans and AI agents; enforce with PR checks.

