Tokens and modes
The CLI covered every command the CLI has, run from a terminal with no agent at all. This chapter changes subject again: not commands, but the declaration and the layer every earlier chapter has relied on, jig.config.json in full, and the token layer it points at. Nothing here differs by which agent you use or which of Getting started’s two setup paths you took.
4 things, in the order a project meets them once it exists: what jig.config.json declares, field by field (4 of them); the 3 interface modes a surfaces entry chooses between; where the resulting token layer lives; and the 3 ways a component reads it.
jig.config.json
jig.config.json declares your project’s own choices so mode selection does not require asking on every task. It takes 4 fields, in this order:
brand: sets where the token layer lives;initwrites the brand file there and puts the mode files beside it. The default location, unset, is covered under “Where the token layer goes,” below.surfaces: one entry per surface, each{ match, mode }. It outranks an agent’s own reading of the project, so it is worth getting right beforeinitruns.exempt: files that render outside the cascade altogether (a generated social card, a PDF or email template) where a literal value is the only thing that works, or an old page unlikely to be revisited. An exact path is preferred over a glob, since an exemption is a claim about one file’s rendering context.checklists every entry and how many files it matched on every run, and says when one matches nothing. Nothing is exempt by default.critique: when pages are critiqued. Left out, it is decided per page each time."at-ship"says once that critiques and a tweak’s re-judge wait for/jig ship, though a page’s own spec can still saycritique: eachorcritique: at-shipfor itself.
README’s own worked config below declares every field at once: a project choosing editorial, product and operator across its surfaces, exactly what that field is for.
// jig.config.json
{
// Where the token layer lives. `init` writes the brand file here and puts
// the mode files beside it. Omit it and the layer follows your own
// layout , beside the stylesheet init wires, or the project root.
"brand": "src/styles/jig/brand.acme.css",
// One entry per surface. This outranks an agent's own reading of the
// project, so it is worth getting right before `init` runs.
"surfaces": [
{ "match": "/", "mode": "editorial" },
{ "match": "/app/**", "mode": "product" },
{ "match": "/admin/**", "mode": "operator" }
],
// Files `check` should skip. Empty unless you add to it; Jig never does.
// Add:
// - a file that renders outside your stylesheets (a generated social
// card, a PDF or email template), where literal values are the only
// thing that works;
// - an old page you will not revisit. Plain `check` reads only the files
// you change, but `check --all` and `ship` read every file.
//
// Write each path from the project folder (where this file is), the way
// your editor shows it: "src/social-card.tsx". A whole folder:
// "src/emails/**". `check` lists every entry and how many files it
// matched on every run, and tells you when one matches nothing.
"exempt": ["src/social-card.tsx", "src/emails/**"],
// When pages are critiqued. Leave it out to decide each time; "at-ship"
// says once that critiques and a tweak's re-judge wait for `/jig ship`,
// which will not pass until every page is judged as it stands. A page's
// spec can say otherwise for itself: `critique: each` or `critique: at-ship`.
"critique": "at-ship"
}Without this file, mode selection falls to the choosing procedure below: infer, state the inference in one line, and ask when the signals conflict.
Choosing a mode
3 interface modes exist, and a surfaces entry chooses between them:
editorial: for marketing sites, blogs, documentation, brochureware, landing pages.product: for authenticated application UI, customer-facing dashboards, settings, onboarding.operator: for internal tools, admin areas, back-office, data entry, monitoring.
Choosing between them is a short procedure, in order. If the project config already names a mode for this surface (the surfaces entry above), that wins. If it does not, infer from the project instead, state the inference in one line before building, and ask rather than guess when the signals conflict: a mode decision is expensive to reverse.
One sentence resolves it when the choice is close: editorial optimises for first use, operator for the thousandth, and product sits between and must serve both. The full per-mode tables, type ratio, control height, motion budget and the rest, are Interface modes’ own page in the Reference, not repeated here.
Where the token layer goes
Unset, the token layer sits beside the stylesheet init wires (src/styles/jig/ for a project whose CSS lives in src/styles/), or the project root when there is no stylesheet to follow. init prints the path it chose. Set brand (above) to put it somewhere else.
3 files live there:
- The brand file: identity, generated for the project, edited freely.
- The mode file,
mode.<mode>.css: the one file copied verbatim from the package, since a stylesheet@importhas to resolve locally on every machine that builds it, andupdaterefreshes it. - The barrel,
theme.css: brand then mode, in that order. This is the file a project imports.
A project declaring more than one mode across its surfaces gets one barrel per mode instead of a single shared one: the worked example above, naming editorial, product and operator, gets theme.editorial.css, theme.product.css and theme.operator.css. No barrel sits in a shared, global stylesheet; each route’s own layout imports the barrel for its mode, which init names but does not wire, since which entry point serves which route is the project’s own routing.
Commit the token directory (<css dir>/jig/) and .jig/ both: ignoring either means a teammate’s build breaks on a missing import, or their update can no longer tell a file they edited from one it wrote.
Wiring itself is yours to direct. init’s default (one import into the one obvious entry stylesheet) is a convenience, not a requirement: skip it and import the two files directly, take only the brand file, inline them into a build step, or wire a different barrel per route. Every file init writes is checksummed, so an edit survives the next update.
One constraint that is not a preference: the tokens are declared on :root, so the import has to reach the page globally. Imported inside a scoped component block or a CSS module, the tokens exist only there.
Consuming tokens
3 ways to read a token once the barrel is imported, in this order.
Plain CSS, any framework. One import, the barrel:
@import "./jig/theme.css";Then var(--color-text-strong), var(--spacing-card), var(--text-body) anywhere: plain CSS, CSS modules, styled-components, Vue, Svelte, Rails alike, ordinary custom properties with no dependency on anything.
Tailwind v4. The same import, alongside Tailwind’s own:
@import "tailwindcss";
@import "./jig/theme.css";Jig needs nothing from Tailwind, and Tailwind needs nothing from Jig: that is what init already wires, and it is enough on its own.
The optional utility alias block. Tailwind can also generate utility classes from the tokens, p-card, rounded-surface, text-text-strong, but only for names declared in a @theme block:
@import "tailwindcss";
@import "./jig/utilities.css"; /* the generated @theme block */So init offers to generate one, and asks before writing it, since it changes how every component in the project is written and both arrangements are correct. Under --yes it declines and says how to get it. One set of utilities serves every mode: the utility references the variable rather than a resolved value, so whichever mode barrel a route loaded supplies it, with no dark: variant and nothing written per mode.
Do not nest the import inside the @theme block itself: Tailwind rejects it (“@theme blocks must only contain custom properties or @keyframes”), and Jig’s tokens cannot move into one regardless, since they live in :root and are redeclared under [data-theme="dark"] and a prefers-color-scheme query, which is what makes dark mode work at all.
The rest, including why a duplicate declaration in the compiled CSS is correct and must not be “fixed,” is Design Tokens’ own page in the Reference.