<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Transtyle</title>
    <link>https://transtyle.github.io/transtyle/blog/</link>
    <atom:link href="https://transtyle.github.io/transtyle/blog/rss.xml" rel="self" type="application/rss+xml"/>
    <description>Releases, design decisions, and findings from compiling real design systems into real ecosystems.</description>
    <language>en</language>
    <image>
      <url>https://transtyle.github.io/transtyle/feed-icon-144.png</url>
      <title>Transtyle</title>
      <link>https://transtyle.github.io/transtyle/blog/</link>
      <width>144</width>
      <height>144</height>
    </image>
    <docs>https://www.rssboard.org/rss-specification</docs>
    <item>
      <title>A compiler for design systems</title>
      <link>https://transtyle.github.io/transtyle/blog/a-compiler-for-design-systems/</link>
      <guid isPermaLink="true">https://transtyle.github.io/transtyle/blog/a-compiler-for-design-systems/</guid>
      <pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator>Julien Déramond</dc:creator>
      <description>An experiment I finally had time to run: describe a design system once, compile native themes for every ecosystem you ship in. What it is, how it works, and what four real systems look like coming out the other end.</description>
      <content:encoded><![CDATA[<h2 id="first-what-this-actually-is">First, what this actually is</h2>
<p>I have had this idea for years. Every time I met it again — at work, where design systems are my
job; in Bootstrap, where I maintain one of the frameworks this thing now compiles for; in the
various tools and contributions that fill the rest of the week — I thought <em>somebody should build
that</em>, and then life carried on. Evenings and weekends are not where a compiler gets written. So
the idea stayed an idea, for a very long time.</p>
<p>This is the experiment I finally ran, and I ran it with Claude. Transtyle is <strong>vibe coded</strong>: I
brought the architecture, the design-system judgement and the arguments, and an AI wrote most of the
lines. That is worth saying plainly for two reasons.</p>
<p>The first is that it inevitably contains AI slop. There is code in here I would have written
differently, comments that are longer than they need to be, and abstractions that exist because they
seemed reasonable at 11pm. I am not going to pretend otherwise, and I would rather you find it
knowing that than find it thinking a team of five spent a year on it.</p>
<p>The second is that the result is genuinely encouraging — encouraging enough that I want other people
to look at it. This is something I could not have built alone. Not because the ideas were beyond me,
but because the sheer volume of it — eight exporters against real framework surfaces, a derivation
engine, thirty-two runnable demos, a conformance kit, the checks that hold it all together — is
hundreds of hours I was never going to have. The experiment was as much about whether that kind of
project is now reachable for one person with a day job as it was about design tokens.</p>
<p>It turns out it is. So here is what came out.</p>
<h2 id="the-problem-in-one-paragraph-you-can-verify-yourself">The problem, in one paragraph you can verify yourself</h2>
<p>Your brand blue lives in six places.</p>
<p>It is a hex value in Figma. It is <code>$primary</code> in a Bootstrap Sass override. It is <code>--primary</code> in a
shadcn <code>globals.css</code>, twice — once for light mode, once for dark. It is <code>color[0]</code> in an ECharts
theme JSON. It is <code>colorPrimary</code> in a Storybook manager config. Somewhere there is a <code>.docx</code> brand
book with the same hex printed under a paragraph nobody reads.</p>
<p>Change the blue, and you have six pull requests, five reviewers who each know one framework, and one
of them will be missed. Six months later nobody can say which copy is authoritative — only which one
is on production.</p>
<p>That is the tax. Every organization with more than one front-end framework pays it, forever, and it
is nobody’s job because it is everybody’s.</p>
<h3 id="the-shape-of-the-problem">The shape of the problem</h3>
<p>The tax is not a discipline failure. It is arithmetic. With <strong>N</strong> design systems and <strong>M</strong> target
ecosystems, hand-maintained theming costs <strong>N × M</strong> mappings — and every framework upgrade
invalidates a column.</p>
<svg id="nxm" viewBox="0 0 760 250" role="img" aria-label="Left: three design systems each connected by a separate line to four frameworks — twelve mappings. Right: the same three systems connected to one shared pivot, which connects to the four frameworks — seven mappings." style="max-width:100%;height:auto;margin:1.5rem 0">
  <style>
    /* Selectors are id-scoped on purpose: this is raw HTML inside markdown, so
       a bare `.nl` here would be a global rule on whatever page includes it.
       Every var() carries its light-theme fallback: this diagram also travels
       in the RSS feed, where the site's stylesheet doesn't exist and an
       unresolved var() would drop to the SVG default of solid black. */
    #nxm .nl { stroke: var(--border, #dbdee3); stroke-width: 1.4; fill: none; }
    #nxm .nl.hi { stroke: var(--primary, #2d69de); stroke-width: 1.8; }
    #nxm .nb { fill: var(--surface, #ffffff); stroke: var(--border, #dbdee3); stroke-width: 1.2; }
    #nxm .nb.hub { fill: var(--primary-soft, #e0ecff); stroke: var(--primary, #2d69de); }
    #nxm .nt { fill: var(--text, #12161d); font: 600 11px 'JetBrains Mono Variable', monospace; }
    #nxm .nt.hub { fill: var(--primary-strong, #0e4ec8); }
    #nxm .nc { fill: var(--text-muted, #4f5661); font: 700 11px 'Inter Variable', system-ui, sans-serif; letter-spacing: 0.06em; }
    #nxm .nn { fill: var(--text-muted, #4f5661); font: 400 11px 'Inter Variable', system-ui, sans-serif; }
  </style>
  <text class="nc" x="0" y="14">BY HAND — N × M</text>
  <text class="nn" x="0" y="238">3 systems × 4 frameworks = 12 mappings to maintain</text>
  <g>
    <path class="nl" d="M96 52 H244 M96 52 L244 92 M96 52 L244 132 M96 52 L244 172"/>
    <path class="nl" d="M96 106 L244 52 M96 106 L244 92 M96 106 L244 132 M96 106 L244 172"/>
    <path class="nl" d="M96 160 L244 52 M96 160 L244 92 M96 160 L244 132 M96 160 L244 172"/>
    <g>
      <rect class="nb" x="6" y="38" width="90" height="28" rx="7"/><text class="nt" x="20" y="56">system A</text>
      <rect class="nb" x="6" y="92" width="90" height="28" rx="7"/><text class="nt" x="20" y="110">system B</text>
      <rect class="nb" x="6" y="146" width="90" height="28" rx="7"/><text class="nt" x="20" y="164">system C</text>
      <rect class="nb" x="244" y="38" width="98" height="28" rx="7"/><text class="nt" x="258" y="56">Bootstrap</text>
      <rect class="nb" x="244" y="78" width="98" height="28" rx="7"/><text class="nt" x="258" y="96">shadcn</text>
      <rect class="nb" x="244" y="118" width="98" height="28" rx="7"/><text class="nt" x="258" y="136">ECharts</text>
      <rect class="nb" x="244" y="158" width="98" height="28" rx="7"/><text class="nt" x="258" y="176">PrimeNG</text>
    </g>
  </g>
  <text class="nc" x="400" y="14">THROUGH A PIVOT — N + M</text>
  <text class="nn" x="400" y="238">3 bindings + 4 exporters = 7 mappings, each reusable</text>
  <g>
    <path class="nl hi" d="M484 52 L530 106 M484 106 H530 M484 160 L530 106"/>
    <path class="nl hi" d="M626 106 L666 52 M626 106 H666 M626 106 L666 136 M626 106 L666 176"/>
    <g>
      <rect class="nb" x="400" y="38" width="84" height="28" rx="7"/><text class="nt" x="412" y="56">system A</text>
      <rect class="nb" x="400" y="92" width="84" height="28" rx="7"/><text class="nt" x="412" y="110">system B</text>
      <rect class="nb" x="400" y="146" width="84" height="28" rx="7"/><text class="nt" x="412" y="164">system C</text>
      <rect class="nb hub" x="530" y="78" width="96" height="56" rx="10"/>
      <text class="nt hub" x="546" y="102">catalog</text><text class="nt hub" x="546" y="120">(meanings)</text>
      <rect class="nb" x="666" y="38" width="88" height="28" rx="7"/><text class="nt" x="678" y="56">Bootstrap</text>
      <rect class="nb" x="666" y="78" width="88" height="28" rx="7"/><text class="nt" x="678" y="96">shadcn</text>
      <rect class="nb" x="666" y="118" width="88" height="28" rx="7"/><text class="nt" x="678" y="136">ECharts</text>
      <rect class="nb" x="666" y="158" width="88" height="28" rx="7"/><text class="nt" x="678" y="176">PrimeNG</text>
    </g>
  </g>
</svg>
<p>This is the oldest trick in compiler engineering. Machine translation between twenty languages does
not hire translators for four hundred language pairs; it translates through a pivot. Babel does not
have a converter per syntax pair; it parses to one AST. LLVM does not have a compiler per
language-and-chip combination; it has frontends, an intermediate representation, and backends.</p>
<p><strong>Transtyle applies that architecture to design systems.</strong> You describe your system once, in
standard <a href="https://design-tokens.github.io/community-group/format/">W3C design tokens</a>. It compiles
native theme files for each ecosystem you target.</p>
<div class="schema" role="img" aria-label="You write design tokens; Transtyle normalizes, derives and validates them; you ship a native theme per framework plus a coverage report">
  <div class="s-col">
    <span class="s-kicker">You write</span>
    <span class="s-main">Your decisions, your names</span>
    <span class="s-sub">the tokens you already maintain, in a standard format any design tool can read</span>
  </div>
  <div class="s-arrow" aria-hidden="true">→</div>
  <div class="s-col hi">
    <span class="s-kicker">Transtyle compiles</span>
    <span class="s-main">translate · fill gaps · check</span>
    <span class="s-sub">binds your names to a shared vocabulary of meanings, completes what you never decided, verifies contrast</span>
  </div>
  <div class="s-arrow" aria-hidden="true">→</div>
  <div class="s-col">
    <span class="s-kicker">You ship</span>
    <span class="s-main">A native theme per framework</span>
    <span class="s-sub">files a Bootstrap or shadcn practitioner would recognize as their own — plus a report grading every value</span>
  </div>
</div>
<p>That is the whole idea. Everything below is either a demonstration of it or an argument about the
parts that are hard.</p>
<h2 id="watch-it-happen-three-files-in-nine-themes-out">Watch it happen: three files in, nine themes out</h2>
<p>Enough prose. Here is the worked example that ships in the repository, start to finish. You can run
every command in this section yourself.</p>
<h3 id="what-you-write">What you write</h3>
<p>A config that names your token files, your modes, and the targets you want:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "name"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"acme-design-system"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "tokens"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tokens/option.tokens.json"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tokens/semantic.tokens.json"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">],</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "modes"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "color-scheme"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"values"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"light"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"dark"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">], </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"default"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"light"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "derivation"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"rules"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"standard@1"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "targets"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "shadcn"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"output"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"dist/shadcn"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"options"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"era"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"tailwind-v4"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "bootstrap"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"output"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"dist/bootstrap"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "echarts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"output"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"dist/echarts"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "primeng"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"output"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"dist/primeng"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>Your raw values, in plain <a href="https://design-tokens.github.io/community-group/format/">DTCG</a> — this is
a valid design-token file, readable by Figma, Tokens Studio and Style Dictionary, with no
proprietary format to migrate into:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "option"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "color"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "$type"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"color"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "white"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"$value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"oklch(1 0 0)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "blue"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "600"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"$value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"oklch(0.55 0.18 255)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "gray"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "50"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"$value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"oklch(0.985 0.003 255)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "900"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"$value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"oklch(0.22 0.012 255)"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>And a second file saying what those values <em>mean</em>, which is the only part that is Transtyle-specific
— and even that is expressed as ordinary DTCG aliases:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="json"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">  "semantic"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "color"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "$type"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"color"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "primary"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"solid"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"$value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"{option.color.blue.600}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">      "text"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">        "base"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">          "$value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"{option.color.gray.900}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">          "$extensions"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">            "transtyle.modes"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"color-scheme"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"dark"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"{option.color.gray.50}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">          }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">        }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">      }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">    },</span></span>
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">    "radius"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"md"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"$type"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"dimension"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">"$value"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF">"0.5rem"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> } }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">  }</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<!-- measured: acme.authored = 40 -->
<!-- measured: acme.slots = 271 -->
<p>That is the shape of the whole input. In full, the Acme example authors <strong>40 tokens</strong>, and the
compiler resolves them into <strong>271 slots</strong> per mode — every hover shade, every readable foreground,
every step of the dark mirror, filled in by rule.</p>
<h3 id="what-you-run">What you run</h3>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -D</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @transtyle/cli</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transtyle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">      # scaffold a config and a starter token file</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transtyle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> bootstrap</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transtyle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span></span></code></pre>
<h3 id="what-comes-out">What comes out</h3>
<p>The real output of <code>npx transtyle build</code> in <code>examples/acme</code>, unedited:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>$ npx transtyle build</span></span>
<span class="line"><span></span></span>
<span class="line"><span>ℹ TST1204 semantic.color.primary.solid has no authored value for color-scheme=dark — the light-mode</span></span>
<span class="line"><span>  value carries over unchanged, and so does its whole derived grid</span></span>
<span class="line"><span></span></span>
<span class="line"><span>shadcn  42% native · 53% derived · 3% approximated · 3% dropped</span></span>
<span class="line"><span>  ↳ dist/shadcn/globals.transtyle.css</span></span>
<span class="line"><span>  ↳ dist/shadcn/usage.md</span></span>
<span class="line"><span>  ↳ dist/shadcn/report.json</span></span>
<span class="line"><span></span></span>
<span class="line"><span>bootstrap  8% native · 69% derived · 5% approximated · 10% dropped · 8% unsupported</span></span>
<span class="line"><span>  ↳ dist/bootstrap/_variables.transtyle.scss</span></span>
<span class="line"><span>  ↳ dist/bootstrap/_maps.transtyle.scss</span></span>
<span class="line"><span>  ↳ dist/bootstrap/bootstrap-theme.css</span></span>
<span class="line"><span>  ↳ dist/bootstrap/usage.md</span></span>
<span class="line"><span>  ↳ dist/bootstrap/report.json</span></span>
<span class="line"><span></span></span>
<span class="line"><span>echarts  45% native · 18% derived · 18% approximated · 9% dropped · 9% unsupported</span></span>
<span class="line"><span>  ↳ dist/echarts/theme.acme-design-system-light.json</span></span>
<span class="line"><span>  ↳ dist/echarts/theme.acme-design-system-light.js</span></span>
<span class="line"><span>  ↳ dist/echarts/theme.acme-design-system-dark.json</span></span>
<span class="line"><span>  ↳ dist/echarts/theme.acme-design-system-dark.js</span></span>
<span class="line"><span>  ↳ dist/echarts/usage.md</span></span>
<span class="line"><span>  ↳ dist/echarts/report.json</span></span>
<span class="line"><span></span></span>
<span class="line"><span>primeng  69% native · 19% derived · 3% approximated · 0% dropped · 9% unsupported</span></span>
<span class="line"><span>  ↳ dist/primeng/preset.transtyle.ts</span></span>
<span class="line"><span>  ↳ dist/primeng/usage.md</span></span>
<span class="line"><span>  ↳ dist/primeng/report.json</span></span>
<span class="line"><span></span></span>
<span class="line"><span>✔ build complete</span></span></code></pre>
<p>Every one of those files is the kind of file a practitioner in that ecosystem already knows. The
shadcn one is a Tailwind v4 <code>globals.css</code>:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="css"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">:root</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --radius</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0.5</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583">rem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">; </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* radius.md */</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --background</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">oklch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* color.elevation.0.surface */</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --foreground</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">oklch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0.22</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.012</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 255</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* color.text.base */</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --primary</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">oklch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0.55</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.18</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 255</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* color.primary.solid */</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --primary-foreground</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">oklch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">1</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* color.primary.on-solid · derived */</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --muted-foreground</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">oklch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0.55</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.012</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 255</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* color.text.muted */</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --accent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">oklch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0.95</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.017</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 255</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* color.accent.tint · derived */</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">  --destructive</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">oklch</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">0.55</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 0.19</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> 25</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">); </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">/* color.danger.solid · derived */</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>The Bootstrap one is Sass variables, imported the way Bootstrap’s own documentation tells you to:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="scss"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// GENERATED by transtyle — do not edit; source: acme-design-system token files</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">$primary</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#026fd7</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">$secondary</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#617c9f</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">$success</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#319751</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">$info</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#0088c1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70">$warning</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF">#daa932</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8">;</span></span></code></pre>
<p>Note the trailing comment on every custom property, and the fact that four of those five Bootstrap
colours were never authored by anyone. <code>$primary</code> is the one blue in the token file. The rest were
computed — and the report will tell you exactly how.</p>
<h3 id="adding-a-target-is-one-line">Adding a target is one line</h3>
<p>The nine targets in the full example are nine entries in <code>targets</code>. Adding the tenth ecosystem you
ship in is a line of config and a rebuild; there is no second definition of your design system to
keep in step, because there was never a first one specific to a framework.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transtyle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> add</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> radix</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">        # writes the config entry</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transtyle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            # every target, from the same tokens</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transtyle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> check</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D">            # every target, without writing anything</span></span></code></pre>
<p><code>check</code> is <code>build</code> with the writing stage skipped — the same numbers, safe to run in CI on every
pull request:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>$ npx transtyle check</span></span>
<span class="line"><span></span></span>
<span class="line"><span>shadcn         42% native · 53% derived · 3% approximated · 3% dropped</span></span>
<span class="line"><span>shadcn-v3      42% native · 53% derived · 3% approximated · 3% dropped</span></span>
<span class="line"><span>echarts        45% native · 18% derived · 18% approximated · 9% dropped · 9% unsupported</span></span>
<span class="line"><span>daisyui        17% native · 65% derived · 9% approximated · 9% dropped</span></span>
<span class="line"><span>bootstrap       8% native · 69% derived · 5% approximated · 10% dropped · 8% unsupported</span></span>
<span class="line"><span>storybook      63% native · 19% derived · 13% approximated · 6% dropped</span></span>
<span class="line"><span>css-variables  100% native</span></span>
<span class="line"><span>radix          41% native · 58% approximated · 1% dropped</span></span>
<span class="line"><span>primeng        69% native · 19% derived · 3% approximated · 9% unsupported</span></span>
<span class="line"><span></span></span>
<span class="line"><span>✔ check passed</span></span></code></pre>
<p>Hold that output in mind — we will come back to why a compiler that tells you it only mapped 8% of
something <em>natively</em> is the honest one in the room.</p>
<h2 id="four-design-systems-thirty-two-running-demos">Four design systems, thirty-two running demos</h2>
<!-- measured: demos = 32 -->
<p>The claim so far is a claim about what things <em>look like</em>, and prose is a bad medium for it. So all
<strong>32</strong> demo projects in the repository are deployed and running in a browser: four design systems,
eight targets each, rebuilt from the token files on every deploy.</p>
<p>Start here: <strong><a href="https://transtyle.github.io/transtyle/demo/">the demo gallery</a></strong>.</p>
<figure>
  <a href="https://transtyle.github.io/transtyle/demo/"><img src="https://transtyle.github.io/transtyle/figures/four-systems.png" alt="The same miniature admin interface rendered four times — Acme in light blue, Cathode as green-on-black terminal, GOV.UK in flat government blue, Carbon in IBM blue — with each system's primary hex printed above it." /></a>
  <figcaption>The same miniature interface, four times. Every colour, corner radius and chart bar in this image was read out of a real compile of that system — the figure is generated by <code>npm run gen:figures</code> and regenerated whenever the tokens move, so it cannot quietly go stale. The typeface is the one exception: it is Inter throughout, because the real ones are not mine to redistribute. For the real typography, open the demos.</figcaption>
