{"id":"l-ds-3-2","courseId":"design-systems","lang":"en","kind":"lesson","summary":"Recognize prop explosion and replace it with compound components that share state through context, while knowing when a small configured component is still the better choice.","html":"<p>The first version of Northwind’s ShipmentCard had six props. A year later it had twenty-three: <code dir=\"ltr\">title</code>, <code dir=\"ltr\">subtitle</code>, <code dir=\"ltr\">status</code>, <code dir=\"ltr\">statusTone</code>, <code dir=\"ltr\">showMenu</code>, <code dir=\"ltr\">menuItems</code>, <code dir=\"ltr\">footerText</code>, <code dir=\"ltr\">footerAction</code>, <code dir=\"ltr\">footerActionVariant</code>, <code dir=\"ltr\">hideBorder</code>, <code dir=\"ltr\">compact</code>, <code dir=\"ltr\">imageUrl</code>, <code dir=\"ltr\">imagePosition</code>, and so on. Every team’s slightly different card became one more prop. The component’s code was a thicket of conditionals, and the docs page listed props nobody could keep in their head. This is prop explosion, and every design system meets it.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Configuration versus composition</h2>\n<p>A <strong>configured</strong> component receives data and decides the layout itself. A <strong>composed</strong> component offers pieces, and the consumer arranges them.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 660 240\" role=\"img\" aria-labelledby=\"l-ds-3-2-t1\"><defs><marker id=\"arrow-l-ds-3-2-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-3-2-t1\">Left: a single ShipmentCard box fed by a long list of props. Right: a Card box containing Header, Body and Footer pieces that the consumer arranges.</title>\n  <text class=\"d-label-strong\" x=\"150\" y=\"28\" text-anchor=\"middle\">Configuration</text>\n  <rect class=\"d-box\" x=\"20\" y=\"44\" width=\"110\" height=\"170\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"30\" y=\"70\">title</text>\n  <text class=\"d-code\" x=\"30\" y=\"94\">statusTone</text>\n  <text class=\"d-code\" x=\"30\" y=\"118\">menuItems</text>\n  <text class=\"d-code\" x=\"30\" y=\"142\">footerText</text>\n  <text class=\"d-code\" x=\"30\" y=\"166\">hideBorder</text>\n  <text class=\"d-label-muted\" x=\"30\" y=\"196\">+18 more</text>\n  <path class=\"d-arrow\" d=\"M130 129 L176 129\" marker-end=\"url(#arrow-l-ds-3-2-1)\"/>\n  <rect class=\"d-box-primary\" x=\"180\" y=\"94\" width=\"110\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"235\" y=\"134\" text-anchor=\"middle\">ShipmentCard</text>\n  <text class=\"d-label-strong\" x=\"500\" y=\"28\" text-anchor=\"middle\">Composition</text>\n  <rect class=\"d-box-primary\" x=\"390\" y=\"44\" width=\"220\" height=\"180\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"404\" y=\"68\">Card</text>\n  <rect class=\"d-box-accent\" x=\"410\" y=\"80\" width=\"180\" height=\"36\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"500\" y=\"103\" text-anchor=\"middle\">Card.Header</text>\n  <rect class=\"d-box-accent\" x=\"410\" y=\"124\" width=\"180\" height=\"44\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"500\" y=\"151\" text-anchor=\"middle\">Card.Body</text>\n  <rect class=\"d-box-accent\" x=\"410\" y=\"176\" width=\"180\" height=\"36\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"500\" y=\"199\" text-anchor=\"middle\">Card.Footer</text>\n</svg><figcaption>One component with many props, versus small pieces arranged by the consumer</figcaption></figure>\n<p>Here is the same shipment card, composed:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">RouteCard.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:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Shipment NW-4471&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Badge</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> tone</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"info\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>In transit&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Badge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">ShipmentMenu</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> shipmentId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"NW-4471\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Body</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Rotterdam to Hamburg · 2 pallets&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Body</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Footer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<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\"> size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sm\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Track&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<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\"> size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"sm\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> variant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"primary\"</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card.Footer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Card</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>The card never needed a <code dir=\"ltr\">footerSecondaryAction</code> prop because <code dir=\"ltr\">Card.Footer</code> is a slot that accepts any actions. The menu is the team’s own component, placed where the header allows. Yet the system still owns spacing, borders, typography and responsive behavior of each piece. Teams get flexibility in <em>what goes where</em>; the system keeps control of <em>how each piece looks</em>.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Sharing state between the pieces</h2>\n<p>Static pieces like Card only need layout. Interactive ones like Tabs need shared state: the selected tab, and which panel to show. The robust way to share it is context:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Tabs.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\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { createContext, use, useId, useState } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react'</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\"> TabsContext</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> createContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;{</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  selected</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:#8250DF;--shiki-dark:#D2A8FF\">  select</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</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:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  baseId</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:#1F2328;--shiki-dark:#E6EDF3\">} </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">|</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">null</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useTabs</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\"> ctx</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> use</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(TabsContext);</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\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">ctx) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Tabs.* must be used inside &#x3C;Tabs>'</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:#1F2328;--shiki-dark:#E6EDF3\"> ctx;</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\"> Tabs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">defaultValue</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">children</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 style=\"color:#953800;--shiki-dark:#FFA657\">defaultValue</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\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> React</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ReactNode</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:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selected</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useState</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(defaultValue);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> baseId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useId</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:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">TabsContext</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{ selected, select, baseId }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">TabsContext</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:#1F2328;--shiki-dark:#E6EDF3\">Tabs.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">Trigger</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Trigger</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 style=\"color:#953800;--shiki-dark:#FFA657\">children</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 style=\"color:#953800;--shiki-dark:#FFA657\">value</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\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> React</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ReactNode</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:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selected</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">baseId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useTabs</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:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      type</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"button\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      role</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tab\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">baseId</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}-tab-${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      aria-selected</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">selected </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      aria-controls</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">baseId</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}-panel-${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">value</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(value)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\">      {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</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:#6E7781;--shiki-dark:#8B949E\">// Tabs.List (role=\"tablist\") and Tabs.Panel (role=\"tabpanel\") follow the same idea.</span></span></code></pre></div>\n<p>A trigger reads the shared state wherever it sits in the tree: inside a tooltip, a layout <code dir=\"ltr\">div</code>, or a component the team wrote. In React 19 you render the context directly as a provider (<code dir=\"ltr\">&lt;TabsContext value={…}&gt;</code>) and read it with <code dir=\"ltr\">use</code>. The ARIA roles come from the WAI-ARIA tabs pattern, which you’ll meet properly in the next lesson; keyboard handling is left out here to keep the example short.</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>Wiring compound components by inspecting children</span></p>\n<p>An older technique has the parent loop over <code dir=\"ltr\">children</code> with <code dir=\"ltr\">React.Children.map</code> and inject props into each trigger. It works in the demo and breaks the first time a team wraps a trigger in a tooltip, because the parent now sees the tooltip, not the trigger. Use context; it doesn’t care how deep or how wrapped the pieces are.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Composition has costs too</h2>\n<p>Composition isn’t free. Consumers write more markup, and they can arrange pieces in ways you didn’t intend, such as two footers or a title in the body. Northwind handles this three ways. Each piece is constrained: <code dir=\"ltr\">Card.Footer</code> lays out actions and nothing else looks right in it. Common arrangements ship as documented recipes that teams copy. And a few very common combinations become small configured wrappers built <em>from</em> the composed pieces, such as a <code dir=\"ltr\">ShipmentSummaryCard</code> with four props, so the 80% case stays short while the 20% case stays possible.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Migrating away from a configured component</h2>\n<p>You rarely get to start fresh. ShipmentCard was used on 60 screens when we redesigned it. We didn’t delete it; we rebuilt it <em>on top of</em> the new pieces, so its 23 props now render <code dir=\"ltr\">Card.Header</code>, <code dir=\"ltr\">Card.Body</code> and <code dir=\"ltr\">Card.Footer</code> internally. Every existing screen kept working and picked up the new styling for free. Then we marked ShipmentCard deprecated, documented the composed equivalent of each common prop combination, and migrated screens team by team. Building the old API from the new pieces is what made the migration boring, which is the highest compliment a migration can get.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">When configuration is right</h2>\n<p>Not every component should be compound. A status Badge has a <code dir=\"ltr\">tone</code> and a label; an Avatar has an image, a name and a size. Their variations are few and closed, so props describe them completely and composition would only add ceremony.</p>\n<p>A practical rule from Northwind’s API reviews: when props start describing <strong>layout</strong> (<code dir=\"ltr\">imagePosition</code>, <code dir=\"ltr\">footerAlign</code>) or <strong>content structure</strong> (<code dir=\"ltr\">menuItems</code>, <code dir=\"ltr\">footerText</code>), the component wants slots or pieces. When props describe <strong>appearance or state</strong> (<code dir=\"ltr\">tone</code>, <code dir=\"ltr\">size</code>, <code dir=\"ltr\">disabled</code>), configuration is fine.</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>Ask what the next ten requests will be</span></p>\n<p>In API review, don’t just evaluate today’s request. Ask the requesting team what similar cards look like elsewhere in their product. If you can picture ten more props coming, design the slot now.</p>\n</aside>\n<p>In the exercise, you judge a Dialog API proposal with these rules. Next, you make sure every one of these pieces works with a keyboard and a screen reader, by default.</p>\n","toc":[{"id":"sec-1","text":"Configuration versus composition","level":2},{"id":"sec-2","text":"Sharing state between the pieces","level":2},{"id":"sec-3","text":"Composition has costs too","level":2},{"id":"sec-4","text":"Migrating away from a configured component","level":2},{"id":"sec-5","text":"When configuration is right","level":2}],"takeaways":["When every new use case adds a prop, the component is configured from the outside and will keep growing; composition lets consumers arrange pieces instead.","Compound components such as <code dir=\"ltr\">Card.Header</code> and <code dir=\"ltr\">Tabs.Trigger</code> share state through context, not by inspecting children.","The system still owns each piece’s look and behavior, so composition adds flexibility without giving up consistency.","Small, closed components like Badge or Avatar are fine with configuration; reach for composition when props start describing layout or content."],"further":[{"title":"Passing data deeply with context (React)","url":"https://react.dev/learn/passing-data-deeply-with-context"},{"title":"use (React API reference)","url":"https://react.dev/reference/react/use"},{"title":"Tabs pattern (WAI-ARIA Authoring Practices)","url":"https://www.w3.org/WAI/ARIA/apg/patterns/tabs/"}],"quiz":[{"q":"<p>A team asks for a <code dir=\"ltr\">footerSecondaryActionIcon</code> prop on ShipmentCard. What does this request most likely signal?</p>","options":[{"text":"The card needs one more prop, and then it will be complete.","why":"Each “last” prop is followed by another; the pattern shows the API has no natural end."},{"text":"The team should build its own card.","why":"A fork loses the system’s styling and fixes; the problem is the card’s API shape, not the team."},{"text":"The card is configured from the outside, so layout and content decisions keep turning into props.","why":"Correct. A footer slot that accepts any actions would have absorbed this request and the next ten."},{"text":"The icon set is missing an icon.","why":"The request is about where content goes, not which icons exist."}],"answer":2},{"q":"<p>How should <code dir=\"ltr\">Tabs.Trigger</code> find out which tab is selected?</p>","options":[{"text":"Read it from a context that <code dir=\"ltr\">Tabs</code> provides.","why":"Correct. Context works however deeply the trigger is nested and whatever order the consumer uses."},{"text":"<code dir=\"ltr\">Tabs</code> loops over its children with <code dir=\"ltr\">React.Children.map</code> and injects props.","why":"That breaks as soon as a trigger is wrapped in another element, such as a tooltip or a layout div."},{"text":"Each trigger reads a global variable.","why":"Two tab sets on one page would fight over the same variable."},{"text":"The consumer passes <code dir=\"ltr\">selected</code> to every trigger by hand.","why":"Possible, but it pushes the component’s own state management onto every team."}],"answer":0},{"q":"<p>Which component is the best candidate to keep as a simple configured component?</p>","options":[{"text":"A Dialog with a header, body, footer and optional side panel.","why":"Its content and layout vary a lot between uses, which is exactly where composition helps."},{"text":"A data table with custom cells, toolbars and row actions.","why":"Tables vary enormously; configuration-only tables become the biggest prop lists in any system."},{"text":"A status Badge with a <code dir=\"ltr\">tone</code> and a short label.","why":"Correct. Its variations are few and enumerable, so a couple of props describe it completely."}],"answer":2}],"exercise":{"kind":"choice","multiple":false,"options":["props","children-only","compound","per-team"],"correct":["compound"],"title":"Choose the Dialog API","prompt":"<p>Northwind needs a Dialog. Known uses so far: a delete confirmation (title, sentence, two buttons), a driver-assignment form with a searchable list, a route preview with a map and a side panel, and a marketing newsletter sign-up. Four API proposals arrived. Pick the one you would approve.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">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:#6E7781;--shiki-dark:#8B949E\">// A</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> title</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Delete route?\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> body</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"…\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> confirmLabel</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Delete\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> confirmVariant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"danger\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        cancelLabel</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Cancel\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> showSidePanel</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onConfirm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</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>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// B</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> open</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">open</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onOpenChange</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">setOpen</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">anything</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><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\">// C</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> open</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">open</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onOpenChange</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">setOpen</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\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Delete route?&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Header</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Body</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>…&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Body</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Footer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<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\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">close</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Cancel&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<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\"> variant</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"danger\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> onClick</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">remove</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Delete&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog.Footer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Dialog</span><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\">// D</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// No shared Dialog: each team builds its own on top of the native &#x3C;dialog> element.</span></span></code></pre></div>\n","hints":["List how different the four known uses are. Which API absorbs a map with a side panel without new props?","Then ask which option still lets the system own focus handling, spacing and the title’s accessible name."],"explanation":"<p>Proposal C composes: the system owns the overlay, focus handling, spacing and the title that names the dialog, while teams arrange header, body and footer content freely. A works for the delete confirmation and turns into prop explosion by the second use case. B is flexible but gives up structure, so every team re-solves titles, spacing and footers differently. D throws away the system’s accessibility work entirely. A small configured <code dir=\"ltr\">ConfirmDialog</code> can later be built on top of C for the most common case.</p>\n","checkLabels":{},"optionText":{"props":{"text":"A: one Dialog configured entirely through props.","why":"It fits the confirmation and fails the map preview; every new use becomes another prop such as <code dir=\"ltr\">showSidePanel</code>."},"children-only":{"text":"B: a Dialog that renders whatever children it receives.","why":"Maximally flexible, but the system no longer owns titles, spacing or footers, so dialogs drift apart again."},"compound":{"text":"C: a compound Dialog with Header, Title, Body and Footer pieces.","why":"Correct. It covers all four uses while the system keeps control of structure, styling and accessibility."},"per-team":{"text":"D: no shared component; teams use the native <code dir=\"ltr\">&lt;dialog&gt;</code> element directly.","why":"The native element is a good foundation for the system’s Dialog, but leaving every team to style and label it recreates the inconsistency the system exists to remove."}}},"words":697,"readMinutes":3}