tailwind2-5 Exercise 3: A Shared Theme Package in a Monorepo ============================================================== Setup: create this layout, then run npm install vite @tailwindcss/vite tailwindcss in the root (npm workspaces link the packages into node_modules/@acme), and npx vite build in apps/web. mono/ ├── package.json ├── packages/ │ ├── theme/ (package.json, theme.css) │ └── ui/ (package.json, index.js) └── apps/web/ (package.json, vite.config.js, index.html, src/main.js, src/style.css) --- package.json (root) --- {"name":"mono","private":true,"workspaces":["packages/*","apps/*"],"dependencies":{"@tailwindcss/vite":"^4.3.3","tailwindcss":"^4.3.3","vite":"^8.3.4"}} --- packages/theme/package.json --- { "name": "@acme/theme", "version": "1.0.0", "exports": { "./theme.css": "./theme.css" } } --- packages/theme/theme.css --- @theme { --color-brand: oklch(0.55 0.2 260); --color-accent: oklch(0.7 0.15 150); } --- packages/ui/package.json --- { "name": "@acme/ui", "version": "1.0.0", "type": "module", "exports": { ".": "./index.js" } } --- packages/ui/index.js --- export const button = 'rounded-lg bg-brand px-4 py-2 text-white hover:bg-accent'; --- apps/web/package.json --- { "name": "web", "private": true, "type": "module", "dependencies": { "@acme/theme": "1.0.0", "@acme/ui": "1.0.0" } } --- apps/web/vite.config.js --- import { defineConfig } from 'vite'; import tailwindcss from '@tailwindcss/vite'; export default defineConfig({ plugins: [tailwindcss()] }); --- apps/web/index.html ---
--- apps/web/src/main.js --- import './style.css'; import { button } from '@acme/ui'; document.getElementById('app').innerHTML = `

app class

`; --- apps/web/src/style.css, three versions --- 1: @import "tailwindcss"; @import "@acme/theme/theme.css"; 2: @import "tailwindcss"; @import "@acme/theme/theme.css"; @source "../../../packages/ui"; 3: @import "tailwindcss"; @source "../../../packages/ui"; Results (does the built CSS contain a rule for each class?): 1 (no @source) 2 (theme + @source) 3 (@source, no theme) bg-brand 0 1 0 text-white 0 1 1 px-4 0 1 1 hover:bg-accent - 1 - text-accent (app's own) 1 1 0 --color-brand variable absent oklch(55% .2 260) absent WHY THIS WORKS AS AN ANSWER --------------------------- The class names live in packages/ui/index.js, which is outside the app's folder; the automatic scan starts from the directory the build runs in and does not follow the link in node_modules, so version 1 produced nothing for the shared button (the only rule is text-accent, written in the app itself). @source "../../../packages/ui" adds that folder; the path is relative to the CSS file (apps/web/src), so three levels up reach the monorepo root. Version 3 shows the other half: the theme is a separate package imported with @import "@acme/theme/theme.css" (the package exposes the file in its "exports"). Without that import, --color-brand and --color-accent do not exist, so bg-brand and text-accent silently produce no rule at all; the only classes left are the ones that need no custom token (text-white, px-4). Neither mistake raises an error, so check for a known class in the result after wiring a new app to the shared packages. One more observation from this kind of layout: Next.js printed "We detected multiple lockfiles and selected the directory of C:\lwdj\tw2\package-lock.json as the root directory" when I built an example below another project's lockfile. That warning is about the workspace root, the same question your @source paths answer.