</figure>
<h3 id="what-does-not-change-is-the-interesting-part">What does not change is the interesting part</h3>
<p>Each demo is the same fake admin page — <em>Nimbus Console</em> — built with a target’s real components.
Acme’s Bootstrap demo and Carbon’s Bootstrap demo are not two pages that happen to resemble each
other. They are the same source files, byte for byte, and
<a href="https://github.com/transtyle/transtyle/blob/main/scripts/check-demo-parity.mjs">a CI check</a> fails
the build if one of them drifts. That check exists because the drift already happened once: a
tooltip was added to Acme’s demo and to nobody else’s, and three files went out of sync before
anyone noticed.</p>
<p>Which means the comparison is clean. Open <a href="https://transtyle.github.io/transtyle/demo/acme/bootstrap/">Acme on Bootstrap</a>, then use the
switcher in the corner to jump to <a href="https://transtyle.github.io/transtyle/demo/cathode/bootstrap/">Cathode</a>. Every single thing that
changed — the colours, the corner radii, the type, the hover states, the focus ring, the dark mode —
came out of the compiler, from a different set of token files. Nothing else could have changed it,
because nothing else is different.</p>
<p>Then go the other way: from Cathode’s Bootstrap page to
<a href="https://transtyle.github.io/transtyle/demo/cathode/primeng/">Cathode’s PrimeNG page</a>. Same design system, different ecosystem, different
framework, different component library, different language even — Angular rather than a Vite bundle.
It still looks like Cathode.</p>
<h3 id="the-four-systems-and-why-each-one-is-there">The four systems, and why each one is there</h3>
<p>They were chosen to disagree with each other.</p>
<h4 id="acme--invented-minimal">Acme — invented, minimal</h4>
<figure>
  <a href="https://transtyle.github.io/transtyle/demo/acme/shadcn/"><img src="https://transtyle.github.io/transtyle/figures/acme.png" alt="Acme rendered in light and dark: blue primary buttons, soft grey surfaces, 8-pixel rounded corners, a five-bar chart palette." /></a>
  <figcaption>Acme, light and dark, compiled from <code>examples/acme</code>. Its one authored radius is <code>0.5rem</code>, and it is the only one of the four with rounded corners.</figcaption>
