{"id":"l-ds-4-3","courseId":"design-systems","lang":"en","kind":"lesson","summary":"Remove tokens, props and components without breaking teams, by shipping the replacement first, deprecating loudly in every channel, automating migration with codemods, and deleting only in a major release.","html":"<p>Removing things is the hardest part of running a design system. Adding a component makes one team happy; removing one makes forty teams do work they didn’t plan for. Northwind’s first major release removed eleven props and six tokens in one go, with a migration guide published the same day. Two teams were still on the old major nine months later, quietly forking fixes. The removals themselves were right. The path to them was missing.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The deprecation path</h2>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 210\" role=\"img\" aria-labelledby=\"l-ds-4-3-t1\"><defs><marker id=\"arrow-l-ds-4-3-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-4-3-t1\">A timeline from version 4.3 to 5.0 and beyond: 4.3 ships the replacement and deprecates the old API; 4.4 to 4.9 run migration with codemods and usage tracking; 5.0 removes the old API; 4.x keeps receiving patches for six months.</title>\n  <path class=\"d-line\" d=\"M30 90 L650 90\"/>\n  <circle class=\"d-dot\" cx=\"80\" cy=\"90\" r=\"8\"/>\n  <circle class=\"d-dot\" cx=\"300\" cy=\"90\" r=\"8\"/>\n  <circle class=\"d-dot\" cx=\"520\" cy=\"90\" r=\"8\"/>\n  <text class=\"d-label-strong\" x=\"80\" y=\"60\" text-anchor=\"middle\">4.3.0</text>\n  <text class=\"d-label-strong\" x=\"300\" y=\"60\" text-anchor=\"middle\">4.4 to 4.9</text>\n  <text class=\"d-label-strong\" x=\"520\" y=\"60\" text-anchor=\"middle\">5.0.0</text>\n  <rect class=\"d-box-success\" x=\"20\" y=\"112\" width=\"120\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"80\" y=\"136\" text-anchor=\"middle\">Replacement</text>\n  <text class=\"d-label\" x=\"80\" y=\"156\" text-anchor=\"middle\">+ deprecate</text>\n  <rect class=\"d-box-warn\" x=\"230\" y=\"112\" width=\"140\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"300\" y=\"136\" text-anchor=\"middle\">Codemods,</text>\n  <text class=\"d-label\" x=\"300\" y=\"156\" text-anchor=\"middle\">track usage</text>\n  <rect class=\"d-box-primary\" x=\"460\" y=\"112\" width=\"120\" height=\"56\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"520\" y=\"144\" text-anchor=\"middle\">Remove</text>\n  <rect class=\"d-box\" x=\"560\" y=\"20\" width=\"110\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label-muted\" x=\"615\" y=\"45\" text-anchor=\"middle\">4.x patched</text>\n</svg><figcaption>A deprecation spans releases: replacement, warning, migration, removal</figcaption></figure>\n<p>Every removal at Northwind now follows the same steps, spread over several releases.</p>\n<p><strong>1. Ship the replacement first.</strong> You can’t ask teams to migrate to something that doesn’t exist. Button’s <code dir=\"ltr\">variant</code> prop shipped in 4.3.0 as a plain addition, a minor release.</p>\n<p><strong>2. Deprecate in every channel at once.</strong> In the same release, <code dir=\"ltr\">kind</code> is marked deprecated everywhere people might meet it:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Button.tsx</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ButtonProps</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:#6E7781;--shiki-dark:#8B949E\">  /** </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@deprecated</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> Use `variant`. Removed in 5.0.0. */</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  kind</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'primary'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'secondary'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'danger'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  variant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'primary'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'secondary'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'danger'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …rest of the props</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\"> warned</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\"> Set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</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\"> warnOnce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">key</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">message</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</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\"> (process.env.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">NODE_ENV</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'production'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ||</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> warned.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">has</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(key)) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  warned.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(key);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">warn</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`[Northwind UI] ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">message</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>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">variant</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">rest</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ButtonProps</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\"> (kind) </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">warnOnce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'button-kind'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Button `kind` is deprecated; use `variant`. Removed in 5.0.0.'</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\"> resolvedVariant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> variant </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> kind </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'secondary'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …render as before, using resolvedVariant</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The <code dir=\"ltr\">@deprecated</code> JSDoc tag makes editors strike the prop through as people type. The warning appears once per session, only in development builds; bundlers replace <code dir=\"ltr\">process.env.NODE_ENV</code> with a constant so the check and message drop out of production code. The docs page gets a “Deprecated” badge, and the Figma component property is renamed so designers notice too.</p>\n<p>Tokens get the same treatment. The old name stays as an alias of the new one, so nothing breaks, and the token file says so:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JSON</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\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"color\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"text\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"light\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        \"$type\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">        \"$value\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"{color.text.subtle}\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        \"$deprecated\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Use color.text.subtle. Removed in 5.0.0.\"</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></code></pre></div>\n<p><strong>3. Automate the migration.</strong> A codemod is a script that rewrites source code. For a renamed prop, a jscodeshift transform is a few lines:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">codemods/button-kind.js</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\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> transformer</span><span style=\"color:#953800;--shiki-dark:#FFA657\">(file, api) </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\"> j</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> api.jscodeshift;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> j</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(file.source)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">find</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(j.JSXOpeningElement, { name: { name: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Button'</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\">find</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(j.JSXAttribute, { name: { name: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'kind'</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\">forEach</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:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      path.node.name.name </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'variant'</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 style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toSource</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>Teams run it with <code dir=\"ltr\">npx jscodeshift --parser=tsx --extensions=tsx,ts -t codemods/button-kind.js src/</code> (without those two flags jscodeshift only reads <code dir=\"ltr\">.js</code> files with the Babel parser), then review the diff. For the five largest consumers, Northwind’s core team opens the migration pull requests itself. A rename that would cost forty teams an afternoon each becomes forty quick reviews.</p>\n<p>Codemods have limits, and the migration guide should name them. The transform above renames <code dir=\"ltr\">kind=&quot;danger&quot;</code> and <code dir=\"ltr\">kind={isUrgent ? 'danger' : 'primary'}</code> correctly, because both are attributes written on a <code dir=\"ltr\">&lt;Button&gt;</code>. It can’t see <code dir=\"ltr\">kind</code> hidden inside spread props, such as <code dir=\"ltr\">&lt;Button {...actionProps} /&gt;</code> where <code dir=\"ltr\">actionProps</code> was built in another file, or a Button re-exported under another name. That’s why the runtime warning matters even after the codemod has run: it catches the cases static rewriting can’t reach. Northwind’s guide for every removal has the same four parts: before-and-after code, the codemod command, the known cases the codemod misses, and a link to the office hours slot where the core team helps with the rest.</p>\n<p><strong>4. Measure, then remove.</strong> Code search across repositories tells you how many uses remain; Section 5 covers how Northwind collects that automatically. When usage is near zero, the removal goes into the next major release, batched with other planned removals so teams face one upgrade instead of several.</p>\n<p><strong>5. Support the previous major.</strong> After 5.0.0 ships, 4.x still gets bug and security fixes for six months. Teams that can’t upgrade immediately aren’t stranded, and they don’t fork.</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>Deprecating without a date</span></p>\n<p>“Deprecated, will be removed in a future version” gives nobody a reason to act this quarter. Every deprecation message names the replacement and the version that removes it. Teams plan around dates; they ignore “someday”.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">Announce it like a product change</h2>\n<p>A deprecation is news for forty teams, so treat it like a product announcement rather than a line in a changelog. Northwind posts every deprecation in the design system channel with three sentences: what is changing, why it’s worth the effort, and when the removal happens. The post links the migration guide and is pinned until the major ships. Engineering managers get a monthly summary of upcoming removals that affect their teams, because they’re the ones who schedule the work.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">How long is long enough?</h2>\n<p>Northwind keeps a deprecated API for at least two minor releases or three months, whichever is longer, before the major that removes it, and publishes the major release schedule a quarter ahead. Your numbers can differ; what matters is that they’re written down and predictable, so teams can plan migrations into their own roadmaps instead of discovering them in a failed build.</p>\n<p>In the exercise, you put a full deprecation in order. Next, you build the release pipeline that catches unintended breaking changes before they ship.</p>\n","toc":[{"id":"sec-1","text":"The deprecation path","level":2},{"id":"sec-2","text":"Announce it like a product change","level":2},{"id":"sec-3","text":"How long is long enough?","level":2}],"takeaways":["Never deprecate something until its replacement has shipped; a deprecation without an alternative is just a complaint.","Mark deprecations everywhere people look - types, tokens, dev-only console warnings, docs and Figma - and always name the replacement and the removal version.","Codemods turn a migration from a task for 40 teams into a review for 40 teams.","Remove deprecated things only in a major release, after measuring that usage is near zero, and keep patching the previous major for a stated period."],"further":[{"title":"jscodeshift (official repository)","url":"https://github.com/facebook/jscodeshift"},{"title":"Design token $deprecated property (DTCG specification source)","url":"https://github.com/design-tokens/community-group/blob/main/technical-reports/format/design-token.md"},{"title":"console: warn() static method (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/API/console/warn_static"}],"quiz":[{"q":"<p>The core team wants to deprecate ShipmentCard because the composed Card is better. Card’s Footer slot hasn’t shipped yet. What should happen first?</p>","options":[{"text":"Ship the Card pieces, including the Footer slot, so every ShipmentCard use has a working replacement.","why":"Correct. Teams can only migrate to something that exists and covers their use case."},{"text":"Deprecate ShipmentCard now so teams stop adding new uses.","why":"Teams who need a card today have no alternative, so they’ll ignore the warning or build their own."},{"text":"Remove ShipmentCard in the next major and let teams adapt.","why":"Removing it with no replacement breaks 60 screens and leaves teams nothing to move to."},{"text":"Write the migration guide first, then build Card later.","why":"A guide that points to an unreleased component can’t be followed."}],"answer":0},{"q":"<p>Where should the “Button <code dir=\"ltr\">kind</code> is deprecated” warning appear at runtime?</p>","options":[{"text":"In production as an alert, so product managers notice.","why":"End users would see a message about code they can’t change."},{"text":"Nowhere; the changelog is enough.","why":"Most engineers don’t read every changelog; a warning in their own console during development reaches them where they work."},{"text":"In every render, in every environment.","why":"Logging on every render floods the console and hides real errors; production users don’t need it either."},{"text":"In the development console, once per session, naming the replacement and the removal version.","why":"Correct. It reaches developers while they work on the code, without noise in production."}],"answer":3},{"q":"<p>Northwind’s code search shows 4 remaining uses of the deprecated <code dir=\"ltr\">kind</code> prop across 40 repositories, all in one team’s legacy admin tool. The 5.0 release is next week. What’s the best move?</p>","options":[{"text":"Delay 5.0 until all 40 repositories are free of every deprecated API.","why":"Holding a major release hostage to one legacy tool delays every other team’s improvements."},{"text":"Ship 5.0, and open a pull request to that team running the codemod, telling them they can stay on 4.x, which still gets patches, until it merges.","why":"Correct. The remaining work is small, you help directly, and the support window protects them meanwhile."},{"text":"Ship 5.0 without telling the team.","why":"Their next dependency update breaks with no warning, which costs the system trust."}],"answer":1}],"exercise":{"kind":"order","items":["replacement","deprecate","codemod","measure","remove","support"],"title":"Plan the token rename end to end","prompt":"<p>Northwind is replacing the token <code dir=\"ltr\">--nw-color-text-light</code> with <code dir=\"ltr\">--nw-color-text-subtle</code>. About 300 uses exist across 40 repositories, and the current version is 4.6.0. Put the steps of the deprecation path in order.</p>\n","hints":["People can only migrate to something that already exists.","Removal is a breaking change, so it waits for a major release and for usage data.","One step happens after the removal ships."],"explanation":"<p>The new token ships first (a minor release), so there is something to move to. In the same release the old name becomes an alias marked <code dir=\"ltr\">$deprecated</code>, with docs and changelog naming the replacement and the removal version. Codemods and migration pull requests do the bulk of the work, usage tracking shows when the remaining uses are near zero, and only then does the next major remove the alias. Patching 4.x for a stated window afterwards protects teams that need more time.</p>\n","checkLabels":{},"itemText":{"replacement":"Ship <code dir=\"ltr\">--nw-color-text-subtle</code> in 4.7.0","deprecate":"Keep <code dir=\"ltr\">--nw-color-text-light</code> as an alias marked <code dir=\"ltr\">$deprecated</code>, and announce the removal version in the docs and changelog","codemod":"Publish a codemod and open migration pull requests for the largest consumers","measure":"Track remaining uses across repositories until they are near zero","remove":"Remove the old token in 5.0.0, batched with other planned removals","support":"Keep shipping bug fixes on 4.x for six months"}},"words":723,"readMinutes":3}