{"id":"l-ds-2-5","courseId":"design-systems","lang":"en","kind":"lesson","summary":"Set up a token build pipeline that turns DTCG JSON into CSS custom properties per theme and native formats per platform, keeping references intact so runtime theming still works.","html":"<p>With tokens stored as DTCG JSON, something has to turn them into what each platform consumes: CSS custom properties for the web app and marketing site, Swift for the iOS driver app, Kotlin or XML for Android. That something is a token pipeline. It’s a small piece of code that every design system team ends up owning, and getting it right early saves months of drift later.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">What a pipeline does</h2>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-ds-2-5-t1\"><defs><marker id=\"arrow-l-ds-2-5-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-ds-2-5-t1\">Token JSON files flow through parse, resolve, transform and format steps, producing CSS for web, Swift for iOS and Kotlin for Android.</title>\n  <rect class=\"d-box-primary\" x=\"10\" y=\"85\" width=\"110\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"65\" y=\"110\" text-anchor=\"middle\">*.tokens</text>\n  <text class=\"d-label\" x=\"65\" y=\"128\" text-anchor=\"middle\">.json</text>\n  <rect class=\"d-box\" x=\"150\" y=\"85\" width=\"80\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"190\" y=\"118\" text-anchor=\"middle\">Parse</text>\n  <rect class=\"d-box\" x=\"250\" y=\"85\" width=\"90\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"295\" y=\"118\" text-anchor=\"middle\">Resolve</text>\n  <rect class=\"d-box\" x=\"360\" y=\"85\" width=\"100\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"410\" y=\"118\" text-anchor=\"middle\">Transform</text>\n  <rect class=\"d-box\" x=\"480\" y=\"85\" width=\"80\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"520\" y=\"118\" text-anchor=\"middle\">Format</text>\n  <rect class=\"d-box-accent\" x=\"590\" y=\"20\" width=\"80\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"630\" y=\"47\" text-anchor=\"middle\">CSS</text>\n  <rect class=\"d-box-accent\" x=\"590\" y=\"91\" width=\"80\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"630\" y=\"118\" text-anchor=\"middle\">Swift</text>\n  <rect class=\"d-box-accent\" x=\"590\" y=\"162\" width=\"80\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"630\" y=\"189\" text-anchor=\"middle\">Kotlin</text>\n  <path class=\"d-arrow\" d=\"M120 113 L146 113\" marker-end=\"url(#arrow-l-ds-2-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M230 113 L246 113\" marker-end=\"url(#arrow-l-ds-2-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M340 113 L356 113\" marker-end=\"url(#arrow-l-ds-2-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M460 113 L476 113\" marker-end=\"url(#arrow-l-ds-2-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M560 105 L586 45\" marker-end=\"url(#arrow-l-ds-2-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M560 113 L586 113\" marker-end=\"url(#arrow-l-ds-2-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M560 121 L586 183\" marker-end=\"url(#arrow-l-ds-2-5-1)\"/>\n</svg><figcaption>A token pipeline: one source, one output per platform</figcaption></figure>\n<ol>\n<li><strong>Parse</strong> every source file and deep-merge them into one tree.</li>\n<li><strong>Resolve</strong> aliases like <code dir=\"ltr\">{color.blue.600}</code>, rejecting broken or circular references.</li>\n<li><strong>Transform</strong> names and values for each platform: <code dir=\"ltr\">color.action.bg</code> becomes <code dir=\"ltr\">--nw-color-action-bg</code> in CSS and <code dir=\"ltr\">colorActionBg</code> in Swift; <code dir=\"ltr\">1rem</code> becomes <code dir=\"ltr\">16</code> points on iOS.</li>\n<li><strong>Format</strong> the result as a file: a block of custom properties, a Swift enum, an Android resource file.</li>\n</ol>\n<p>Here’s the heart of the CSS side, small enough to read in one go:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"0\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> tokens</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  color: {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    $type: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'color'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    blue: { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { $value: { colorSpace: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'srgb'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, components: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.122</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.353</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.878</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], hex: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#1f5ae0'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } } },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    white: { $value: { colorSpace: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'srgb'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, components: [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], hex: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#ffffff'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    action: {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      bg: { $value: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'{color.blue.600}'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      text: { $value: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'{color.white}'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  space: { $type: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'dimension'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { $value: { value: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, unit: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'rem'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } } },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Walk the tree and collect every token with its path.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> flatten</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">node</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">path</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> t</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> node.$type </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> type;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'$value'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> node) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [{ path, type: t, value: node.$value }];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Object.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">entries</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(node)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">filter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(([</span><span style=\"color:#953800;--shiki-dark:#FFA657\">key</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> !</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">key.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">startsWith</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'$'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">flatMap</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(([</span><span style=\"color:#953800;--shiki-dark:#FFA657\">key</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">child</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> flatten</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(child, [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">path, key], t));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> cssName</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `--nw-${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">path</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">join</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'-'</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> cssValue</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">typeof</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> value </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'string'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> value.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">startsWith</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'{'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `var(${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">cssName</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">slice</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">split</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'.'</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">))</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">})`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// keep the reference</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (type </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'color'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> value.hex;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (type </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'dimension'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">unit</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(value);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> lines</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> flatten</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(tokens).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">t</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `  ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">cssName</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">path</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}: ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">cssValue</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">};`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`/* Generated from tokens. Do not edit. */</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">:root {</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">lines</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">join</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\n</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>Look at the output. The path becomes the name, so <code dir=\"ltr\">color.blue.600</code> comes out as <code dir=\"ltr\">--nw-color-blue-600</code>; earlier lessons shortened primitives to <code dir=\"ltr\">--nw-blue-600</code> for readability, but in a generated system the name is whatever the group structure says, which is one more reason to design groups and names together. More importantly, semantic tokens come out as <code dir=\"ltr\">var(--nw-color-blue-600)</code>, not as a hex value. That choice is important enough to deserve its own section.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Keep the references</h2>\n<p>A pipeline can either resolve aliases to final values or keep them as references in the output. For CSS, keep them. Resolved output flattens your tiers: the browser sees <code dir=\"ltr\">--nw-color-action-bg: #1f5ae0</code>, so overriding a primitive at runtime does nothing, and a developer inspecting a button in DevTools can’t see which primitive it comes from. Referenced output preserves the exact structure you designed, including the subtree theming from the themes lesson.</p>\n<p>Native platforms are different. Swift and Kotlin constants are usually resolved to final values, because those platforms handle themes through their own mechanisms (asset catalogs, Compose color schemes).</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">A real pipeline with Style Dictionary</h2>\n<p>You could grow the script above into a full tool, but most teams use Style Dictionary, the most widely used open-source token build system. Recent versions read DTCG files natively and auto-detect the format. A Northwind-style config builds the light theme into <code dir=\"ltr\">:root</code> and the dark theme under its attribute selector:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">build-tokens.mjs</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StyleDictionary </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'style-dictionary'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> themes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  light: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">':root'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  dark: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'[data-theme=\"dark\"]'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Object.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">entries</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(themes)) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sd</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> StyleDictionary</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    include: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'tokens/primitives.tokens.json'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    source: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`tokens/semantic-${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.tokens.json`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    platforms: {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      css: {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        transformGroup: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'css'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        prefix: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'nw'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        buildPath: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'dist/css/'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        files: [{</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">          destination: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`theme-${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.css`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">          format: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'css/variables'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">          filter: name </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'light'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">token</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> token.isSource,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">          options: { selector, outputReferences: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }],</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  });</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sd.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">buildAllPlatforms</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The key ideas: primitives come in through <code dir=\"ltr\">include</code>, so references can resolve, while the theme’s semantic file comes in through <code dir=\"ltr\">source</code>. For dark, the <code dir=\"ltr\">filter</code> keeps only source tokens, so the dark file contains the remapped semantic tokens and not a second copy of every primitive. <code dir=\"ltr\">outputReferences: true</code> keeps <code dir=\"ltr\">var()</code> references in the output. Style Dictionary may warn that the dark file references tokens it filtered out; here that is intended, because the light file already defines the primitives on <code dir=\"ltr\">:root</code>.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Editing generated files</span></p>\n<p>Generated CSS looks like ordinary CSS, so sooner or later someone fixes a color directly in <code dir=\"ltr\">dist/</code>. The next build wipes the fix. Put a “Generated, do not edit” header in every output file, keep <code dir=\"ltr\">dist/</code> out of code review diffs, and route every change through the JSON.</p>\n</aside>\n<aside class=\"callout callout-note\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">Note</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Check your tool’s DTCG version</span></p>\n<p>The object color format arrived with DTCG 2025.10, and tools adopted it at different times: Style Dictionary’s built-in transforms read object colors from 5.3 and object dimensions from 5.4. Before migrating Northwind’s files to it, we ran the pipeline against a few sample tokens and compared the CSS output. Do the same; if your version still expects hex strings, keep the <code dir=\"ltr\">hex</code> field populated or add a small custom transform.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Checks that run with the build</h2>\n<p>Once tokens are data, CI can test design decisions like code. Northwind’s pipeline runs three checks on every pull request: the naming lint from earlier, a reference check that fails on broken or circular aliases, and a contrast check that resolves each documented text and background pair in every theme and fails below 4.5:1. That last one caught a dark-mode regression in the Tidewater brand before any designer saw it.</p>\n<p>In the exercise, you complete the dark theme’s build config. That finishes the token architecture; the next section builds components on top of it.</p>\n","toc":[{"id":"sec-1","text":"What a pipeline does","level":2},{"id":"sec-2","text":"Keep the references","level":2},{"id":"sec-3","text":"A real pipeline with Style Dictionary","level":2},{"id":"sec-4","text":"Checks that run with the build","level":2}],"takeaways":["A token pipeline parses source files, resolves aliases, transforms names and values per platform, and writes one output format per platform.","Keep references in CSS output (<code dir=\"ltr\">outputReferences</code>) so semantic tokens still point at primitives at runtime and subtree theming keeps working.","Build each theme from shared primitives plus that theme’s semantic file, and emit only the theme’s own tokens under its selector.","Generated files are build output; nobody edits them by hand, and CI rebuilds them on every change."],"further":[{"title":"Style Dictionary (official repository and docs)","url":"https://github.com/style-dictionary/style-dictionary"},{"title":"Design Tokens Community Group repository","url":"https://github.com/design-tokens/community-group"},{"title":"Using CSS custom properties (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties"}],"quiz":[{"q":"<p>The pipeline writes <code dir=\"ltr\">--nw-color-action-bg: #1f5ae0;</code> instead of <code dir=\"ltr\">--nw-color-action-bg: var(--nw-color-blue-600);</code>. What breaks?</p>","options":[{"text":"Nothing; the color is identical.","why":"The color is identical today, but overriding the primitive at runtime no longer flows through, and the tiers are no longer visible in the output."},{"text":"The CSS becomes invalid because hex values aren’t allowed in custom properties.","why":"Custom properties accept any valid token stream, including hex colors."},{"text":"Browsers can’t cache the stylesheet.","why":"Caching doesn’t depend on what’s inside custom properties."},{"text":"Runtime overrides of primitives stop reaching semantic tokens, and debugging which primitive a token uses gets harder.","why":"Correct. With references preserved, the output keeps the tier structure; with resolved values, every link is flattened."}],"answer":3},{"q":"<p>Why does the dark theme build use <code dir=\"ltr\">include</code> for primitives and <code dir=\"ltr\">source</code> for the dark semantic file, then filter on <code dir=\"ltr\">isSource</code>?</p>","options":[{"text":"Primitives are needed to resolve references, but only the dark semantic tokens should be written under the dark selector.","why":"Correct. Emitting primitives again under <code dir=\"ltr\">[data-theme=&quot;dark&quot;]</code> would duplicate hundreds of lines without changing anything."},{"text":"Files in <code dir=\"ltr\">include</code> are loaded faster.","why":"Speed isn’t the point; the difference is whether tokens count as source tokens for filtering."},{"text":"Style Dictionary can’t read two files in <code dir=\"ltr\">source</code>.","why":"The <code dir=\"ltr\">source</code> array accepts many files and globs."}],"answer":0},{"q":"<p>A product engineer edits <code dir=\"ltr\">dist/css/theme-dark.css</code> directly to fix a contrast bug, and the fix vanishes after the next release. What should the process have been?</p>","options":[{"text":"Commit the generated CSS so the edit is kept.","why":"Committing it doesn’t stop the next build from overwriting it; the source is still wrong."},{"text":"Fix the token in the source JSON, let CI rebuild, and mark generated files with a “do not edit” header.","why":"Correct. The JSON is the source of truth; generated files should say so at the top."},{"text":"Add the fix to the product’s own stylesheet instead.","why":"That hides the bug for one product and leaves every other consumer of the system with it."},{"text":"Turn off the build step for CSS.","why":"Then CSS and the other platforms drift apart, which is what the pipeline exists to prevent."}],"answer":1}],"exercise":{"kind":"fill","language":"js","template":"export default {\n  include: ['tokens/primitives.tokens.json'],\n  source: ['tokens/semantic-dark.tokens.json'],\n  platforms: {\n    css: {\n      transformGroup: 'css',\n      prefix: '{{0}}',\n      buildPath: 'dist/css/',\n      files: [{\n        destination: 'theme-dark.css',\n        format: '{{1}}',\n        filter: (token) => token.{{2}},\n        options: {\n          selector: '{{3}}',\n          outputReferences: {{4}},\n        },\n      }],\n    },\n  },\n};","blanks":[{"answers":["nw"]},{"answers":["css/variables"]},{"answers":["isSource"]},{"answers":["[data-theme=\"dark\"]","[data-theme=dark]"]},{"answers":["true"]}],"title":"Complete the dark theme build","prompt":"<p>Fill in the Style Dictionary config that generates Northwind’s dark theme. The output must:</p>\n<ul>\n<li>name every variable with the <code dir=\"ltr\">nw</code> prefix (<code dir=\"ltr\">--nw-…</code>),</li>\n<li>use Style Dictionary’s built-in CSS custom properties format,</li>\n<li>contain only the dark semantic tokens, not the primitives loaded through <code dir=\"ltr\">include</code>,</li>\n<li>wrap them in the attribute selector that switches any element to dark, written with double quotes inside the single-quoted string,</li>\n<li>keep <code dir=\"ltr\">var()</code> references instead of resolved values.</li>\n</ul>\n","hints":["The format name is the same one used in the lesson’s build script.","Tokens loaded through <code dir=\"ltr\">source</code> are marked differently from tokens loaded through <code dir=\"ltr\">include</code>. Which property tells them apart?","The selector is the one from the themes lesson: an attribute selector on its own, without <code dir=\"ltr\">html</code>."],"explanation":"<p><code dir=\"ltr\">prefix: 'nw'</code> adds the namespace to every name. <code dir=\"ltr\">css/variables</code> writes custom properties inside the given selector. Filtering on <code dir=\"ltr\">token.isSource</code> keeps the dark semantic tokens and drops the primitives, which were only included so references could resolve. <code dir=\"ltr\">[data-theme=&quot;dark&quot;]</code> lets any element start a dark subtree, and <code dir=\"ltr\">outputReferences: true</code> keeps lines like <code dir=\"ltr\">--nw-color-action-bg: var(--nw-color-blue-400);</code> so the tier structure survives into the browser.</p>\n","checkLabels":{}},"words":696,"readMinutes":3}