PyColors BlogTechnical article

Tailwind v4 Not Styling Installed UI Components? Check Source Detection

Fix missing Tailwind v4 utilities in a Next.js app using installed PyColors components, with verified source paths and a public-package reproduction.

PP

Patrice Parny

Founder of PyColors

March 17, 20268 min read

Why it matters: this article turns a real PyColors product decision into a reusable SaaS implementation pattern.

If installed PyColors components render as plain content, check whether their utilities exist in the generated CSS. In the Tailwind v4 reproduction below, adding @source for the installed package fixes the missing styles. Installing the package and passing a production build are not enough.

For app/globals.css, the complete stylesheet is:

app/globals.css
@import "tailwindcss";
@import "@pycolors/tokens/tokens.css";
@source "../node_modules/@pycolors/ui/dist";

@custom-variant dark (&:where(.dark, .dark *));

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Already importing Tailwind and PyColors tokens? Add only the missing @source line. Keep the imports at the top; do not duplicate the token mappings.

Read the PyColors UI installation guide for the canonical setup and requirements.

Revised September 28, 2026: verified against the public npm packages and production consumer described below. Original publication: March 17, 2026.

Use the path relative to your stylesheet

The two tested layouts need different registrations:

Global stylesheetSource registration
app/globals.css@source "../node_modules/@pycolors/ui/dist";
src/app/globals.css@source "../../node_modules/@pycolors/ui/dist";

Tailwind v4 ignores node_modules during automatic detection. An explicit @source opts this package in; its path starts at the stylesheet, not the terminal's working directory. See Tailwind's source detection documentation.

Use the installed package's dist directory for this npm consumer. A workspace that imports local source has a different location: resolve that location from its stylesheet. Scanning an entire monorepo or all of node_modules is unnecessary.

What the failing build actually showed

The example uses the public Card and Button exports without adding their internal classes to the app. Both the failing and corrected versions passed TypeScript checking and a production build.

ConditionGenerated CSSBrowser result
Tokens loaded, no library registrationNo .inline-flex, .bg-primary or .bg-card ruleLink styled as a Button remains inline, transparent and unpadded
Wrong relative source pathSame missing rulesSame unstyled components
Correct source pathAll three rules presentButton uses inline-flex, a 36px height and 8px/16px padding; Button and Card have themed backgrounds
Correct source, primary/card mappings disabledLayout rule present; both background rules absentButton keeps its layout but both surfaces are transparent

The source-path results were verified for both stylesheet locations. The last row is a separate negative control: the test resets only --color-primary and --color-card to initial inside @theme. It leaves the underlying CSS variables loaded. Do not add that reset to your application.

These utilities were never generated in the missing-source case; they were not existing component styles that Tailwind subsequently removed. If the rule is present but crossed out in browser DevTools, inspect the cascade and competing CSS instead of adding more scan paths.

Reproduce with the tested public packages

The consumer ran with Node 24.18.1, pnpm 10.32.1, Next.js 16.1.1, React/React DOM 19.2.0, Tailwind CSS and its PostCSS plugin 4.3.3, PostCSS 8.5.28, UI 1.5.4, and tokens 1.2.3. These are tested pins, not a promise about every compatible version.

Create an empty directory outside a monorepo and add the files below. With Node 24 and pnpm 10.32.1 available, run:

pnpm install --ignore-scripts --prod=false
pnpm types:check
pnpm build
pnpm start

The manifest pins every direct dependency. This first install resolves transitive dependencies and creates pnpm-lock.yaml; keep that file and use pnpm install --frozen-lockfile --ignore-scripts --prod=false for subsequent installs. A later first install can resolve different transitive versions, so record your lockfile when comparing results. No repository access, workspace links or copied component implementations are needed.

package.json
{
  "name": "pycolors-tailwind-consumer",
  "private": true,
  "type": "module",
  "packageManager": "pnpm@10.32.1",
  "engines": { "node": ">=24 <25" },
  "scripts": {
    "build": "next build --webpack",
    "types:check": "next typegen && tsc --noEmit",
    "start": "next start"
  },
  "dependencies": {
    "@pycolors/ui": "1.5.4",
    "@pycolors/tokens": "1.2.3",
    "lucide-react": "1.47.0",
    "next": "16.1.1",
    "react": "19.2.0",
    "react-dom": "19.2.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "4.3.3",
    "tailwindcss": "4.3.3",
    "postcss": "8.5.28",
    "@types/node": "24.12.0",
    "@types/react": "19.2.2",
    "@types/react-dom": "19.2.2",
    "typescript": "6.0.3"
  }
}
postcss.config.mjs
export default { plugins: { "@tailwindcss/postcss": {} } };

