Fixing Tailwind Culling in Monorepos


When you split a React monorepo into separate component and app packages, Tailwind will cull utility classes from the component package if they’re not also used in the consuming app. Tailwind only generates CSS for classes it can statically detect in the files it scans — if your app only scans its own source, classes defined in shared packages are invisible.

At the same time, component packages still need their own standalone CSS build for tools like Storybook that run outside of a consuming app. This article covers how to handle both situations.

Why Not Import Component CSS?

The intuitive approach is to have component packages build their own CSS and have apps import it. Avoid this. Tailwind v4 wraps generated utilities in @layer utilities, so importing both CSS files gives the browser two @layer utilities blocks. The CSS cascade resolves same-specificity conflicts by source order — the later block wins. This causes:

  • Utility classes unique to one package silently disappearing or getting overridden
  • Variant-prefixed classes (hover:, dark:, md:, etc.) breaking because a plain utility in the later block overrides them

Solution

The app generates all CSS itself by scanning both its own source and the component source using Tailwind v4’s @source directive. This produces a single @layer utilities block with no cascade conflicts.

  • @source is a Tailwind v4 CSS directive. The v3-style content array in postcss.config.js does not work in v4. Source scanning must be configured in the CSS file itself.
  • Paths are relative to the CSS file, not the project root or the PostCSS config.
  • Automatic detection covers the app’s own directory. @source is only needed for directories outside the app.

App Setup

src/index.css — use @source to scan the component package:

@import "tailwindcss";

@source "../../components/src";

postcss.config.js — no content scanning needed:

export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

main.tsx — only import the app’s own CSS:

import "./index.css";
// Do NOT import component CSS here

Component Package Setup

Components still need their own CSS build for Storybook:

package.json:

{
  "scripts": {
    "build:css": "postcss src/styles.css -o dist/styles.css"
  },
  "exports": {
    "./styles.css": "./dist/styles.css"
  }
}

This output is consumed by Storybook only, never by apps.