{"id":"l-ds-2-1","courseId":"design-systems","lang":"en","kind":"lesson","summary":"Structure design tokens in primitive, semantic and component tiers as CSS custom properties, so one change of intent updates every component that shares it.","html":"<p>Northwind’s first token file was a list of colors: <code dir=\"ltr\">--blue-600</code>, <code dir=\"ltr\">--gray-200</code>, <code dir=\"ltr\">--red-500</code>. Every component used them directly. It looked tidy until the Tidewater acquisition, when someone asked a simple question: “Which blues are Northwind brand blues, and which are just links?” Nobody could answer without reading 300 files. A flat list of colors records values. It doesn’t record decisions.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Tier 1: primitives</h2>\n<p>Primitives are the palette: every raw value the system allows, named by what it <em>is</em>.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">:root</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-blue-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1f5ae0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-blue-700</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1848b8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-gray-900</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#1f2933</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-gray-200</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#d9dee3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-white</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#ffffff</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-space-2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.5</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-space-4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">rem</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>Primitives answer “which values exist?” They deliberately carry no meaning. <code dir=\"ltr\">--nw-blue-600</code> doesn’t know whether it’s a button, a link or a chart line.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Tier 2: semantic tokens</h2>\n<p>Semantic tokens name an <em>intent</em> and point at a primitive:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">:root</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-blue-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-bg-hover</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-blue-700</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-white</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-gray-900</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-border</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-gray-200</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>This is the tier people use most, and the one themes change. When dark mode or the Tidewater brand arrives, you remap these names to different primitives and leave every component alone.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Tier 3: component tokens</h2>\n<p>Component tokens name one component’s adjustable parts and point at semantic tokens:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">.nw-button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-button-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-button-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-color-action-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-button-padding-x</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-space-4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-button-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-button-text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-space-2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-button-padding-x</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>Now three kinds of change each have one obvious place:</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 660 230\" role=\"img\" aria-labelledby=\"l-ds-2-1-t1\"><defs><marker id=\"arrow-l-ds-2-1-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-1-t1\">Component token button-bg points to semantic token color-action-bg, which points to primitive blue-600 with the value 1f5ae0. Each tier is changed for a different reason.</title>\n  <rect class=\"d-box-accent\" x=\"20\" y=\"30\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"115\" y=\"63\" text-anchor=\"middle\">--nw-button-bg</text>\n  <rect class=\"d-box-primary\" x=\"235\" y=\"30\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"330\" y=\"63\" text-anchor=\"middle\">--nw-color-action-bg</text>\n  <rect class=\"d-box\" x=\"450\" y=\"30\" width=\"190\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"545\" y=\"63\" text-anchor=\"middle\">--nw-blue-600</text>\n  <path class=\"d-arrow\" d=\"M210 58 L231 58\" marker-end=\"url(#arrow-l-ds-2-1-1)\"/>\n  <path class=\"d-arrow\" d=\"M425 58 L446 58\" marker-end=\"url(#arrow-l-ds-2-1-1)\"/>\n  <text class=\"d-label-strong\" x=\"115\" y=\"120\" text-anchor=\"middle\">Component</text>\n  <text class=\"d-label-strong\" x=\"330\" y=\"120\" text-anchor=\"middle\">Semantic</text>\n  <text class=\"d-label-strong\" x=\"545\" y=\"120\" text-anchor=\"middle\">Primitive</text>\n  <text class=\"d-label-muted\" x=\"115\" y=\"150\" text-anchor=\"middle\">Change one component</text>\n  <text class=\"d-label-muted\" x=\"330\" y=\"150\" text-anchor=\"middle\">Change a theme or brand</text>\n  <text class=\"d-label-muted\" x=\"545\" y=\"150\" text-anchor=\"middle\">Change the palette</text>\n  <text class=\"d-code\" x=\"545\" y=\"190\" text-anchor=\"middle\">#1f5ae0</text>\n  <path class=\"d-line\" d=\"M545 86 L545 172\"/>\n</svg><figcaption>Each tier references only the tier below it</figcaption></figure>\n<ul>\n<li><strong>New palette</strong> (a slightly more accessible blue): change the primitive.</li>\n<li><strong>New theme or brand</strong> (dark mode, Tidewater): remap semantic tokens.</li>\n<li><strong>One component needs to differ</strong> (the marketing hero button uses a larger padding): override that component’s token.</li>\n</ul>\n<p>The rule that makes this work: <strong>components reference semantic tokens, never primitives.</strong> The moment a component uses <code dir=\"ltr\">--nw-blue-600</code> directly, it stops following themes, and you have rebuilt the flat list with longer names.</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>Declaring component tokens on :root</span></p>\n<p>If you write <code dir=\"ltr\">--nw-button-bg: var(--nw-color-action-bg)</code> inside <code dir=\"ltr\">:root</code>, the browser substitutes the <code dir=\"ltr\">var()</code> right there, at the root. Every button then inherits the already-computed blue, and an override of <code dir=\"ltr\">--nw-color-action-bg</code> on a <code dir=\"ltr\">.promo</code> section has no effect on its buttons. Declare component tokens on the component’s own selector, so they resolve on each button against whatever semantic values its ancestors set.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">How the browser resolves a token</h2>\n<p>Custom properties inherit like <code dir=\"ltr\">color</code> does. When the browser computes styles for a button, it looks up <code dir=\"ltr\">--nw-button-bg</code> on the button itself, finds <code dir=\"ltr\">var(--nw-color-action-bg)</code>, then looks that up on the button too, where the value is inherited from the nearest ancestor that set it. That’s why you can re-theme part of a page by setting a semantic token on a container:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">.campaign-banner</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-green-600</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>Every button inside the banner turns green; every button outside stays blue. No new classes, no component changes. This scoping behavior is the foundation for themes in the next lessons.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Be stingy with component tokens</span></p>\n<p>Northwind exposes component tokens only for values that teams have actually needed to change, usually two to five per component. Every token you publish is a promise you have to keep through future releases. You can always add a token later; removing one is a breaking change.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">Not just colors</h2>\n<p>Every category of token gets the same treatment. Spacing primitives are a scale (<code dir=\"ltr\">--nw-space-1</code> to <code dir=\"ltr\">--nw-space-12</code>); semantic spacing names a role, such as <code dir=\"ltr\">--nw-space-inset-md</code> for padding inside a card or <code dir=\"ltr\">--nw-space-stack-sm</code> for the gap between stacked form fields. Typography primitives are font families, sizes and weights; semantic typography names styles such as <code dir=\"ltr\">--nw-font-body</code> or <code dir=\"ltr\">--nw-font-heading-lg</code>. Radii, shadows and motion durations follow the same pattern.</p>\n<p>For scale, Northwind today has about 180 primitives, 120 semantic tokens and 90 component tokens. The semantic tier is where most of the design thinking happens, and it’s the tier designers and engineers should reach for first. If a team keeps asking for a primitive, that usually means a semantic token is missing, and the right fix is to add the role, not to hand out the raw value.</p>\n<h2 id=\"sec-6\" tabindex=\"-1\">A quick way to check your tiers</h2>\n<p>Open any component’s CSS and search for your primitive prefix, such as <code dir=\"ltr\">--nw-blue</code> or <code dir=\"ltr\">--nw-gray</code>. In a healthy system those names appear in exactly one place: the semantic token definitions. Northwind runs this as a lint rule in CI, which you will meet in Section 4. In the exercise, you add the semantic and component tiers to a button that currently skips straight to a primitive. Next, you name these tokens so they still make sense in three years.</p>\n","toc":[{"id":"sec-1","text":"Tier 1: primitives","level":2},{"id":"sec-2","text":"Tier 2: semantic tokens","level":2},{"id":"sec-3","text":"Tier 3: component tokens","level":2},{"id":"sec-4","text":"How the browser resolves a token","level":2},{"id":"sec-5","text":"Not just colors","level":2},{"id":"sec-6","text":"A quick way to check your tiers","level":2}],"takeaways":["Primitive tokens name raw values (<code dir=\"ltr\">--nw-blue-600</code>); semantic tokens name intent (<code dir=\"ltr\">--nw-color-action-bg</code>); component tokens name one component’s knobs.","Components reference semantic tokens, never primitives, so re-theming only changes the semantic tier.","Declare component tokens on the component’s selector, not on <code dir=\"ltr\">:root</code>, so they resolve against overrides in that part of the page.","Add component tokens sparingly; each one is public API you will have to support."],"further":[{"title":"Using CSS custom properties (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties"},{"title":"var() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/var"},{"title":"Design tokens (Material Design 3)","url":"https://m3.material.io/foundations/design-tokens"}],"quiz":[{"q":"<p>Marketing wants every primary action on the site to turn green for a campaign. With a three-tier system, which tier do you change?</p>","options":[{"text":"The primitive tier, by changing <code dir=\"ltr\">--nw-blue-600</code> to green.","why":"That turns every use of that blue green, including links and info badges that aren’t actions, and makes the name a lie."},{"text":"The semantic tier, by pointing <code dir=\"ltr\">--nw-color-action-bg</code> at a green primitive.","why":"Correct. Every component that expresses “primary action” follows the change, and nothing else does."},{"text":"The component tier, by overriding the background of each component one by one.","why":"It works, but you would have to find every component with an action color, which is the problem tiers are meant to solve."},{"text":"None; add a new <code dir=\"ltr\">.green</code> class to each button.","why":"One-off classes bypass the system entirely and leave cleanup for after the campaign."}],"answer":1},{"q":"<p>Why does the button inside <code dir=\"ltr\">.promo</code> stay blue?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"css\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">CSS</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:#0550AE;--shiki-dark:#79C0FF\">:root</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-blue-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  --nw-button-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-color-action-bg</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:#0550AE;--shiki-dark:#79C0FF\">.promo</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-color-action-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-green-600</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); }</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">.nw-button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">--nw-button-bg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); }</span></span></code></pre></div>\n","options":[{"text":"Custom properties don’t inherit into buttons.","why":"Custom properties inherit by default, including into buttons; inheritance is what delivers the blue."},{"text":"<code dir=\"ltr\">.promo</code> has lower specificity than <code dir=\"ltr\">:root</code>.","why":"The two rules target different elements, so specificity isn’t compared; both declarations apply where they are written."},{"text":"The browser caches the first value of each custom property.","why":"There is no caching; values are computed per element during the normal cascade."},{"text":"<code dir=\"ltr\">--nw-button-bg</code> was resolved on <code dir=\"ltr\">:root</code>, so buttons inherit the already-computed blue.","why":"Correct. <code dir=\"ltr\">var()</code> inside a custom property is substituted where it is declared; move <code dir=\"ltr\">--nw-button-bg</code> onto <code dir=\"ltr\">.nw-button</code> and it resolves against <code dir=\"ltr\">.promo</code>’s override."}],"answer":3},{"q":"<p>A teammate proposes a component token for every CSS property of every component. What’s the main cost?</p>","options":[{"text":"Browsers slow down noticeably with more than 100 custom properties.","why":"Browsers handle thousands of custom properties fine; performance isn’t the issue at this scale."},{"text":"Every token becomes public API that teams depend on, so the system gets hard to change.","why":"Correct. Each exposed knob is a promise; renaming or removing it later is a breaking change."},{"text":"Component tokens can’t reference semantic tokens.","why":"They can and should; that’s how the tiers connect."}],"answer":1}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<section class=\"app\">\n  <button class=\"nw-button\" id=\"main\">Assign driver</button>\n</section>\n<section class=\"promo\">\n  <button class=\"nw-button\" id=\"promo\">Start free trial</button>\n</section>","css":"/* Tier 1: primitives (done) */\n:root {\n  --nw-blue-600: #1f5ae0;\n  --nw-blue-700: #1848b8;\n  --nw-white: #ffffff;\n  --nw-space-2: 0.5rem;\n  --nw-space-4: 1rem;\n}\n\n/* Tier 2: add semantic tokens here */\n\n/* Tier 3: the button skips straight to primitives. Fix it. */\n.nw-button {\n  background: var(--nw-blue-600);\n  color: var(--nw-white);\n  padding: var(--nw-space-2) var(--nw-space-4);\n  border: 0;\n  border-radius: 6px;\n  font: inherit;\n}\n.promo { margin-top: 16px; }\n","js":""},"solution":{"html":"<section class=\"app\">\n  <button class=\"nw-button\" id=\"main\">Assign driver</button>\n</section>\n<section class=\"promo\">\n  <button class=\"nw-button\" id=\"promo\">Start free trial</button>\n</section>","css":"/* Tier 1: primitives (done) */\n:root {\n  --nw-blue-600: #1f5ae0;\n  --nw-blue-700: #1848b8;\n  --nw-white: #ffffff;\n  --nw-space-2: 0.5rem;\n  --nw-space-4: 1rem;\n}\n\n/* Tier 2: semantic tokens */\n:root {\n  --nw-color-action-bg: var(--nw-blue-600);\n  --nw-color-action-text: var(--nw-white);\n}\n\n/* Tier 3: component tokens, declared on the component */\n.nw-button {\n  --nw-button-bg: var(--nw-color-action-bg);\n  --nw-button-text: var(--nw-color-action-text);\n\n  background: var(--nw-button-bg);\n  color: var(--nw-button-text);\n  padding: var(--nw-space-2) var(--nw-space-4);\n  border: 0;\n  border-radius: 6px;\n  font: inherit;\n}\n.promo { margin-top: 16px; }\n","js":""},"checks":[{"id":"still-blue","test":"return style('#main', 'background-color') === 'rgb(31, 90, 224)' && style('#main', 'color') === 'rgb(255, 255, 255)';"},{"id":"semantic","test":"const cs = getComputedStyle(document.documentElement); return cs.getPropertyValue('--nw-color-action-bg').trim() !== '' && cs.getPropertyValue('--nw-color-action-text').trim() !== '';"},{"id":"component","test":"const b = $('#main'); b.style.setProperty('--nw-button-bg', 'rgb(4, 5, 6)'); const ok = style(b, 'background-color') === 'rgb(4, 5, 6)'; b.style.removeProperty('--nw-button-bg'); return ok;"},{"id":"follows-semantic","test":"const p = $('.promo'); p.style.setProperty('--nw-color-action-bg', 'rgb(10, 130, 60)'); const ok = style('#promo', 'background-color') === 'rgb(10, 130, 60)' && style('#main', 'background-color') === 'rgb(31, 90, 224)'; p.style.removeProperty('--nw-color-action-bg'); return ok;"},{"id":"no-primitives","test":"const rules = [...document.styleSheets].flatMap((s) => [...s.cssRules]).filter((r) => r.selectorText); const btn = rules.filter((r) => r.selectorText.includes('nw-button')); return btn.length > 0 && btn.every((r) => !/--nw-(blue|white)/.test(r.style.cssText));"}],"title":"Give the button three tiers","prompt":"<p>The primitives are done, but <code dir=\"ltr\">.nw-button</code> reaches straight for <code dir=\"ltr\">--nw-blue-600</code>, so no theme can ever change it. Add the two missing tiers:</p>\n<ol>\n<li>On <code dir=\"ltr\">:root</code>, declare the semantic tokens <code dir=\"ltr\">--nw-color-action-bg</code> and <code dir=\"ltr\">--nw-color-action-text</code>, pointing at the right primitives.</li>\n<li>On <code dir=\"ltr\">.nw-button</code>, declare the component tokens <code dir=\"ltr\">--nw-button-bg</code> and <code dir=\"ltr\">--nw-button-text</code>, pointing at the semantic tokens, and use them for <code dir=\"ltr\">background</code> and <code dir=\"ltr\">color</code>.</li>\n</ol>\n<p>The button must look exactly as it does now, and no <code dir=\"ltr\">.nw-button</code> rule may mention a <code dir=\"ltr\">--nw-blue-…</code> or <code dir=\"ltr\">--nw-white</code> primitive.</p>\n","hints":["Semantic tokens look like <code dir=\"ltr\">--nw-color-action-bg: var(--nw-blue-600);</code> inside <code dir=\"ltr\">:root</code>.","Component tokens go inside the <code dir=\"ltr\">.nw-button { … }</code> rule, not on <code dir=\"ltr\">:root</code>, so they resolve on each button.","Then change <code dir=\"ltr\">background: var(--nw-blue-600)</code> to <code dir=\"ltr\">background: var(--nw-button-bg)</code>, and the same for <code dir=\"ltr\">color</code>."],"explanation":"<p>The button now resolves <code dir=\"ltr\">--nw-button-bg</code> → <code dir=\"ltr\">--nw-color-action-bg</code> → <code dir=\"ltr\">--nw-blue-600</code>. Because the component tokens are declared on <code dir=\"ltr\">.nw-button</code>, each button looks up the semantic token from its own ancestors, so setting <code dir=\"ltr\">--nw-color-action-bg</code> on <code dir=\"ltr\">.promo</code> re-colors only the promo button. Had you declared <code dir=\"ltr\">--nw-button-bg</code> on <code dir=\"ltr\">:root</code>, the value would be computed once at the root and the promo override would do nothing.</p>\n","checkLabels":{"still-blue":"The button still renders blue with white text","semantic":"<code dir=\"ltr\">:root</code> defines <code dir=\"ltr\">--nw-color-action-bg</code> and <code dir=\"ltr\">--nw-color-action-text</code>","component":"The button’s background comes from <code dir=\"ltr\">--nw-button-bg</code>","follows-semantic":"Overriding <code dir=\"ltr\">--nw-color-action-bg</code> on <code dir=\"ltr\">.promo</code> re-colors only the promo button","no-primitives":"No <code dir=\"ltr\">.nw-button</code> rule references a primitive token"}},"words":697,"readMinutes":3}