This is the Tailwind v4 PostCSS setup described in the official Next.js framework guide. Use the app/globals.css shown at the start of this article, then add:

app/layout.tsx
import type { ReactNode } from "react";
import "./globals.css";

export default function Layout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
app/page.tsx
import {
  Button,
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@pycolors/ui";

export default function Page() {
  return (
    <main>
      <Card>
        <CardHeader>
          <CardTitle>Installed components</CardTitle>
          <CardDescription>
            Check the package styles in your app.
          </CardDescription>
        </CardHeader>
        <CardContent>
          <Button asChild>
            <a href="https://pycolors.io/docs/ui/usage-patterns">
              Explore usage patterns
            </a>
          </Button>
        </CardContent>
      </Card>
    </main>
  );
}
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "incremental": true,
    "plugins": [{ "name": "next" }]
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
css-imports.d.ts
declare module "*.css";
.gitignore
node_modules/
.next/
next-env.d.ts
*.tsbuildinfo

The CSS declaration supports the side-effect stylesheet import with the tested TypeScript 6.0.3 setup. Keep .next out of source detection, and keep diagnostic scripts and expected class strings outside the consumer. Otherwise the test itself can accidentally make Tailwind generate the missing utilities.

For the second layout, move the complete app directory into src/app and change only the source registration to the second path in the table. Rebuild from a clean .next directory. Both layouts were tested with fresh production output for every condition.

Check production CSS, then the browser

Temporarily remove the @source line from the disposable example, build again, and inspect .next/static/css. Restore the correct line and rebuild. Search for .inline-flex, .bg-primary and .bg-card in the emitted CSS, not just in the component's DOM class attribute. All three should appear after the correction.

Serve the production output with pnpm start. In DevTools, inspect the computed styles of [data-slot="button"] and [data-slot="card"]. For the corrected example, Chromium measured these background colors:

SurfaceLightWith .dark on the document root
Buttonoklch(0.49 0.23 292)oklch(0.68 0.19 292)
Cardoklch(0.99 0 0)oklch(0.19 0.008 285)

Wait for color transitions to finish before measuring. Your application owns its existing theme selection; PyColors does not require a new provider. This check covers this composition's styles, not an accessibility certification.

Distinguish source detection from token setup

The public @pycolors/tokens/tokens.css import already supplies the theme values and their Tailwind v4 semantic mappings. A --primary value alone does not define a bg-primary utility. The mapping and a detected class are both needed. Our separate negative control demonstrates this: layout survives, while the two intentionally unmapped background utilities disappear.

Keep the supported import once, and remove conflicting theme resets rather than copying a second mapping layer. Continue with the theming guide or semantic design tokens article when the remaining problem is theme values rather than missing utilities.

In your own components, write complete class names. Tailwind scans text; it does not evaluate strings such as bg-${color}-500. Map a prop to complete static alternatives instead. This follows the official class detection guidance.

A note for readers of the earlier Tailwind v3 recipe

The earlier version of this article used tailwind.config content paths, a Tailwind v3 convention. Tailwind v4 does not automatically detect JavaScript configuration files; see the upgrade guide. The tested PyColors setup here uses v4 CSS configuration. This reproduction does not establish compatibility of these package versions with Tailwind v3.

Package download failures, ESLint configuration and deployment logs are separate diagnostics. Changing from pnpm to npm does not supply a missing source path; this entire reproduction uses pnpm.

Final checks

  • Resolve the narrow package source path from the actual stylesheet location.
  • Import the public tokens stylesheet once and preserve its semantic mappings.
  • Verify generated utilities in a fresh production build.
  • Inspect computed component styles in both themes; investigate overrides when rules exist.
  • Keep complete class names and avoid diagnostic files contaminating source detection.

Read the PyColors UI installation guide, then use UI usage patterns to build the next screen.

Keep exploring

Related articles around the same technical and product surface.

Next.js

How to build semantic design tokens with Tailwind CSS v4

Migrate a small Next.js settings card from hardcoded colors to semantic tokens, reproduce a dark-mode contrast failure, and verify the fix.

September 25, 202612 min read
Next.jsFeatured

Email Verification with Auth.js and Prisma: A Tested Token Lifecycle

Build an isolated email-verification example with hashed, purpose-bound tokens. Test expiry, concurrent consumption, rollback and the Auth.js credentials gate.

April 17, 202620 min read
SaaS ArchitectureFeatured

Why I Stopped Overengineering SaaS Starters

I removed complexity from my SaaS starter to make it more useful, easier to ship, and closer to what developers actually need.

March 19, 20268 min read
Next step

Build the product surface first. Upgrade when wiring becomes the bottleneck.

Use the blog for product engineering insight, Starter Free for validation, and Starter Pro when real auth, billing, and protected architecture should already be handled.