Consuming ========= Spec: descriptive guidance, not a checkable rule. It carries no forbidden and instead pair, and no `jig check` detector applies to it directly. Section: Design Tokens **Plain CSS, any framework** ```css @import "./jig/theme.css"; .card { background: var(--color-bg-raised); border: 1px solid var(--color-stroke-weak); border-radius: var(--radius-surface); padding: var(--spacing-card); } ``` **Tailwind v4** — the barrel is imported flat, exactly as anywhere else ```css @import "tailwindcss"; @import "./jig/theme.css"; ``` That is all that is required, and it is what `init` writes. Every token is readable as `var(--color-text-strong)` in any stylesheet or component. **Do not nest the import inside `@theme`.** Earlier versions of this file told you to, and Tailwind rejects it outright: ``` @theme blocks must only contain custom properties or @keyframes. ``` `@theme` takes declarations, not an `@import`, and Jig's tokens cannot move into one regardless: `@theme` requires them top-level and unnested, while Jig's live in `:root` and are redeclared under `[data-theme="dark"]` and a `prefers-color-scheme` query. That structure is what makes dark mode work. ### Optional: Tailwind utility classes The flat import gives you the tokens. It does **not** give you `p-card` or `rounded-surface` as classes — Tailwind only generates utilities for names declared in `@theme`. If you want them, add one alias block: ```css /* jig/utilities.css — one per project, not one per mode */ @theme inline { --color-text-strong: var(--color-text-strong); --color-bg-base: var(--color-bg-base); --spacing-card: var(--spacing-card); --radius-surface: var(--radius-surface); /* …every token you want as a utility */ } ``` ```css /* your root stylesheet */ @import "tailwindcss"; @import "./jig/utilities.css"; ``` Then `
` works, and the value still comes from whichever mode barrel that route loaded — so one set of utilities serves every mode, with no `dark:` variants and nothing per-mode. **`@theme` and `@theme inline` behave identically here.** Both generate the utilities; both also emit a self-referential declaration you will see in the compiled CSS: ```css @layer theme { :root, :host { --radius-surface: var(--radius-surface) } } /* Tailwind's */ :root { --radius-surface: var(--radius-md) } /* Jig's */ ``` **This is not a bug and must not be "fixed".** Tailwind's copy is inside `@layer theme`; Jig's is unlayered. Unlayered declarations beat layered ones in the cascade regardless of source order, so Jig's value always wins. Removing either one breaks something: drop the alias and the utility stops existing, drop Jig's and the token has no value. **The alternative, if the duplicate bothers you:** alias to *different* names, the way a project with its own semantic layer would. ```css @theme { --color-ink: var(--color-text-strong); --color-paper: var(--color-bg-base); } ``` Utilities become `text-ink`, `bg-paper`. No self-reference, no duplicate declaration, and the names read as yours rather than as Jig's. The cost is a mapping to maintain. Both arrangements are correct; this is a naming preference, not a correctness one. **A missing alias fails silently.** A class whose token is not in the block renders onto the element and matches no rule — no error, no warning, no style. Generate the block rather than hand-maintaining it, and regenerate it when the token layer changes. **In a monorepo, add `@source` for every workspace package that uses these utilities.** Tailwind v4's content detection does not cross package boundaries: a package reached through a `node_modules` symlink is skipped by design, so a utility used *only* inside `packages/ui` is never generated. ```css @source "../../../../packages/ui/src"; ``` The failure mode is the reason this is worth stating. Nothing errors — the class lands on the element and no rule exists to match it, so the style simply does not apply. Most utilities survive by coincidence, because the app happens to use the same ones; the ones that do not are whatever only the shared package uses, which tends to be its theme and state handling. Observed in a real project as a theme switch that had silently never worked. **One import per surface, through a barrel.** `init` writes `jig/theme.css`, which imports the brand file and then one mode file, and wires that single line into your stylesheet. Your stylesheet then never changes again: switching the mode in `jig.config.json` rewrites the barrel, not your CSS. A barrel holds exactly **one** mode, never a merge. The three mode files declare the same token names with different values, so importing all three into one document leaves only the last — the other two are inert. That is the mechanical reason behind the seam rule below. **Multiple modes in one app** — scope by route, not by class. Every barrel names its mode, `jig/theme..css`, and each route's layout or entry point imports the one for its mode. The global stylesheet imports **no** barrel: one imported there puts that mode's tokens under every route, so the operator pages carry editorial's too and get whichever loaded last. It keeps what every route shares — Tailwind and `utilities.css`, whose aliases name tokens rather than values and so hold for any mode. When a second mode is declared, `init` replaces `theme.css` with `theme..css` and removes the import it had wired, then prints which barrel each surface imports; which layout serves `/admin/**` is your routing, which it cannot see. Do not attempt to nest two modes in one document (`01-modes.md`, seam rules).