{"id":"l-ds-5-3","courseId":"design-systems","lang":"en","kind":"lesson","summary":"Measure design system adoption, token coverage, version currency and team satisfaction with automated scans and surveys, and say no to requests in a way that protects the system and keeps teams’ trust.","html":"<p>Two years into Northwind UI’s second life, I reran the color script from the interface inventory lesson. Sixty-four greys had become nine, and seven of those were primitives in the token file. That one number did more for the team’s funding than any presentation, because it was the same measurement, taken the same way, showing a before and an after. Measuring is how a design system proves it’s a product worth paying for, and how it finds out where it is failing its users.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">What to measure</h2>\n<p>No single number tells you whether a system works. Northwind tracks four trends, reviewed monthly:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Metric</th>\n<th>Question it answers</th>\n<th>How we collect it</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Component adoption</td>\n<td>How much UI is built with system components?</td>\n<td>Import scans of every repository, DOM scans of key pages</td>\n</tr>\n<tr>\n<td>Token coverage</td>\n<td>Are styles using tokens or raw values?</td>\n<td>The color script, extended to spacing and type</td>\n</tr>\n<tr>\n<td>Version lag</td>\n<td>How far behind are teams?</td>\n<td>Package versions across repositories</td>\n</tr>\n<tr>\n<td>Satisfaction</td>\n<td>Does the system make teams faster?</td>\n<td>A five-question survey every quarter</td>\n</tr>\n</tbody>\n</table></div><p>Together they resist gaming. A team could inflate adoption by wrapping everything in a system <code dir=\"ltr\">Box</code>, but token coverage and satisfaction wouldn’t move.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Collecting adoption honestly</h2>\n<p>Import scans are the backbone. A script walks each repository, parses source files, and counts JSX elements whose component was imported from <code dir=\"ltr\">@northwind/ui</code> versus components defined locally. GitHub’s code search gives a fast first answer for a single question, such as “where is the deprecated <code dir=\"ltr\">kind</code> prop still used?”, which is how the deprecation lesson’s usage tracking works.</p>\n<p>For production pages, every Northwind component renders a marker attribute:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">HTML</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:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"nw-button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> data-variant</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"primary\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> data-nw-component</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Button\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Assign driver&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>A scan of the rendered page counts elements with <code dir=\"ltr\">data-nw-component</code> against all elements of the same kind. The attribute matters more than it looks. The first version of our scan counted the <code dir=\"ltr\">nw-button</code> class, and reported 94% Button adoption on the marketing site. Then we found three teams had copied the Button’s CSS into their own components, class names included. Only the real component writes the data attribute, so forks stop counting as adoption.</p>\n<p>The numbers then roll up per team:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><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=\"1\" 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\"> scans</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\">  { team: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dispatch'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, system: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">412</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">455</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, version: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'5.2.0'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { team: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Billing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, system: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">96</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">180</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, version: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'4.9.1'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { team: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Marketing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, system: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">230</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">251</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, version: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'5.2.0'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { team: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Reporting'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, system: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">40</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">160</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, version: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'3.8.0'</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:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> latestMajor</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5</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\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> s</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> scans) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> percent</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((s.system </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> s.total) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</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\"> majorsBehind</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> latestMajor </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(s.version.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">split</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</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\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">s</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">team</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">padEnd</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">String</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">percent</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">padStart</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}%  ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">majorsBehind</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">majorsBehind</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} major(s) behind`</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'current'}`</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 class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>Reporting’s 25% isn’t a reason to send an email to its manager. It’s a research question: what does Reporting need that the system doesn’t have? In Northwind’s case the answer was charts and dense tables, which became the next two quarters of roadmap.</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>Using adoption as a stick</span></p>\n<p>When adoption numbers appear in leadership reviews with team names attached, teams learn to game them: relabeling local components, wrapping things in system primitives they don’t need. Report trends for the whole organization upward, and use per-team numbers only inside conversations with those teams, to find out what’s missing.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Saying no</h2>\n<p>A system that says yes to everything turns back into the 64 greys, with extra steps. Northwind says no to one-team components, brand behavior changes, props that bypass tokens, and anything that breaks a principle. The skill is saying it in a way that keeps the team’s trust:</p>\n<ol>\n<li><strong>Name the reason</strong>, using the principle: “Per-component colors would break dark mode and Tidewater, and our principle is accessible before beautiful.”</li>\n<li><strong>Offer an alternative</strong>: a local build from system parts, an existing component with better docs, a documented escape hatch such as a component token.</li>\n<li><strong>Write it down</strong> in the decision log, so the next team with the same request gets the same answer and the same alternative.</li>\n<li><strong>Leave the door open</strong>: “If two more teams need this, bring it back to API review.”</li>\n</ol>\n<aside class=\"callout callout-why\" 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\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Why this matters</span></p>\n<p>Every yes is a promise to maintain something in every theme, brand and release for years. Forty teams depend on the core team keeping those promises. Saying no clearly and kindly is how you keep the system small enough to keep them.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Where you are now</h2>\n<p>You’ve followed Northwind UI from an audit of 64 greys to a system that forty teams use: principles and a team model, tokens in three tiers with themes and brands, components with composable APIs and accessibility built in, versioned releases with deprecation paths and visual tests, and a contribution model with governance and metrics. None of it is finished, and that’s the point of running it as a product. In the exercise, you write the adoption scan that tells you where to look next.</p>\n","toc":[{"id":"sec-1","text":"What to measure","level":2},{"id":"sec-2","text":"Collecting adoption honestly","level":2},{"id":"sec-3","text":"Saying no","level":2},{"id":"sec-4","text":"Where you are now","level":2}],"takeaways":["Measure adoption with signals only the real system produces, such as package imports or a component’s own data attribute, not class names that can be copied.","Track a few trends together - component adoption, token coverage, version lag and satisfaction - because any single number can be gamed.","Use metrics to find where the system is failing teams, never to shame teams for low numbers.","A good “no” names the principle, offers an alternative, and is written down so it can be revisited with new evidence."],"further":[{"title":"Data attributes (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/data-*"},{"title":"About code search (GitHub Docs)","url":"https://docs.github.com/en/search-github/github-code-search/about-github-code-search"},{"title":"Design Systems 101 (Nielsen Norman Group)","url":"https://www.nngroup.com/articles/design-systems-101/"}],"quiz":[{"q":"<p>Northwind’s VP sets a target: “100% design system adoption by Q4”. What’s the main risk?</p>","options":[{"text":"Teams will reach 100% too quickly and the core team will run out of work.","why":"Systems always have more work than people; that’s not the realistic danger."},{"text":"The scanning scripts can’t count above 100%.","why":"The arithmetic is fine; the problem is what people do to hit the number."},{"text":"Adoption numbers can’t be measured automatically.","why":"Import and DOM scans measure adoption well; measurability isn’t the issue."},{"text":"Teams will force system components into places they don’t fit, or relabel local components, to hit the target.","why":"Correct. When a measure becomes a target, people optimize the measure; some local components are the right answer."}],"answer":3},{"q":"<p>Which signal is most reliable for counting where the real system Button is used in production pages?</p>","options":[{"text":"Elements with the class <code dir=\"ltr\">nw-button</code>.","why":"Teams copy class names along with forked CSS, so forks get counted as adoption."},{"text":"Elements with <code dir=\"ltr\">data-nw-component=&quot;Button&quot;</code>, which only the system’s component renders.","why":"Correct. A marker that only the real component writes can’t be copied by accident with the CSS."},{"text":"Buttons whose background color matches the action token.","why":"Color matches happen by coincidence and miss every non-primary variant."},{"text":"Pages that load the system’s CSS file.","why":"Loading the file says nothing about how many elements on the page actually use it."}],"answer":1},{"q":"<p>The reporting team asks for a <code dir=\"ltr\">chartColors</code> prop on every component so they can match their dashboards. How should the core team say no?</p>","options":[{"text":"Ignore the request until the team stops asking.","why":"Silence reads as disrespect and pushes the team toward forking."},{"text":"Accept it, since saying no hurts adoption.","why":"A prop that bypasses tokens on every component damages every brand and theme to please one team."},{"text":"Explain that per-component colors break theming, point them to the data-visualization palette tokens, record the decision, and invite them to bring evidence if needs change.","why":"Correct. It names the principle, offers a real alternative, and keeps the door open in writing."},{"text":"Tell them the design system team has final authority.","why":"Authority without reasons wins the argument and loses the team."}],"answer":2}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<header class=\"bar\">\n  <button class=\"nw-button\" data-nw-component=\"Button\" data-variant=\"primary\">New shipment</button>\n  <button class=\"btn-legacy\">Export</button>\n</header>\n<main>\n  <button class=\"nw-button\" data-nw-component=\"Button\">Filter</button>\n  <button class=\"nw-button\" data-nw-component=\"Button\">Sort</button>\n  <button class=\"nw-button\">Refresh</button>\n  <section id=\"settings\">\n    <button class=\"nw-button\" data-nw-component=\"Button\">Save</button>\n    <button class=\"btn-legacy\">Reset</button>\n    <button class=\"nw-button\">Advanced</button>\n  </section>\n</main>\n<p id=\"report\"></p>","css":"button { margin: 4px; }\n#report { font-weight: 600; }","js":"// Every real Northwind component renders data-nw-component=\"…\".\n// Some teams copied the nw-button class into their own components.\nfunction measureAdoption(root) {\n  const total = 0;\n  const system = 0;\n  const percent = 0;\n  return { total, system, percent };\n}\n\nconst result = measureAdoption(document);\ndocument.querySelector('#report').textContent = '';\n"},"solution":{"html":"<header class=\"bar\">\n  <button class=\"nw-button\" data-nw-component=\"Button\" data-variant=\"primary\">New shipment</button>\n  <button class=\"btn-legacy\">Export</button>\n</header>\n<main>\n  <button class=\"nw-button\" data-nw-component=\"Button\">Filter</button>\n  <button class=\"nw-button\" data-nw-component=\"Button\">Sort</button>\n  <button class=\"nw-button\">Refresh</button>\n  <section id=\"settings\">\n    <button class=\"nw-button\" data-nw-component=\"Button\">Save</button>\n    <button class=\"btn-legacy\">Reset</button>\n    <button class=\"nw-button\">Advanced</button>\n  </section>\n</main>\n<p id=\"report\"></p>","css":"button { margin: 4px; }\n#report { font-weight: 600; }","js":"// Every real Northwind component renders data-nw-component=\"…\".\n// Some teams copied the nw-button class into their own components.\nfunction measureAdoption(root) {\n  const buttons = [...root.querySelectorAll('button')];\n  const total = buttons.length;\n  const system = buttons.filter((b) => b.dataset.nwComponent === 'Button').length;\n  const percent = total === 0 ? 0 : Math.round((system / total) * 100);\n  return { total, system, percent };\n}\n\nconst result = measureAdoption(document);\ndocument.querySelector('#report').textContent =\n  `${result.system} of ${result.total} buttons use Northwind UI (${result.percent}%)`;\n"},"checks":[{"id":"counts-page","test":"const r = window.measureAdoption(document); return r.total === 8 && r.system === 4 && r.percent === 50;"},{"id":"ignores-copies","test":"const r = window.measureAdoption(document); return r.system === $$('[data-nw-component=\"Button\"]').length && r.system < $$('.nw-button').length;"},{"id":"scoped","test":"const r = window.measureAdoption($('#settings')); return r.total === 3 && r.system === 1 && r.percent === 33;"},{"id":"live","test":"const b = document.createElement('button'); b.dataset.nwComponent = 'Button'; $('main').append(b); const r = window.measureAdoption(document); b.remove(); return r.total === 9 && r.system === 5 && r.percent === 56;"},{"id":"report","test":"const t = text('#report'); return t.includes('4 of 8') && t.includes('50%');"}],"title":"Write the adoption scan","prompt":"<p>Write <code dir=\"ltr\">measureAdoption(root)</code> so it scans the <code dir=\"ltr\">&lt;button&gt;</code> elements inside <code dir=\"ltr\">root</code> (a document or any element) and returns <code dir=\"ltr\">{ total, system, percent }</code>:</p>\n<ul>\n<li><code dir=\"ltr\">total</code>: the number of <code dir=\"ltr\">&lt;button&gt;</code> elements,</li>\n<li><code dir=\"ltr\">system</code>: how many have <code dir=\"ltr\">data-nw-component=&quot;Button&quot;</code>, the marker only the real component renders (the <code dir=\"ltr\">nw-button</code> class doesn’t count; teams copied it),</li>\n<li><code dir=\"ltr\">percent</code>: <code dir=\"ltr\">system / total</code> as a whole number rounded with <code dir=\"ltr\">Math.round</code>, or <code dir=\"ltr\">0</code> when there are no buttons.</li>\n</ul>\n<p>Then write the page result into <code dir=\"ltr\">#report</code> as <code dir=\"ltr\">4 of 8 buttons use Northwind UI (50%)</code>, using your computed numbers.</p>\n","hints":["<code dir=\"ltr\">[...root.querySelectorAll('button')]</code> gives you an array of every button inside <code dir=\"ltr\">root</code>.","<code dir=\"ltr\">button.dataset.nwComponent</code> reads <code dir=\"ltr\">data-nw-component</code>. Filter on it being <code dir=\"ltr\">'Button'</code>.","Build the report with a template literal: <code dir=\"ltr\">${result.system} of ${result.total} buttons use Northwind UI (${result.percent}%)</code>."],"explanation":"<p>Counting by the data attribute measures what you actually care about: elements rendered by the real system component. Counting the <code dir=\"ltr\">nw-button</code> class would report 6 of 8 (75%) here, because two teams copied the class into their own buttons, and you’d never find out that those forks exist. Accepting any <code dir=\"ltr\">root</code> lets the same function report per page section or per team area, and computing on demand means the numbers stay right as the page changes.</p>\n","checkLabels":{"counts-page":"<code dir=\"ltr\">measureAdoption(document)</code> returns 8 buttons, 4 from the system, 50%","ignores-copies":"Copied <code dir=\"ltr\">nw-button</code> classes don’t count as adoption","scoped":"Scanning <code dir=\"ltr\">#settings</code> alone returns 1 of 3 (33%)","live":"The count updates when a system button is added to the page","report":"<code dir=\"ltr\">#report</code> shows “4 of 8” and “50%”"}},"words":719,"readMinutes":3}