<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Toucan</title>
    <description>The latest articles on DEV Community by Toucan (@toucan_ui).</description>
    <link>https://dev.to/toucan_ui</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3827388%2F2c0b04e3-a299-4f60-be57-faee294518e9.png</url>
      <title>DEV Community: Toucan</title>
      <link>https://dev.to/toucan_ui</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/toucan_ui"/>
    <language>en</language>
    <item>
      <title>Your design system has a coupling problem</title>
      <dc:creator>Toucan</dc:creator>
      <pubDate>Mon, 16 Mar 2026 17:26:51 +0000</pubDate>
      <link>https://dev.to/toucan_ui/your-design-system-has-a-coupling-problem-1222</link>
      <guid>https://dev.to/toucan_ui/your-design-system-has-a-coupling-problem-1222</guid>
      <description>&lt;p&gt;Pick a popular component library, find the Button component.&lt;/p&gt;

&lt;p&gt;This one thing does three jobs (structure, interactivity, skin/aesthetic).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt; — the semantic HTML and accessibility contract. What element is it? What ARIA role does it have? What does it declare to assistive technology?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interaction&lt;/strong&gt; — the behavioural logic. Focus management, disclosure state, keyboard navigation, scroll locking (i.e modal open).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aesthetics&lt;/strong&gt; — the visual treatment. Colors, spacing, typography, border radius, elevation. Everything that defines the look of a digital interface....the brand.&lt;/p&gt;

&lt;p&gt;These "3 pillars" are subject to constant change in any product...no exclusions. We need to anticipate these changes/fluctuations. Examples: The color palette is updated (aesthetics). New form fields are required which rightly demands accessibility &amp;amp; inclusion (structural). Requirement for a keyboard shortcut is introduced (interaction).&lt;/p&gt;