</figure>
<p>The ordinary case, and the baseline every other column is a departure from: one brand blue, a few
neutrals, one radius. Most of what you see in its demos was derived rather than authored — the dark
mode, the hover shades, the chart palette, the readable foreground on every coloured background.
It is the system this post’s worked example uses.</p>
<p><a href="https://transtyle.github.io/transtyle/demo/acme/shadcn/">Acme demos →</a> · <a href="https://transtyle.github.io/transtyle/demo/">all eight targets</a></p>
<h4 id="cathode--invented-hostile">Cathode — invented, hostile</h4>
<figure>
  <a href="https://transtyle.github.io/transtyle/demo/cathode/bootstrap/"><img src="https://transtyle.github.io/transtyle/figures/cathode.png" alt="Cathode rendered dark and light: phosphor green on near-black in dark mode, ink on paper in light mode, hard square corners throughout." /></a>
  <figcaption>Cathode, dark (its native mode) and light. Radius zero everywhere, and a primary colour that is also the text colour.</figcaption>
</figure>
<p>A retro CRT terminal system, built specifically to break assumptions. Its vocabulary has no
“primary” in it — the tokens are called <code>crt.ink</code>, <code>crt.tube</code>, <code>crt.glass</code>. It is dark-native, so
<em>light</em> mode is the paper-printout mode, and the compiler has to carry that round the right way when
it emits into shadcn’s light-first <code>:root</code> / <code>.dark</code> structure. Its brand colour is also its text
colour. Its radius is zero, which makes shadcn’s <code>calc(var(--radius) - 4px)</code> negative, which browsers
clamp to zero — brutalism by accident of CSS.</p>
<p>If Transtyle only worked on systems shaped like Acme, Cathode is where you would see it fail.</p>
<p><a href="https://transtyle.github.io/transtyle/demo/cathode/bootstrap/">Cathode demos →</a></p>
<h4 id="govuk--real-public-sector">GOV.UK — real, public sector</h4>
<figure>
  <a href="https://transtyle.github.io/transtyle/demo/govuk/shadcn/"><img src="https://transtyle.github.io/transtyle/figures/govuk.png" alt="GOV.UK rendered in light mode beside a dashed panel reading 'This system has one mode' — the compiler emits no dark theme because the real system publishes none." /></a>
  <figcaption>GOV.UK compiles to one mode, because that is what it publishes. The second panel is what the compiler says instead of inventing a dark palette.</figcaption>
