Skip to content

Monorepo & Content Scope

If you use csszyx inside a monorepo and your build fails with something like Cannot find module './h.cur', an invalid url() in your CSS, or a cryptic hashing crash deep in the bundler — this page is the fix. It is a one-time, two-line change to your CSS entry, and it is not specific to any framework: it applies to Vite, Next.js (webpack and Turbopack), Astro, and any bundler running Tailwind v4.

Tailwind v4 has automatic content detection — no content: [...] array. It picks a base directory and scans everything under it for class names. How wide that base is depends on the build: a Vite build roots it at the Vite root, while other setups root it at the workspace root and scan every package. Either way, the files it scans are not just your components — a doc or fixture sitting beside your entry counts.

Tailwind ignores node_modules, .gitignored files, binaries, CSS, and lockfiles — but it does not ignore .md, .mdx, or .txt. Documentation is full of class names written as examples: a README code block, an MDX snippet, an AI-context file like llms-full.txt. Tailwind can’t tell “example in the docs” from “class in my app”, so it generates CSS for them — including arbitrary-value classes that compile to broken url(...) rules.

Disable automatic detection and tell Tailwind exactly what to scan. csszyx already auto-injects an @source for the classes it generates, so you only add your own templates.

src/index.css
@import "tailwindcss" source(none);
@source "..";

That @source is your package root, relative to this CSS file. source(none) turns off automatic detection entirely; the explicit @source adds back only what you name. This is the most reliable form — a narrowing source("..") alone is not enough, because automatic detection can still escape the base in some bundler pipelines.

Disable detection, then list what to scan. Deterministic; nothing leaks in.

@import "tailwindcss" source(none);
@source ".."; /* this package */
@source "../../packages/ui/src"; /* a shared UI lib you import classes from */

source(none) only affects scanning. If you generate class names dynamically (or need a class that appears in no file), safelist it with @source inline(...) — it supports brace expansion and ranges:

@source inline("underline");
@source inline("{hover:,focus:,}bg-blue-{50,{100..900..100},950}");

csszyx compiles your sz props to Tailwind classes and writes them to a generated safelist file (.csszyx/csszyx-classes.txt), then auto-injects an @source pointing at it. That is why, even with source(none), your csszyx styles still emit — the plugin already registered its own source. You only ever scope your own templates. On Next.js the same directive comes from the @csszyx/unplugin/postcss plugin in postcss.config, since Turbopack offers no hook into the stylesheet itself.

The safelist covers every directory the prescan walks, which includes anything you opted in with compileSources. A vendored design system beside your app is therefore safe to scope: its sz-compiled classes and the raw className literals sitting next to them are both written to the safelist, and the auto-injected @source picks them up wherever the package lives relative to the CSS file.

The condition is the opt-in. A /packages/ directory that is not in compileSources is skipped by the prescan, so its classes are absent from the safelist and source(none) would drop them — that is exactly what the build-end “skipped by ignore rules” warning is telling you. Add the directory first, confirm the warning is gone, then scope.