&lt;p&gt;Binding Structure, Interaction, Aesthetics is a breeding ground for terrible UI/UX. Think buttons...we have icons, pagination, forms, linking to 3rd party sites...all buttons, all entangled. Whats the ARIA, whats the color, what's the icon size. Entangled...And that's buttons, which are an intrinsic part of forms. So now we're dealing with inputs and checkboxes and textareas and the list goes forever on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evidence
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simple button&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;colorPalette&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Icon button — separate component, zero padding preset,&lt;/span&gt;
&lt;span class="c1"&gt;// but literally just extends ButtonProps with no new types.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IconButton&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Search"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SearchIcon&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Button with icon — just compose as children now,&lt;/span&gt;
&lt;span class="c1"&gt;// but YOU manage the icon sizing and spacing.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;colorPalette&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"teal"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EmailIcon&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; Email
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Loading state — still baked into Button itself.&lt;/span&gt;
&lt;span class="c1"&gt;// Sets data-loading, disables the button, swaps children for a Loader.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt; &lt;span class="na"&gt;loadingText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Submitting"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Custom spinner? Pass a whole component via prop.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt; &lt;span class="na"&gt;spinner&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BeatLoader&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Spinner placement? Another prop.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt; &lt;span class="na"&gt;spinnerPlacement&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"end"&lt;/span&gt; &lt;span class="na"&gt;loadingText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Saving..."&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Link that looks like a button — polymorphic `as` prop.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com"&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  External Link
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Router link that looks like a button — different `as`.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ReactRouterLink&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Dashboard&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Button group — parent still controls children's layout,&lt;/span&gt;
&lt;span class="c1"&gt;// border-radius, and attached styling.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Group&lt;/span&gt; &lt;span class="na"&gt;attached&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IconButton&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Add"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AddIcon&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Close button — a THIRD component, with a default&lt;/span&gt;
&lt;span class="c1"&gt;// aria-label baked in. Different from IconButton why, exactly?&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CloseButton&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Form submit — now the same component also needs to&lt;/span&gt;
&lt;span class="c1"&gt;// know about HTML form semantics.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"checkout-form"&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Pay Now
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prop mapping blow up
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Props / API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Loading&lt;/td&gt;
&lt;td&gt;loading, loadingText, spinner, spinnerPlacement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Polymorphism&lt;/td&gt;
&lt;td&gt;as (a, RouterLink, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variants&lt;/td&gt;
&lt;td&gt;variant, colorPalette, size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;disabled, data-loading (set internally)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form&lt;/td&gt;
&lt;td&gt;type, form, formAction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group context&lt;/td&gt;
&lt;td&gt;Group component controls attached layout, border-radius&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;aria-label required on IconButton, baked into CloseButton, inferred nowhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate components&lt;/td&gt;
&lt;td&gt;Button, IconButton, CloseButton — three exports for one concept&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where the entanglement bites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Icon sizing is coupled to button size.&lt;/strong&gt; &lt;code&gt;IconButton&lt;/code&gt; hardcodes &lt;code&gt;_icon={{ fontSize: "1.2em" }}&lt;/code&gt; — the &lt;code&gt;em&lt;/code&gt; unit means it scales relative to the button's font size, which is dictated by &lt;code&gt;size&lt;/code&gt;. Want a &lt;code&gt;sm&lt;/code&gt; button with a slightly larger icon? You're overriding internals. The coupling is baked into the component's source, not exposed as configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Polymorphism shifts ARIA responsibility to you.&lt;/strong&gt; v3 replaced the &lt;code&gt;as&lt;/code&gt; prop with &lt;code&gt;asChild&lt;/code&gt; for rendering as a different element. The good news: &lt;code&gt;chakra.button&lt;/code&gt; no longer injects a conflicting &lt;code&gt;role="button"&lt;/code&gt;. The bad news: if you use &lt;code&gt;asChild&lt;/code&gt; to render an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, you're entirely on your own for role semantics, &lt;code&gt;href&lt;/code&gt; handling, and keyboard behavior. The component doesn't help — it just gets out of the way and hopes you know what you're doing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Group still mutates children via &lt;code&gt;cloneElement&lt;/code&gt;.&lt;/strong&gt; The &lt;code&gt;Group&lt;/code&gt; component (which replaced &lt;code&gt;ButtonGroup&lt;/code&gt;) uses &lt;code&gt;React.cloneElement&lt;/code&gt; to inject &lt;code&gt;data-group-item&lt;/code&gt;, &lt;code&gt;data-first&lt;/code&gt;, &lt;code&gt;data-last&lt;/code&gt;, &lt;code&gt;data-between&lt;/code&gt;, plus CSS variables &lt;code&gt;--group-count&lt;/code&gt; and &lt;code&gt;--group-index&lt;/code&gt; into every child. An &lt;code&gt;IconButton&lt;/code&gt; inside a &lt;code&gt;Group&lt;/code&gt; behaves differently than one outside it — border-radius gets stripped, negative margins get applied — and none of that is visible in your JSX. Classic action-at-a-distance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loading silently prevents form submission.&lt;/strong&gt; The source literally reads &lt;code&gt;disabled={loading || rest.disabled}&lt;/code&gt;. A &lt;code&gt;type="submit"&lt;/code&gt; button with &lt;code&gt;loading={true}&lt;/code&gt; becomes a disabled button — it will not submit the form. The loading and form concerns collide, and there's no warning. You have to just know.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Three components for one concept.&lt;/strong&gt; &lt;code&gt;Button&lt;/code&gt;, &lt;code&gt;IconButton&lt;/code&gt;, and &lt;code&gt;CloseButton&lt;/code&gt; are separate exports with overlapping but inconsistent APIs. &lt;code&gt;IconButton&lt;/code&gt; extends &lt;code&gt;ButtonProps&lt;/code&gt; with zero additional types — its entire implementation is &lt;code&gt;px="0"&lt;/code&gt; and an icon font-size override. A CSS class could do that. Meanwhile, &lt;code&gt;IconButton&lt;/code&gt; requires you to pass &lt;code&gt;aria-label&lt;/code&gt; but &lt;code&gt;CloseButton&lt;/code&gt; has one baked in. Why the inconsistency? Because &lt;code&gt;CloseButton&lt;/code&gt; was designed for dialogs, not general use — the coupling to a specific context leaks into the API.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's step away from system where changing a color means touching every component that references it. It seems like an obvious solution to me. Or just please, no more theme providers that wrap your entire app in a JavaScript object of visual decisions/demands. Flutter being the only excuser of that request - keep doing what you're doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hello tokens
&lt;/h2&gt;

&lt;p&gt;Tokens - named variables that store visual decisions. A color, a spacing value, a border radius. Instead of hardcoding &lt;code&gt;#2563eb&lt;/code&gt; in a component, you reference &lt;code&gt;--color-primary&lt;/code&gt;. Instead of &lt;code&gt;16px&lt;/code&gt;, you reference &lt;code&gt;--space-md&lt;/code&gt;. The value lives in one place, the token. Everything else points to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  My argument
&lt;/h2&gt;

&lt;p&gt;Decouple. That's it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Components own structure and interaction. Tokens own aesthetics.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;&amp;lt;Button&amp;gt;&lt;/code&gt; defines its &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element, &lt;code&gt;role&lt;/code&gt;, &lt;code&gt;aria-pressed&lt;/code&gt;, keyboard handler. That's it. No &lt;code&gt;color&lt;/code&gt;, no &lt;code&gt;padding&lt;/code&gt;, no &lt;code&gt;border-radius&lt;/code&gt;. None.&lt;/li&gt;
&lt;li&gt;A token system defines every visual value — &lt;code&gt;--color-primary: #2563eb&lt;/code&gt;, &lt;code&gt;--space-md: 1rem&lt;/code&gt;, &lt;code&gt;--radius-sm: 4px&lt;/code&gt; — as CSS custom properties. Primitives feed semantic names (&lt;code&gt;--color-action-primary&lt;/code&gt;), semantic names feed component bindings (&lt;code&gt;--button-bg&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;One CSS file maps tokens to component hooks: &lt;code&gt;.button { background: var(--button-bg); padding: var(--button-padding); border-radius: var(--button-radius); }&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just look. If you have buttons that's it. It's dead simple. Let's remove complications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--button-bg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--button-padding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--button-radius&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire wiring. Three layers (raw HTML, :root vars, CSS mapping), zero entanglement.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this fixes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Theme change?&lt;/strong&gt; Edit one token. &lt;code&gt;--color-primary: #2563eb&lt;/code&gt; becomes &lt;code&gt;--color-primary: #dc2626&lt;/code&gt;. Every alias that references it — &lt;code&gt;--color-action-primary&lt;/code&gt;, &lt;code&gt;--color-link&lt;/code&gt;, &lt;code&gt;--color-focus-ring&lt;/code&gt; — resolves to the new value automatically. No component touched. No JS redeployed. One CSS variable, done. Oh...your buttons all use the primary color...update one token, done. Oh...you have a backend system that requires a tweak of the same brand, sure, same components, same patterns, different tokens, new look, done. All by changing 1 key/value pair.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, why not CSS-in-JS?
&lt;/h3&gt;

&lt;p&gt;CSS-in-JS solved real problems — specificity wars, naming collisions, dead code. But it welded visual treatment to the component tree. styled(Button) isn't styling a button, it's creating a new component. That's coupling.&lt;/p&gt;

&lt;p&gt;We traded global CSS problems for component coupling. Same mess, different file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uhm, Tailwind?
&lt;/h3&gt;

&lt;p&gt;Tailwind removed JavaScript coupling. Real progress. But &lt;code&gt;bg-blue-500 hover:bg-blue-600 dark:bg-blue-400 px-4 py-2 rounded-lg&lt;/code&gt; is still aesthetics tangled with structure. Theming means find-and-replace across every component. Dark mode means &lt;code&gt;dark:&lt;/code&gt; on every class. Brand refresh means touching every file.&lt;/p&gt;

&lt;p&gt;Yes, you can consolidate with &lt;code&gt;@apply&lt;/code&gt; — &lt;code&gt;.button { @apply bg-blue-500 hover:bg-blue-600 dark:bg-blue-400 px-4 py-2 rounded-lg }&lt;/code&gt;. But now you're just remapping utility classes to custom classes. You've added a layer of indirection without removing the coupling. The values are still hardcoded, the dark mode logic is still per-class, and a brand refresh still means updating every &lt;code&gt;@apply&lt;/code&gt; block. You've moved the problem, not solved it. Tokens solve it at the source — one value changes, everything downstream follows.&lt;/p&gt;

&lt;p&gt;Tailwind is utility-first. What I'm describing is token-first. The visual decisions don't live in markup at all. They live in one token layer that feeds CSS that feeds components. The component never knows what color it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The separation
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Owns&lt;/th&gt;
&lt;th&gt;Doesn't touch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTML elements, ARIA roles, semantic contracts&lt;/td&gt;
&lt;td&gt;Colors, spacing, JS behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Interaction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Focus, keyboard nav, disclosure, scroll lock&lt;/td&gt;
&lt;td&gt;Markup choice, visual treatment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Aesthetics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tokens → CSS custom properties → component hooks&lt;/td&gt;
&lt;td&gt;DOM structure, event handling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three columns. No overlap. Change one, the others don't know and don't care.&lt;/p&gt;

&lt;p&gt;This isn't theoretical. This is the way we should be building - stop tying things together that share different concerns. It's a gap that needs addressing. As such - &lt;a href="https://toucan-docs.vercel.app/" rel="noopener noreferrer"&gt;I built this&lt;/a&gt;. And if individual tokens are too much (I agree) &lt;a href="https://toucan-wizard.vercel.app/configurator/palette" rel="noopener noreferrer"&gt;here's a helper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get coding - your best bird Toucan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54fv3c2wkvrvam56qw1e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54fv3c2wkvrvam56qw1e.png" alt=" " width="800" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>design</category>
      <category>frontend</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
