Alpha — experimental. Breaking changes ship without a deprecation cycle, and nothing here carries a stability promise yet.What is built vs. planned →

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:

derivation

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