You add a CSS-based icon library like Iconify. You open DevTools on your homepage and the CSS bundle is huge, mostly icon rules for a page you’re not even on.
Tailwind v4 gives you the primitives to fix this. Here’s the pattern, and what breaks if you skip a step.
How Tailwind generates CSS
Tailwind v4 scans your source files for class names and generates CSS only for what it finds. Use icon-[logos--react], get that one rule. Never use it, you don’t.
The catch: scanning is global by default. Every CSS file that imports Tailwind scans your entire project. If one page uses 55 brand icons, those 55 rules end up in whatever CSS file your shared layout imports. Every page pays for them.
Bundlers make this worse. Many extract CSS shared across pages into a common chunk, so route-specific utilities get shipped everywhere.
The key insight
Each Tailwind stylesheet compiles independently. @import "tailwindcss" doesn’t just pull in styles, it spins up a full pipeline: scanner, plugin resolution, CSS generation.
You can have multiple Tailwind stylesheets that don’t know about each other. Each one scans different files, generates different output.
That’s what makes per-page CSS possible.
Building the split
Say you have a page with 55 brand icons and a shared global.css that every page loads. Goal: get the icon CSS off the shared bundle.
Step 1: Create a scoped stylesheet for the icons
@import "tailwindcss/utilities" source(none);@source "../path/to/page";@source "../path/to/icon-data";@plugin "@iconify/tailwind4";@import "tailwindcss/utilities" imports only the utilities layer. No base reset, no theme variables.
source(none) turns off automatic scanning. Tailwind won’t crawl your project.
@source points the scanner at specific files. Only classes found there get generated.
This stylesheet contains only the icon rules needed by that page.
Step 2: Load it only on the page that needs it
Import the stylesheet in the page or route component. Most frameworks support this:
import "./styles/icons.css";The bundler injects it only on routes that import it.
Step 3: Exclude the file from the shared bundle
@import "tailwindcss";@source not "../path/to/icon-data";@plugin "@iconify/tailwind4";@source not punches a hole in the default scan. Tailwind still crawls everything else, but skips that file. The icon plugin in global.css never sees those icon classes, so it won’t generate rules for them.
The gotcha
@source not excludes the entire file, not just the icon class names.
If that file also contains regular utility classes, those disappear from the shared bundle too. Any page that relies on them breaks.
Use @source inline() to hand class names directly to the compiler, bypassing the file scanner:
@import "tailwindcss";@source not "../path/to/icon-data";@source inline("class-a class-b class-c");@plugin "@iconify/tailwind4";Think of it as a safelist you control in CSS. Those classes always generate regardless of what files are scanned.
The result
Before: everything lands in one shared stylesheet.
After:
global.css scans everything except your icon source file, preserves non-icon utilities via @source inline(), loads on every page.
icons.css scans only your route-specific icon source, loads only where needed.
The shared bundle shrinks by however much the icon CSS weighed. Every other page gets that back for free.
When is this worth it?
This pays off when a page-specific dependency generates a lot of CSS:
- Icon libraries, especially brand/logo sets
- Heavy component libraries used on one route
- A page with a big set of dynamic utility classes not used anywhere else
Not worth it for a handful of utilities. The benefit scales with the size of what you’re moving.
Quick reference
| Directive | What it does |
|---|---|
source(none) | Disables automatic scanning. You control exactly what gets scanned. |
@source "../path" | Adds a specific file or directory to the scanner. |
@source not "../path" | Removes a file or directory from the default scan. |
@source inline("cls1 cls2") | Generates specific classes without scanning any file. |
All four are Tailwind v4. No bundler-specific behavior, no config files. Just CSS.