</figure>
<p>The UK government’s design system — a real, published system that nobody on this project designed.
It was adopted through the <a href="https://transtyle.github.io/transtyle/docs/adopt-existing/">binding layer</a>: its published colours and its own
functional-colour names stay exactly as they are, and a small file states what they mean. Its
<code>error</code> colour becomes the catalog’s danger role with one alias, and everything downstream follows.</p>
<p>It ships no dark theme, so its config declares one mode and every surface says so rather than
inventing one. A compiler that fabricates a mode nobody designed is a compiler you cannot trust
about the modes it did not fabricate.</p>
<p><a href="https://transtyle.github.io/transtyle/demo/govuk/shadcn/">GOV.UK demos →</a> · <a href="https://transtyle.github.io/transtyle/docs/govuk-showcase/">the full showcase</a></p>
<h4 id="carbon--real-enterprise">Carbon — real, enterprise</h4>
<figure>
  <a href="https://transtyle.github.io/transtyle/demo/carbon/primeng/"><img src="https://transtyle.github.io/transtyle/figures/carbon.png" alt="Carbon rendered in light and dark: IBM blue, square corners, and a real dark theme carrying Carbon's own G100 values." /></a>
  <figcaption>Carbon, light (White) and dark (G100) — its real per-theme values, not a derived mirror.</figcaption>
