---
title: "Your own design system"
description: "Keep one layer file in your product. One command writes tokens and code for every platform you ship, in one pull request."
url: "https://tanrei.chnotchy.com/getting-started/your-design-system/"
last_updated: 2026-10-23T11:50:31.000Z
---

# 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.

> [!WARNING]
> **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.

## Install

### From npm

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.

```sh
npm install --save-dev @tanrei/cli
```

The MCP server needs no install: `npx` fetches `@tanrei/mcp` when an agent starts it, as registered further down this page.

### From source

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:

```sh
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
```

```sh
# 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-*.tgz
```

For the MCP server, register the installed copy instead: `claude mcp add tanrei -- npx tanrei-mcp`, run in your product’s folder.

## From a layer to every platform

1. **Start a layer** — from the default system or from a preset.

   ```bash
   npx tanrei init acme --extends tanrei
   ```

   This writes `design/acme.system.json` and `tanrei.json`. Prefer sliders? Shape the layer in the [Theme Studio](https://tanrei.chnotchy.com/foundations/theme-studio/) and download it instead.

2. **Say where each output goes** in `tanrei.json`. List only what you ship.

   ```json
   {
     "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"
     }
   }
   ```

3. **Build.**

   ```bash
   npx tanrei build
   ```

   Commit the result. Add `npx tanrei build --check` to CI: it writes nothing and fails when a committed output is stale.

4. **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.kt` | `TanreiTheme(system = TanreiSystem.Acme) { … }` |
   | Flutter | `acme_system.g.dart` | `TanreiTheme(data: TanreiThemeData.platform(context, system: acmeSystem), …)` |
   | React Native | `acme.ts` | `<TanreiProvider system={acme}>` |

## One change, one pull request

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:

```json
"tanrei.dial.type.base": { "$value": { "value": 17, "unit": "px" }, "reason": "Read at arm's length." }
```

See [Design systems and customization](https://tanrei.chnotchy.com/foundations/design-systems/) 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](https://tanrei.chnotchy.com/foundations/design-systems/#adding-to-the-vocabulary) when values are not enough.

## As much control as you want — always the same file

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](https://tanrei.chnotchy.com/foundations/design-systems/#adding-to-the-vocabulary): a step in a series, a Button size or variant in one line, or tokens of your own |

```bash
npx tanrei studio
```

opens 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.

## Check the code against your system

```bash
npx tanrei audit src
```

reads 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-like` or `@tanrei/react/all` in 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.

## Tailwind CSS and shadcn/ui

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.

```css
@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 */
```

## Sharing a system — with people, tools and AI agents

- The `tokens` output 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/mcp` is a [Model Context Protocol](https://modelcontextprotocol.io/) 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 audit` runs.

  Register it from npm, in your product’s folder ([Using Tanrei with AI agents](https://tanrei.chnotchy.com/getting-started/ai-agents/) has the settings for other clients):

  ```bash
  claude mcp add tanrei -- npx -y @tanrei/mcp
  ```

> [!WARNING]
> **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.
