Your own design system
Keep one layer file in your product. One command writes tokens and code for every platform you ship, in one pull request.
Tanrei’s components never change when your product’s look does. What changes is a layer: a small JSON file that sets values of the shared vocabulary and declares your palettes. @tanrei/cli turns that one file into everything each platform needs.
Prerelease
0.1.0-next.0 is published to npm under the dist-tag next: until 0.1.0, install @tanrei/cli@next and @tanrei/mcp@next. During the 0.x prereleases backward compatibility is not kept: any prerelease may break anything, with no deprecation period first, and its changeset says what broke.
Add the command line tool to your product as a development dependency; every npx tanrei … command on this page then runs that copy. It carries Theme Studio, which tanrei studio serves.
npm install --save-dev @tanrei/cliThe MCP server needs no install: npx fetches @tanrei/mcp when an agent starts it, as registered further down this page.
To use the source as it stands on main, build both packages in a clone of the repository and install the packed files in your product as development dependencies:
git clone https://github.com/chnotchy/tanrei.git
cd tanrei
pnpm install
pnpm --filter @tanrei/studio build:app # Theme Studio, which `tanrei studio` serves
pnpm --filter @tanrei/cli --filter @tanrei/mcp build
pnpm --filter @tanrei/cli --filter @tanrei/mcp pack --pack-destination ../tanrei-packages# in your product: the path to the folder the packages were written to
npm install --save-dev path/to/tanrei-packages/tanrei-cli-*.tgz path/to/tanrei-packages/tanrei-mcp-*.tgzFor the MCP server, register the installed copy instead: claude mcp add tanrei -- npx tanrei-mcp, run in your product’s folder.
-
Start a layer — from the default system or from a preset.
npx tanrei init acme --extends tanreiThis writes
design/acme.system.jsonandtanrei.json. Prefer sliders? Shape the layer in the Theme Studio and download it instead. -
Say where each output goes in
tanrei.json. List only what you ship.{ "system": "design/acme.system.json", "kotlinPackage": "com.acme.design", "outputs": { "tokens": "design/tokens/acme", "web": "apps/web/src/styles", "swiftui": "apps/ios/Acme/Design", "compose": "apps/android/app/src/main/kotlin/com/acme/design", "flutter": "apps/flutter/lib/design", "react-native": "apps/mobile/src/design" } } -
Build.
npx tanrei buildCommit the result. Add
npx tanrei build --checkto CI: it writes nothing and fails when a committed output is stale. -
Use it. The generated files contain your system’s values; the types come from the library, so a vocabulary change breaks the build instead of drifting silently.
Platform You get You write Web acme.css<html data-tanrei-system="acme">SwiftUI TanreiSystem+Acme.swift.tanreiTheme(TanreiTheme(system: .acme))Compose TanreiSystemAcme.ktTanreiTheme(system = TanreiSystem.Acme) { … }Flutter acme_system.g.dartTanreiTheme(data: TanreiThemeData.platform(context, system: acmeSystem), …)React Native acme.ts<TanreiProvider system={acme}>
Edit a value in the layer, run tanrei build, and every platform’s file changes in the same commit. Making all text a little larger is one line:
"tanrei.dial.type.base": { "$value": { "value": 17, "unit": "px" }, "reason": "Read at arm's length." }See Design systems and customization for the four things a layer can do to a value — set a dial, scale a token, re-point an alias, pin a value with a reason — for declaring palettes such as a brand color or a quiz’s correct and incorrect, and for adding tokens when values are not enough.
Customization is layered, and every level edits the one <id>.system.json:
| You want | You use |
|---|---|
| A quick setup | Theme Studio → the rail: the nine rows of the look — brand color, gray, typeface, size, corners, corner smoothing, icons, press feedback, focus — each a press away, with a lock and a shuffle |
| To tune groups | Studio → the rail’s More: all eight dials, every palette, each component, every behaviour |
| To touch one token at a time, in a GUI | Studio → Inspect → Tokens: search the whole vocabulary; each token offers only the edits that are legal for it, and shows what follows it |
| To edit source | Studio → Inspect → Source, or your editor: the JSON, validated against the schema and by the engine |
| A token that does not exist | Add it: a step in a series, a Button size or variant in one line, or tokens of your own |
npx tanrei studioopens Theme Studio on your layer file: edits are saved to it as you make them (review them with git like any change), an edit made in your editor at the same time is never overwritten, and “Build” runs tanrei build. It listens on 127.0.0.1 only and the printed link carries a one-time key. The Studio on this site is the same tool with import and export instead of a file.
npx tanrei audit srcreads your product’s code — script (JS, TS, JSX), markup (HTML, Vue, Svelte, Astro) and stylesheets (CSS, Sass, Less) — and reports what your system does not have, each at its file, line and column, with what to write instead:
- an element of a component the system does not have —
<tanrei-fab-menu>in a page, a template, JSX or a string, an imported React wrapper, or a CSS selector that names it — with the components the system ships for the same job, the systems that have it, and the line of your layer that would take it in; - an import from an entry that does not fit the system —
@tanrei/web/m3-likeor@tanrei/react/allin a product of the default system — with the entry that does; - a written
variant="…"the system leaves out, with the values it offers.
It checks against your product’s system (tanrei.json), or the one --system names: a shipped system such as m3-like, or a layer file. Give it files, directories, or a glob in quotes ('src/**/*.{ts,tsx,html,css}'); a directory is read without node_modules and hidden directories. --format json prints the findings as data. It exits with 1 when it finds something, so it can run in CI beside tanrei build --check, and with 2 when it cannot run. The MCP server’s check_usage runs the same check.
It reads the text, not a running page: comments are skipped, but a variant held in a variable, or a tag put together from pieces, is not seen.
Add "tailwind" or "shadcn" to outputs. Both files only map names onto Tanrei’s custom properties, so themes, contrast levels and the live dials keep working underneath.
@import "tailwindcss";
@import "@tanrei/web/tokens.css";
@import "./styles/acme.css";
@import "./styles/acme.tailwind.css"; /* bg-primary · text-on-primary · rounded-medium · p-200 · text-heading-4 */-
The
tokensoutput is a self-contained DTCG 2025.10 bundle with a standard resolver. Any design-token tool can read it; the layer files that produced it travel inside, and every group carries usage guidance for agents in$extensions["dev.tanrei.llm"]. -
It also holds
components.json, the components your system ships: each with its element (or class), its React name and the module that registers it, and the components it does not have, with the layer that left each out and what to use instead. An agent that reads it suggests only components your product can use. Each shipped system has one as well:@tanrei/web/systems/<id>.components.json. -
@tanrei/mcpis a
Model Context Protocol server. An agent working in your repository can search the vocabulary, see where a value comes from, compare systems, propose an extension (a new Button size, a step, a token of your own) with its resulting values, and validate a layer edit before it writes it — and it is told the rules: change values through a layer, extend the vocabulary only by adding and with a rationale, give every pinned value a reason, never touch a threshold. For the UI itself it answers from the system’s components manifest and the specs: which components the system has (and, for one it does not, what to use instead), a component’s props, events and behaviour rules, and which elements, imports and variants in your code the system does not have, with where they are — the check tanrei auditruns.Register it from npm, in your product’s folder (Using Tanrei with AI agents has the settings for other clients):
claude mcp add tanrei -- npx -y @tanrei/mcp
Accessibility
Contrast thresholds and structural checks are constants. In your layer a violation is a warning, never silent: it is printed by tanrei build, written to a11y-report.json, and stamped at the top of every generated file. At medium and high contrast, aim for zero — those schemes exist because a person asked for contrast. The shipped presets themselves are held to fidelity to the systems they imitate, not to Tanrei’s accessibility criteria, in every scheme; their measurements are information. A layer you build on a preset is still your product, and is warned about what it inherits too.