Configuration reference
One file: transtyle.config.json, in your project root. It’s the only Transtyle-specific file in a project — everything else is standard DTCG. Config is data: no transtyle.config.ts, by design (introspectability, portability, determinism).
The $schema line at the top is a real, published JSON Schema — editors that honor it give you autocomplete and inline validation as you type. The transtyle.dev URL below is the schema’s permanent identifier, not a download link: that domain is not registered yet, so an editor that fetches it literally will come up empty. The identical file is served from this site at the link above, and the compiler fetches neither — it validates against its own bundled copy. The compiler validates the same schema at load time: an unknown or mistyped key is an error (TST1010), never silently ignored, and each target’s options are checked against the selected exporter’s own schema (TST1011) — so a wrong era or a stray option fails the build with the exact path, rather than being dropped without warning.
Full annotated example:
{
"$schema": "https://transtyle.dev/schemas/config/v0.json",
"name": "acme-design-system",
"tokens": [
"tokens/base.tokens.json",
{ "files": "tokens/dark.tokens.json", "mode": { "color-scheme": "dark" } },
"tokens/bindings.tokens.json"
],
"modes": {
"color-scheme": { "values": ["light", "dark"], "default": "light" }
},
"derivation": {
"rules": "standard@1",
"autoDark": false,
"require": ["semantic.color.primary"]
},
"targets": {
"shadcn": { "output": "dist/shadcn", "options": { "era": "tailwind-v4" } },
"shadcn-v3": {
"exporter": "shadcn",
"output": "dist/shadcn-v3",
"options": { "era": "tailwind-v3" }
}
},
"check": {
"failOn": "error",
"contrast": { "standard": "wcag21-aa" }
}
}
name
Used in generated file headers and usage docs. Pick something stable; it’s your design system’s identity in every artifact.
tokens — ordered layers
Think of the layers as transparent sheets stacked on a lightbox: each one can only add to or paint over what’s below it, and you read the stack from the top. Later entries win.
Each entry is a glob string (base layer) or { "files": glob | [globs], "mode": { dimension: mode } } (mode-scoped layer, a pure DTCG file whose values apply to one mode). Globs support single * segments (tokens/*.tokens.json); matched files load in sorted order for determinism.
Worked example. Three files, in this order:
"tokens": [
"tokens/option.tokens.json",
"tokens/semantic.tokens.json",
{ "files": "tokens/dark.tokens.json", "mode": { "color-scheme": "dark" } }
]
| File | Contains | Effect |
|---|---|---|
option.tokens.json |
option.color.blue.500 = #3b5bdb |
A raw value. Nothing binds to it yet. |
semantic.tokens.json |
semantic.color.primary.solid = {option.color.blue.500} |
Binds meaning to the raw value — this is the alias that makes it your brand color. |
dark.tokens.json |
semantic.color.text.base = #f8f9fa |
Applies only in dark mode. Light mode keeps whatever the base layers said. |
The mode-scoped layer never has to repeat anything: it lists only the tokens that genuinely differ in that mode, which on a real design system is usually a handful of neutrals. Everything else — including every derived value — recomputes per mode from what’s underneath.
| Rule | Diagnostic |
|---|---|
| Glob matches nothing | TST1001 warning |
| Token defined twice across base layers | TST1103 warning, last wins |
| Mode value overrides an earlier one | TST1108 warning |
| Mode value for a token with no default value | TST1107 warning, skipped |
Mode not declared in modes |
TST1109 error |
modes
Declares the axes your design system varies along. Each dimension lists its values and names a default; the compiler resolves every combination of them.
default names your design system’s native mode — the one plain $values describe. It does not reorder exporter output: exporters bind mode names, so a dark-native system still gets shadcn’s light-first structure. See Weird things for why.
Worked example — the Acme example declares two dimensions:
"modes": {
"color-scheme": { "values": ["light", "dark"], "default": "light" },
"density": { "values": ["comfortable", "compact"], "default": "comfortable" }
}
which resolves to four full token maps: light+comfortable, light+compact, dark+comfortable, dark+compact. Every derived value is computed independently in each one — a dark-mode hover state darkens or lightens according to that combination, not by translating the light-mode answer.
Two constraints worth knowing:
- The first dimension carries light/dark. Order matters: the first dimension listed is the polarity axis — the one exporters bind as light/dark. So if you use
color-scheme, list it first. Declaring it after another dimension (e.g.densityfirst) would make dark mode silently never reach any exporter — the dark values still resolve into their combinations, but no target reads them, so every exporter emits a dark block filled with light values. Because that output is guaranteed wrong, Transtyle makes it a build error (TST1112), not a warning — reordermodesto fix it. - One dimension per layer. A mode-scoped token file targets
{ "color-scheme": "dark" }, never two axes at once (TST1110). Compose combinations from single-axis layers; that is what keeps “which file set this value?” answerable. - Exporters express what their target can express.
color-schememaps everywhere;densityhas no Bootstrap or PrimeNG counterpart, so it appears in those reports as an honestdroppedrow naming the dimension, rather than being silently flattened.
derivation
rules— the rule pack, pinned with a version (standard@1). Pinning means upgrading Transtyle can never silently change your compiled theme.autoDark— defaultfalse. Regardless of this setting, a role’s.solidyou didn’t author for a non-defaultcolor-schemevalue falls back to the default-mode color (brand colors stay identical across modes) — deliberate, surfaced byTST1204. WhatautoDark: trueadds today: that carry-over is classifiedderivedin coverage instead ofauthored, soreport.jsonshows synthetic dark-theme coverage honestly. Computing a genuinely different dark color is specced but not yet implemented — an open, deliberately deferred research question (see the roadmap), not a missing wire-up.require— tokens that must be authored, not derived. Build fails withTST1202otherwise. Use this to encode team policy (“nobody ships a derived brand color”). A color role may be named at the role (semantic.color.primary) or the anchor cell (semantic.color.primary.solid); both check the.solidcell, which is the one the grid is built from.overrides— per-slot derivation rules (specced, not yet implemented; today, simply author the token — authored always wins).
Note that require is a policy knob, not the engine’s own floor. semantic.color.primary.solid is required whether or not you list it — nothing can invent your brand color — and its absence is TST1201, which fires even with no derivation block at all.
targets — instances, not just names
Each key is a target instance. The optional exporter field selects the plugin (defaults to the key), which is how one exporter runs twice with different options — e.g. shadcn in both Tailwind eras. output is the emit directory (relative to the config). options are exporter-specific; see each exporter’s page.
transtyle build builds all instances; transtyle build shadcn-v3 selects by instance name.
check
failOn—error(default) |warning|approximation: the diagnostic level that makes the build exit non-zero. CI teams typically tighten this over time.contrast.standard—wcag21-aa(4.5:1, the default) orwcag21-aaa(7:1); APCA is specced. Contrast checks run on text/background pairs and every derived on-color; failures are warnings (TST2101), never silent. The same threshold drivestranstyle diff’s contrast-regression flag, socheckanddiffalways agree on what “passing” means.