</figure>
<p>IBM’s Carbon Design System, bound the same way, and the case where the compiler has to <em>stop</em>
deriving: Carbon publishes real per-theme values, White for light and G100 for dark, so those are
what the output carries. Where its dark theme reuses a light value, the build says so in an
informational note instead of hiding it.</p>
<p><a href="https://transtyle.github.io/transtyle/demo/carbon/primeng/">Carbon demos →</a></p>
<blockquote>
<p>The GOV.UK and Carbon demos are independent demonstrations of Transtyle compiling publicly
available design tokens. They are not affiliated with, endorsed by, or produced in collaboration
with the UK Government Digital Service or IBM.</p>
</blockquote>
<h3 id="the-eight-targets">The eight targets</h3>
<p>Every one of the four systems above compiles to every one of these, and each cell of that grid is a
running page you can open:</p>
<table>
<thead>
<tr>
<th>Target</th>
<th>Stack</th>
<th>What the demo shows</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://transtyle.github.io/transtyle/docs/exporter-shadcn/">shadcn/ui</a></td>
<td>React · Tailwind v4</td>
<td>Real shadcn/ui registry components, themed by the generated <code>globals.css</code></td>
</tr>
<tr>
<td><a href="https://transtyle.github.io/transtyle/docs/exporter-daisyui/">daisyUI</a></td>
<td>Tailwind v4</td>
<td>Both modes registered natively as daisyUI themes via generated <code>@plugin</code> blocks</td>
</tr>
<tr>
<td><a href="https://transtyle.github.io/transtyle/docs/exporter-echarts/">Apache ECharts</a></td>
<td>ECharts 5</td>
<td>A chart dashboard — the data-viz palette derived from the same one brand colour</td>
</tr>
<tr>
<td><a href="https://transtyle.github.io/transtyle/docs/exporter-bootstrap/">Bootstrap</a></td>
<td>Bootstrap 5.3 · Sass</td>
<td>The Sass path: <code>.btn-primary</code> and friends compiled from the theme, not overridden</td>
</tr>
<tr>
<td><a href="https://transtyle.github.io/transtyle/docs/exporter-storybook/">Storybook</a></td>
<td>Storybook 9</td>
<td>Storybook’s own chrome — sidebar, toolbar and panels wearing the theme</td>
</tr>
<tr>
<td><a href="https://transtyle.github.io/transtyle/docs/exporter-radix/">Radix Themes</a></td>
<td>React · @radix-ui/themes</td>
<td>Compiled 12-step scales overriding a stock Radix preset in place</td>
</tr>
<tr>
<td><a href="https://transtyle.github.io/transtyle/docs/exporter-primeng/">PrimeNG</a></td>
<td>Angular 22</td>
<td>A typed PrimeNG preset, checked against PrimeNG’s own <code>DesignTokens</code> types</td>
</tr>
<tr>
<td><a href="https://transtyle.github.io/transtyle/docs/exporter-css-variables/">CSS variables</a></td>
<td>No framework</td>
<td>Every catalog slot as a plain custom property. The reference dump</td>
</tr>
</tbody>
</table>
<h3 id="the-gallery-is-compiled-too">The gallery is compiled too</h3>
<p>The swatches on the gallery page are not hand-picked hexes that resemble the output. Every colour,
corner radius and typeface on that page — and in the four figures above — is read out of a live
compile of the four examples. Change <code>option.color.blue.600</code> in <code>examples/acme</code> and the gallery card
moves along with the demos it links to, or the build fails saying which slot went missing.</p>
<p>That is the same rule the docs already follow for numbers: nothing on this site claims something
about the compiler that was not asked of the compiler while the page was being built.</p>
<h3 id="what-publishing-them-cost-and-what-it-caught">What publishing them cost, and what it caught</h3>
<p>The whole publishing step is under a minute of CI: 32 static builds, 33 MB, no build matrix and
nothing cached. Each demo is built with the same command a contributor runs, plus one argument that
makes its asset paths relative so it works from a subdirectory rather than a server root.</p>
<p>Publishing them surfaced four defects that local development had been hiding, which is the usual
reward for deploying something:</p>
<ul>
<li>The Angular demos referenced <code>/favicon.svg</code> from the server root — invisible locally, a 404 under
any subdirectory. There is now a check that fails assembly on any root-absolute reference in a
built demo.</li>
<li>The Storybook demos pointed their brand image at a <code>/logo.png</code> that has never existed anywhere.</li>
<li>GOV.UK’s and Carbon’s Storybooks were both listening on Acme’s port, so two of them could not run
at once — while their READMEs, the docs and the editor config all named three different ports.</li>
<li>Three of the four demo READMEs said “seven projects” and had no PrimeNG row, months after PrimeNG
shipped.</li>
</ul>
<p>The last two were found by a new checker rather than by reading, which is the point of writing
checkers: the demo table now has to match the directory it describes, and the ports have to match
the <code>package.json</code> that opens them.</p>
<h2 id="is-this-actually-new">Is this actually new?</h2>
<p>That question deserves a straight answer, because “new” is the cheapest word in software.</p>
<p><strong>What is not new:</strong> design tokens as an idea (a decade old). The DTCG format (a W3C community group
standard; Transtyle’s input files are valid DTCG plus namespaced extensions, so Figma, Tokens Studio
and Style Dictionary can read them unchanged — there is no proprietary format to migrate into).
Token pipelines — <a href="https://styledictionary.com/">Style Dictionary</a> has been transforming token files
into platform outputs for years, and <a href="https://terrazzo.app/">Terrazzo</a> does it DTCG-natively with a
clean plugin model. Per-framework theme generators exist for nearly every target listed above. And
the compiler metaphor itself is borrowed shamelessly from Babel and LLVM.</p>
<p>The existing pipelines solve the <strong>bottom half</strong> of the problem beautifully: given token files,
produce variable files. What none of them do is the <strong>top half</strong> — understand what a design system
<em>means</em>, and produce configuration that a specific framework actually consumes.</p>
<p>That gap has four parts, and each one is a falsifiable claim rather than a slogan.</p>
<h3 id="1-a-pivot-vocabulary-of-meanings-not-names">1. A pivot vocabulary of meanings, not names</h3>
<p>Style Dictionary will happily emit <code>--color-brand-blue-600</code> for every platform you ask. It has no
opinion about which of your tokens is “the danger color”, because it has no vocabulary for danger.</p>
<p>Transtyle’s <a href="https://transtyle.github.io/transtyle/docs/language/">catalog</a> is that vocabulary: a fixed set of semantic slots —
<code>primary.solid</code>, <code>danger.on-tint</code>, <code>elevation.3.surface</code>, <code>text.muted</code> — that exporters bind to.
Your names map <em>into</em> it once; each framework’s names map <em>out of</em> it, maintained by people who know
that framework.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>your semantics            the catalog (pivot)          each library's semantics</span></span>
<span class="line"><span>──────────────            ───────────────────          ────────────────────────</span></span>
<span class="line"><span>"brand-action"   ─alias→  primary.solid        ─table→  --primary        (shadcn)</span></span>
<span class="line"><span>"flame-soft"     ─alias→  primary.tint         ─table→  $primary-bg-subtle (Bootstrap)</span></span>
<span class="line"><span>(nothing)        ─rule──→ primary.on-solid     ─table→  --primary-foreground</span></span></code></pre>
<p>The reason this has to exist — and the reason a simple rename table cannot replace it — is <strong>false
friends</strong>. The same word means different things in different ecosystems:</p>
<table>
<thead>
<tr>
<th>Word</th>
<th>In the catalog</th>
<th>In shadcn</th>
<th>In Bootstrap</th>
<th>In Radix</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>secondary</strong></td>
<td>your second <em>brand</em> color</td>
<td>a subtle gray button surface</td>
<td>a theme color (close to ours)</td>
<td>—</td>
</tr>
<tr>
<td><strong>accent</strong></td>
<td>brand emphasis color</td>
<td>a hover-highlight tint</td>
<td>not a concept</td>
<td>the <em>only</em> brand color (≈ primary)</td>
</tr>
<tr>
<td><strong>subtle</strong></td>
<td><code>&lt;role&gt;.tint</code> — one wash, all states</td>
<td><code>muted</code>, a surface + foreground pair</td>
<td><code>-bg-subtle</code> / <code>-border-subtle</code></td>
<td>steps 3–5, a <em>range</em> of values</td>
</tr>
</tbody>
</table>
<p>Bind Bootstrap’s <code>secondary</code> to shadcn’s <code>secondary</code> because they are spelled the same and you have
shipped a bug. Bind both by meaning through a pivot, and the collision becomes harmless. This is why
the catalog was designed from a comparative study of <strong>14 ecosystems</strong> rather than reverse-engineered
from whichever framework I happened to like — a vocabulary shaped like one library’s internals is
that library’s config file wearing a neutral name.</p>
<p>The shape that study produced is the part experts should poke at hardest. Colors are not a flat list
of roles; each role is a <strong>grid</strong> — prominence crossed with interaction state, plus paired
foregrounds:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>prominence →   solid            tint            outline          text</span></span>
<span class="line"><span>rest           solid            tint            outline          text</span></span>
<span class="line"><span>hover          solid-hover      tint-hover      outline-hover    text-hover</span></span>
<span class="line"><span>active         solid-active     tint-active     —                text-active</span></span>
<span class="line"><span>selected       solid-selected   tint-selected   —                —</span></span>
<span class="line"><span>on-colors      on-solid         on-tint         —                —</span></span></code></pre>
<p>Radix’s 12 steps, Ant Design’s map tokens, Bootstrap’s subtle triad, Chakra’s <code>colorPalette</code> and
Material 3’s container/<code>on-*</code> pairs are all differently-named samples of that same grid. Once you
see it, you cannot unsee it — and once you encode it, an exporter for a system you have never heard
of is a mapping table, not a research project.</p>
<h3 id="2-derivation-as-a-compiler-stage">2. Derivation as a compiler stage</h3>
<p>A real framework needs far more values than any human wants to author: hover shades, pressed shades,
a readable foreground for every colored background, tinted washes, focus rings, the whole dark-mode
mirror, an eight-color chart palette.</p>
<p>Transtyle computes them, from the decisions you did make, with <strong>fixed, versioned, published rules</strong>.
Not AI, not “smart defaults” — arithmetic in a perceptual color space, the same input producing the
same output byte for byte, forever. (The compiler being vibe coded and the compiler being
deterministic are unrelated facts: an AI wrote the rules, and then the rules run without one.)</p>
<p>Two properties keep that from being a black box. First, <strong>authored always wins</strong>: every derived value
is a proposal, and authoring the token — down to one dark-mode hover shade — makes the rule step
aside. Second, <strong>every value can explain itself</strong>:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>$ npx transtyle explain semantic.color.primary.on-solid</span></span>
<span class="line"><span></span></span>
<span class="line"><span>semantic.color.primary.on-solid = oklch(1 0 0)  [#ffffff]</span></span>
<span class="line"><span> └─ derived by rule contrast-pick@standard@1</span></span>
<span class="line"><span>    inputs: semantic.color.primary.solid = oklch(0.55 0.18 255)  [#026fd7]</span></span>
<span class="line"><span>     └─ aliased → option.color.blue.600</span></span></code></pre>
<p>That is the whole audit trail for one value: which rule, from which of your tokens, in what order. A
designer can read it without reading any code. An accessibility reviewer can check that the
white-on-blue pairing was <em>chosen for contrast</em>, not picked by eye. Nobody has to trust me — the
trail is in the build output.</p>
<h3 id="3-lossiness-measured-instead-of-hidden">3. Lossiness measured instead of hidden</h3>
<p>Translation between real ecosystems is lossy, in ways that have nothing to do with effort. A <code>rem</code>
radius has to become a pixel number in an ECharts theme JSON. Radix models each color as twelve steps
plus a parallel alpha ramp — a shape no other system carries the information to reconstruct exactly.
Some values simply have no destination.</p>
<p>Every tool in this space faces that. Most respond by quietly emitting something plausible. Transtyle
grades itself instead — per variable, every build:</p>
<ul>
<li><span class="prov native">native</span> — the target has a first-class slot; the mapping is lossless</li>
<li><span class="prov derived">derived</span> — synthesized by a named rule, then mapped natively</li>
<li><span class="prov approx">approx</span> — mapped, but the meaning bent to fit; the reason is recorded</li>
<li><strong>dropped</strong> — this target simply cannot say it; omitted with a note, never faked</li>
<li><strong>unsupported</strong> — the target has a themable slot we do not cover yet; an admission on the record</li>
</ul>
<p>Here is the real GOV.UK Design System — a public system nobody on this project designed — compiled to
all eight targets:</p>
<div class="covmatrix" data-example="govuk" role="img" aria-label="Coverage bars per target for the GOV.UK example: css-variables 100% native; primeng 69% native; storybook 61% native; shadcn 50% native, 47% derived; echarts 50% native; radix 42% native, 58% approximated; daisyui 32% native, 55% derived; bootstrap 2% native, 76% derived">
  <div class="cm-row"><span class="cm-name">css-variables</span><span class="cm-bar"><span class="cm-seg native" style="width:100%"></span></span></div>
  <div class="cm-row"><span class="cm-name">primeng</span><span class="cm-bar"><span class="cm-seg native" style="width:69%"></span><span class="cm-seg derived" style="width:19%"></span><span class="cm-seg approx" style="width:3%"></span><span class="cm-seg other" style="width:9%"></span></span></div>
  <div class="cm-row"><span class="cm-name">storybook</span><span class="cm-bar"><span class="cm-seg native" style="width:61%"></span><span class="cm-seg derived" style="width:16%"></span><span class="cm-seg approx" style="width:13%"></span><span class="cm-seg other" style="width:10%"></span></span></div>
  <div class="cm-row"><span class="cm-name">shadcn</span><span class="cm-bar"><span class="cm-seg native" style="width:50%"></span><span class="cm-seg derived" style="width:47%"></span><span class="cm-seg approx" style="width:3%"></span></span></div>
  <div class="cm-row"><span class="cm-name">echarts</span><span class="cm-bar"><span class="cm-seg native" style="width:50%"></span><span class="cm-seg derived" style="width:20%"></span><span class="cm-seg approx" style="width:20%"></span><span class="cm-seg other" style="width:10%"></span></span></div>
  <div class="cm-row"><span class="cm-name">radix</span><span class="cm-bar"><span class="cm-seg native" style="width:42%"></span><span class="cm-seg approx" style="width:58%"></span></span></div>
  <div class="cm-row"><span class="cm-name">daisyui</span><span class="cm-bar"><span class="cm-seg native" style="width:32%"></span><span class="cm-seg derived" style="width:55%"></span><span class="cm-seg approx" style="width:9%"></span><span class="cm-seg other" style="width:5%"></span></span></div>
  <div class="cm-row"><span class="cm-name">bootstrap</span><span class="cm-bar"><span class="cm-seg native" style="width:2%"></span><span class="cm-seg derived" style="width:76%"></span><span class="cm-seg approx" style="width:5%"></span><span class="cm-seg other" style="width:18%"></span></span></div>
</div>
<p class="covmatrix-legend"><span><i class="native"></i>native — lossless</span><span><i class="derived"></i>derived — computed by a rule</span><span><i class="approx"></i>approximated — meaning bent, reason recorded</span><span><i class="other"></i>dropped / unsupported — this target can't say it</span></p>
<!-- measured: govuk.bootstrap.rows = 712 -->
<p>Read the shape, not a single number — and never compare one target’s bar to another’s. Each measures
a different surface with a different ceiling. <code>css-variables</code> is 100% native because it is the
conformance dump: it has a slot for everything by construction. <code>radix</code> is 58% approximated because
its 12-step alpha ramps are a fixed projection rather than a colorimetric reconstruction — a
compromise stated out loud. And <code>bootstrap</code> is 2% native / 76% derived across <strong>712 classified rows</strong>
because GOV.UK authored a handful of colors and the standard rules coherently filled a very large
variable surface from them. A high derived share is not a weakness; it is the compiler doing the work
you did not want to do by hand, and telling you it did.</p>
<p>And the compromises are not summarized away into a percentage. Each one carries its reason, verbatim,
in <code>report.json</code>:</p>
<table>
<thead>
<tr>
<th>Target</th>
<th>Item</th>
<th>Grade</th>
<th>The note, as written by the exporter</th>
</tr>
</thead>
<tbody>
<tr>
<td>echarts</td>
<td><code>tooltip.borderRadius</code></td>
<td><code>approximated</code></td>
<td>rem → px (base 16)</td>
</tr>
<tr>
<td>radix</td>
<td><code>--primary-a4</code></td>
<td><code>approximated</code></td>
<td>fixed alpha ramp, not a colorimetric derivation of Radix’s real per-color alpha</td>
</tr>
<tr>
<td>echarts</td>
<td>series-specific styles (candlestick, …)</td>
<td><code>unsupported</code></td>
<td>beyond catalog semantics; extend the emitted theme manually</td>
</tr>
</tbody>
</table>
<p>That last row is the interesting one. It is a tool writing down, in its own output, something it
cannot do — and shipping it to you anyway.</p>
<p>A build is not “done” at 100% native — that is impossible across real ecosystems. It is done when the
report matches your intent: your decisions authored, coherent derivation for the rest, every
compromise known and accepted. <strong>The report is the product.</strong> Everything else is plumbing.</p>
<h3 id="4-native-artifacts-all-the-way-down-to-components">4. Native artifacts, all the way down to components</h3>
<p>The last difference is the least glamorous and the most load-bearing: what lands on disk.</p>
<p>A generic pipeline emits variables and leaves you to wire them up. Transtyle emits
<code>_variables.transtyle.scss</code> and <code>_maps.transtyle.scss</code> that import around Bootstrap’s own Sass build
the way Bootstrap’s docs tell you to; a <code>globals.transtyle.css</code> with <code>@theme inline</code> that drops into
a Tailwind v4 shadcn project; an ECharts theme JSON that registers directly. Delete Transtyle
afterwards and the files still work — there is <strong>no runtime</strong>, nothing shipped into your application,
no dependency to audit.</p>
<!-- measured: bootstrap.surface.total = 952 -->
<!-- measured: bootstrap.surface.component = 657 -->
<!-- measured: primeng.surface.total = 2759 -->
<p>That extends past the color-role layer into component theming, which is where “themed” usually stops
being true. Bootstrap exposes 952 themable Sass variables, 657 of them component-scoped; PrimeNG’s
Aura preset exposes 2,759 design-token slots across 98 families. Both inventories are checked into
the repository and drift-guarded in CI, and every single slot is accounted for in the coverage report
— driven by a token, inherited through the target’s own chaining, left on its default, or honestly
reported as a gap with a note.</p>
<p>The component tier is also where a distinction most token formats cannot express becomes one authored
line:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>author component.control.radius  →  buttons AND inputs move    ("controls are rounder")</span></span>
<span class="line"><span>author component.button.radius   →  only buttons move          ("buttons are pills")</span></span></code></pre>
<p>Two targets that model this in incompatible ways still reproduce the distinction: Bootstrap chains
buttons and inputs through a shared <code>$input-btn-*</code> root, PrimeNG keeps <code>button.*</code> and <code>formField.*</code>
entirely separate. One line, a pill button in both, and form fields untouched in either.</p>
<p>The catalog does not grow to swallow all of that, and the rule for when it may is deliberately
severe: <strong>two independent exporters must need the identical thing for architectural, not nominal,
reasons.</strong> Control padding and radius passed — Bootstrap and PrimeNG both couple a button’s box to a
form field’s box, arrived at separately. The small/large size ladder failed: both have one, and they
disagree about which rungs it has. The disagreement <em>is</em> the finding. Everything a single target
needs stays inside that target’s exporter, where it belongs.</p>
<h3 id="where-this-could-be-wrong">Where this could be wrong</h3>
<p>An honest post includes the parts that could fail.</p>
<p>The catalog is a <strong>bet</strong> that these meanings are genuinely universal. It survived two real,
independently-designed systems (<a href="https://transtyle.github.io/transtyle/docs/govuk-showcase/">GOV.UK</a> and IBM Carbon) compiling to eight
targets with zero catalog amendments and no warnings or errors — Carbon draws a few informational
notes where its dark theme reuses a light value, which is the compiler saying so rather than hiding
it. But two systems is not many, and the ones that will break this are the ones I have not seen.
Translation stays lossy no matter how good the reports get; if you need pixel-identical rendering
across frameworks, no tool can give you that, and I say so rather than implying otherwise. The DTCG
spec has not settled modes and theming upstream, so these extensions have to be deletable when it
does. And the risk is not primarily engineering: it is whether an ecosystem that has been
re-implementing themes by hand for a decade wants to stop.</p>
<h2 id="how-it-works-in-five-schemas">How it works, in five schemas</h2>
<p>Everything above rests on five ideas. If you read only this section, you will still be able to argue
with me competently.</p>
<h3 id="the-pipeline">The pipeline</h3>
<p>Every build runs the same six stages. Only one of them touches your disk.</p>
<div class="flow" role="img" aria-label="The six pipeline stages in order: load, normalize, derive, resolve, emit, report">
  <span class="fnode">LOAD</span><span class="farr">→</span>
  <span class="fnode">NORMALIZE</span><span class="farr">→</span>
  <span class="fnode hi">DERIVE</span><span class="farr">→</span>
  <span class="fnode">RESOLVE</span><span class="farr">→</span>
  <span class="fnode">EMIT</span><span class="farr">→</span>
  <span class="fnode">REPORT</span>
</div>
<table>
<thead>
<tr>
<th>Stage</th>
<th>What happens</th>
</tr>
</thead>
<tbody>
<tr>
<td>LOAD</td>
<td>Read the config and the token files it lists</td>
</tr>
<tr>
<td>NORMALIZE</td>
<td>Merge layers, resolve aliases (cycles detected), expand modes, parse colors to OKLCH</td>
</tr>
<tr>
<td>DERIVE</td>
<td>Fill every unauthored catalog slot with deterministic rules</td>
</tr>
<tr>
<td>RESOLVE</td>
<td>Map the completed token graph onto each target’s native theming surface</td>
</tr>
<tr>
<td>EMIT</td>
<td>Write native artifacts — the only stage that writes files; <code>check</code> skips it</td>
</tr>
<tr>
<td>REPORT</td>
<td>Classify coverage, raise diagnostics, emit <code>report.json</code></td>
</tr>
</tbody>
</table>
<p>No network, no timestamps, no randomness: identical inputs produce byte-identical outputs, and CI
proves it on every commit.</p>
<h3 id="the-three-tiers">The three tiers</h3>
<div class="tiers">
  <div class="tier">
    <span class="tier-name">option</span>
    <span class="tier-ex">color.blue.600 · font.mono</span>
    <span class="tier-desc">raw values — <strong>your</strong> private vocabulary, any names you like</span>
  </div>
  <div class="tier-link" aria-hidden="true">↓ alias</div>
  <div class="tier public">
    <span class="tier-name">semantic</span>
    <span class="tier-ex">primary.solid · text.base · elevation.0.surface</span>
    <span class="tier-desc">meaning — the stable public surface exporters bind to</span>
  </div>
  <div class="tier-link" aria-hidden="true">↓ alias</div>
  <div class="tier">
    <span class="tier-name">component</span>
    <span class="tier-ex">component.control.* · component.button.*</span>
    <span class="tier-desc">optional per-component refinement, defaulting from semantic</span>
  </div>
</div>
<p>Exporters bind <strong>only</strong> to the semantic tier. Rename your entire raw palette tomorrow and no target
output changes, as long as the semantic aliases still point somewhere sensible. If you run a design
system, you already have these tiers; you probably call them primitives and aliases.</p>
<h3 id="modes">Modes</h3>
<p>A mode is a declared axis of variation — <code>color-scheme: light | dark</code> is the familiar one, and a
second axis such as density composes with it. Every token resolves per mode, unspecified values fall
back to the default, and one rule matters more than it sounds: <strong><code>default</code> declares your system’s
native mode.</strong> A dark-native design system still compiles correctly into shadcn’s light-first
<code>:root</code> / <code>.dark</code> structure, because the exporter binds mode <em>names</em>, not the assumption that light
comes first. That is Cathode’s entire reason for existing.</p>
<h3 id="provenance-and-coverage">Provenance and coverage</h3>
<p>Two orthogonal labels, which people conflate constantly. <strong>Provenance</strong> answers <em>where did this value
come from</em> — <span class="prov authored">authored</span>, aliased,
<span class="prov derived">derived</span>, or defaulted. <strong>Coverage</strong> answers <em>how well did it survive
the trip into this target</em> — the native/derived/approximated/dropped/unsupported classes above. A
value can be authored by you and still approximated on arrival, because the target cannot express
what you meant. Both labels sit on every row of every <code>report.json</code>.</p>
<h3 id="the-binding-layer">The binding layer</h3>
<p>This is the part that decides whether adoption takes an afternoon or a quarter. You do <strong>not</strong> rename
anything. You keep your token files, your names, your Figma sync, your governance — and add a thin
layer of one-line aliases stating what your names mean.</p>
<p>GOV.UK was adopted exactly that way. Their <code>error</code> colour becomes the catalog’s danger role with one
alias, and everything downstream follows:</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="plaintext"><code><span class="line"><span>$ npx transtyle explain semantic.color.danger.text</span></span>
<span class="line"><span></span></span>
<span class="line"><span>semantic.color.danger.text = oklch(0.488 0.167 25.6)  [#aa2729]</span></span>
<span class="line"><span> └─ derived by rule contrast-pick(text)@standard@1</span></span>
<span class="line"><span>    inputs: semantic.color.danger.solid = oklch(0.558 0.186 25.6)  [#ca3535]</span></span>
<span class="line"><span>     └─ aliased → semantic.color.govuk.error</span></span>
<span class="line"><span>    inputs: semantic.color.elevation.0.surface = oklch(1 0 0)  [#ffffff]</span></span>
<span class="line"><span>     └─ aliased → semantic.color.govuk.body-background</span></span></code></pre>
<p>Note what that trace shows: an accessible red for <em>text</em> was computed against the <em>actual page
background</em> of that system, not against an assumed white. That is the difference between a
translation layer and a search-and-replace.</p>
<h2 id="who-this-is-actually-for">Who this is actually for</h2>
<h3 id="if-you-are-one-person-with-a-side-project">If you are one person with a side project</h3>
<p>Three tokens is a working design system here — a brand color, a page background, a text color. (Not a
figure of speech: a CI check compiles exactly that three-token system against all eight exporters on
every commit, because a tool that only works on lavishly-authored systems is a tool for people who
already have a design system.) You get a complete, dark-mode-ready, contrast-checked theme for
whichever framework you picked — plus the other seven, free, on the day you change your mind about
the framework.</p>
<p>The real win is subtler: you get a <em>coherent</em> system without knowing color theory. Hover states that
move the right amount. Foregrounds that pass WCAG AA because a rule checked, not because they looked
fine on your monitor. A chart palette that is actually distinguishable. Things a design systems team
would have given you, if you had one.</p>
<p>And the exit is free. The output is plain theme files; there is no runtime, no lock-in, and no import
from us anywhere in your app. If you abandon Transtyle next year, you keep everything it wrote.</p>
<h3 id="if-you-are-a-product-team-with-more-than-one-framework">If you are a product team with more than one framework</h3>
<p>The moment you have a Storybook, an app, and a dashboard with charts, you have the N×M problem in
miniature — three theming surfaces, three dialects, one brand. This is where “regenerate everything
from one source” stops being an abstraction and starts being the Tuesday afternoon you get back.</p>
<p>The generated files are disposable by design: check them in, never edit them, regenerate on change.
Because rebuilds are byte-identical, a token change shows up as exactly the lines it moved — and
<code>transtyle diff</code> reports it semantically, per target, against any git ref. That turns “we changed the
brand blue” into a reviewable pull request instead of an act of faith.</p>
<h3 id="if-you-run-a-design-system-at-a-large-organization">If you run a design system at a large organization</h3>
<p>This is the case the project was built for, and it is less about convenience than about <strong>evidence</strong>.</p>
<ul>
<li><strong>One source of truth that can prove it is the source of truth.</strong> Provenance turns “trust us” into a
queryable chain, per value, per mode, per target. That is an audit artifact, and regulated
organizations spend real money faking it today.</li>
<li><strong>Accessibility becomes a build step.</strong> Contrast is checked during compilation against the real
surface a value lands on, and failures are diagnostics with stable codes — <code>check</code> in CI, not a
manual sweep before a release.</li>
<li><strong>Framework migration stops being a project.</strong> Moving a product from one library to another begins
with a recompile and a coverage report telling you precisely what will not survive, <em>before</em> anyone
estimates the work.</li>
<li><strong>Federated teams stay federated.</strong> The catalog is a published interface. Your platform team owns
the decision layer; product teams consume native artifacts in their own stack; nobody has to learn
anybody else’s dialect. Exporters are plugins on a public API, so a team with an internal component
library can write and own one without touching the compiler.</li>
<li><strong>Procurement-friendly by construction.</strong> MIT-licensed, local-first, no hosted service, no
telemetry, no account, no runtime dependency entering your application’s supply chain.</li>
<li><strong>Agent-operable.</strong> JSON in, JSON out, stable diagnostic codes, deterministic builds. AI agents can
drive the config; the compiler itself stays rule-based and auditable, which is exactly the boundary
a serious organization wants between generated and guaranteed.</li>
</ul>
<p>GOV.UK and IBM Carbon are in the repository as worked proof — not because anyone partnered with
anyone, but because both publish their tokens, and a system you cannot compile without editing is a
system you have not really adopted.</p>
<h2 id="what-it-deliberately-is-not">What it deliberately is not</h2>
<p>Scope discipline is the survival strategy of a project like this, so the boundaries are permanent,
not “not yet”:</p>
<ol>
<li><strong>Not a component library.</strong> It generates configuration for other people’s components and ships no
UI code.</li>
<li><strong>Not a design tool.</strong> It does not edit tokens visually; it reads the files your design tool
already writes.</li>
<li><strong>Not a promise of pixel-perfect equivalence.</strong> The contract is measured fidelity, not false
equivalence.</li>
<li><strong>Not a fork of your framework.</strong> If Bootstrap cannot express something, it is reported; nobody
ships a patched Bootstrap.</li>
<li><strong>Not an AI theming assistant.</strong> Derivation is deterministic and inspectable. An agent can write
the config; it will never be inside the compiler.</li>
</ol>
<h2 id="where-it-stands-honestly">Where it stands, honestly</h2>
<p>Transtyle is on npm: twelve packages under <a href="https://www.npmjs.com/org/transtyle"><code>@transtyle</code></a>, all
at <code>0.1.0-alpha</code>, and the documentation you are reading is deployed rather than previewed on my
laptop. That is not a launch. It is the point at which the project becomes possible to <em>try</em>, which
is a different and much smaller claim.</p>
<!-- measured: exporters = 8 -->
<!-- measured: examples = 4 -->
<p><strong>Real today:</strong> the full pipeline; the derivation engine (role grids, elevation ladder, scales,
modes); <code>build</code>, <code>check</code>, <code>explain</code>, <code>init</code>, <code>add</code>, and <code>diff</code> — the last one comparing the compiled
themes against a git ref, so a token change arrives in review as “here is what moved, in every
target”; 8 exporters — shadcn/ui, daisyUI, Apache ECharts, Bootstrap, Storybook, Radix Themes,
PrimeNG, and plain CSS variables — all on the same public plugin API, with a conformance kit gating
them in CI; 4 example systems, two of them real and independently designed, with 32 runnable demo
projects rendering the compiled themes on each target’s actual components; component-tier theming on
Bootstrap and PrimeNG; JSON schemas for config and reports; zero runtime dependencies; byte-identical
rebuilds verified on every commit.</p>
<p><strong>Specced but not implemented:</strong> <code>preview</code>, and <code>import</code> with the importers behind it — the reverse
direction that turns an existing Tailwind config, Figma variable set, or CSS custom-property sheet
back into tokens. The first release deliberately targets design systems that already speak DTCG. If
your tokens are not DTCG, the front door is shut for now.</p>
<p><strong>What will break:</strong> everything, potentially, and without a deprecation cycle. That is what the alpha
label is for, and it is
<a href="https://github.com/transtyle/transtyle/blob/main/docs/adr/0010-pre-release-breaking-changes.md">written down</a>
rather than implied: the token vocabulary, the generated output, the config format and the CLI surface
can each change between alpha releases. Two consequences worth acting on — <strong>pin an exact version</strong>
(<code>@transtyle/cli@0.1.0-alpha.1</code>, not a range), and <strong>treat generated files as output</strong> (regenerate
them; never hand-edit and keep them, because the compiler will happily overwrite your careful manual
fix, and that is the correct behaviour). The freeze arrives at the first release whose version carries
no prerelease identifier.</p>
<p><strong>Not proven by anyone but me.</strong> Four examples and eight exporters is evidence, not adoption.</p>
<h2 id="what-happens-next">What happens next</h2>
<p>I am going to keep working on this. The next stretch is not new features — it is more evidence: more
design systems, of shapes I have not tried, compiled against more tools, to find out where the catalog
bends and where it snaps. Every system that compiles cleanly is a small confirmation; the first one
that does not is worth more than all of them, because it tells me something I could not have reasoned
my way to.</p>
<p>Which is where you come in. The most useful thing anyone can do with this alpha is point it at a
design system I have never seen and tell me what happened.</p>
<pre class="astro-code astro-code-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> i</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF"> -D</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> @transtyle/cli</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transtyle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> init</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> transtyle</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF"> build</span></span></code></pre>
<p>If you already have a design system, start at <a href="https://transtyle.github.io/transtyle/docs/adopt-existing/">I already have a design system</a>
instead — the whole point of the binding layer is that you do not rename anything you already have.
Or just look at the output first: <strong><a href="https://transtyle.github.io/transtyle/demo/">the demo gallery</a></strong> has all 32 running.</p>
<p>Then tell me how it went in
<strong><a href="https://github.com/transtyle/transtyle/discussions">GitHub Discussions</a></strong> — what compiled, what did
not, what the coverage report said, what the catalog could not express, what was obviously wrong. Bug
reports are welcome as <a href="https://github.com/transtyle/transtyle/issues">issues</a>, but Discussions is
where the useful conversation is: this is an experiment, and the feedback is how it stops being only
mine.</p>
<p>The compiler’s central bet is that a small, frozen pivot vocabulary can carry an arbitrary design
system into an arbitrary ecosystem. A bet like that is only ever settled by someone else’s tokens.</p>
<p>Bring me the design system that breaks it.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
