Consuming
This is a spec: descriptive guidance, not a checkable rule. It carries no forbidden and instead pair, and no jig check detector applies to it directly.
The spec
Plain CSS, any framework
@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
@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:
/* 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 */
}/* your root stylesheet */
@import "tailwindcss";
@import "./jig/utilities.css";Then <article class="bg-bg-base p-card rounded-surface"> 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:
@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.
@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.
@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.<mode>.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.<first mode>.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).
- Kind
- Spec
- Section
- Design Tokens