<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[UI Engineering Excellence: Resources]]></title><description><![CDATA[Guides, patterns, workflows, and AI helpers like prompts and agents. Learn, adapt, ship.]]></description><link>https://blog.robhameetman.com/s/resources</link><image><url>https://substackcdn.com/image/fetch/$s_!deTI!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc5e8141-3a0f-4181-8804-acdbb61e8707_359x359.png</url><title>UI Engineering Excellence: Resources</title><link>https://blog.robhameetman.com/s/resources</link></image><generator>Substack</generator><lastBuildDate>Sat, 30 May 2026 02:21:01 GMT</lastBuildDate><atom:link href="https://blog.robhameetman.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Robert Henry Hameetman]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[robhameetman@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[robhameetman@substack.com]]></itunes:email><itunes:name><![CDATA[Rob Hameetman]]></itunes:name></itunes:owner><itunes:author><![CDATA[Rob Hameetman]]></itunes:author><googleplay:owner><![CDATA[robhameetman@substack.com]]></googleplay:owner><googleplay:email><![CDATA[robhameetman@substack.com]]></googleplay:email><googleplay:author><![CDATA[Rob Hameetman]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[[WIP] Mode-Aware Tokens with OKLCH]]></title><description><![CDATA[Making the most of the browser's color rendering.]]></description><link>https://blog.robhameetman.com/p/wip-mode-aware-tokens-with-oklch</link><guid isPermaLink="false">https://blog.robhameetman.com/p/wip-mode-aware-tokens-with-oklch</guid><dc:creator><![CDATA[Rob Hameetman]]></dc:creator><pubDate>Sun, 24 Aug 2025 22:30:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NYv-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NYv-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NYv-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp 424w, https://substackcdn.com/image/fetch/$s_!NYv-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp 848w, https://substackcdn.com/image/fetch/$s_!NYv-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp 1272w, https://substackcdn.com/image/fetch/$s_!NYv-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NYv-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp" width="1456" height="1097" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1097,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1909332,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.robhameetman.com/i/171579490?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NYv-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp 424w, https://substackcdn.com/image/fetch/$s_!NYv-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp 848w, https://substackcdn.com/image/fetch/$s_!NYv-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp 1272w, https://substackcdn.com/image/fetch/$s_!NYv-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55ac3ddc-ddc7-4527-843b-20f73583d2a2_2464x1856.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div>
      <p>
          <a href="https://blog.robhameetman.com/p/wip-mode-aware-tokens-with-oklch">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[[WIP] Accessible SVG Icons The Right Way]]></title><description><![CDATA[Icons are everywhere, yet they&#8217;re probably the easiest thing in your design system to screw up.]]></description><link>https://blog.robhameetman.com/p/accessible-svg-icons</link><guid isPermaLink="false">https://blog.robhameetman.com/p/accessible-svg-icons</guid><dc:creator><![CDATA[Rob Hameetman]]></dc:creator><pubDate>Sun, 24 Aug 2025 22:17:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/c54b3810-d9fa-4986-9775-77008fb03c90_2464x1856.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!enWJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!enWJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp 424w, https://substackcdn.com/image/fetch/$s_!enWJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp 848w, https://substackcdn.com/image/fetch/$s_!enWJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp 1272w, https://substackcdn.com/image/fetch/$s_!enWJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!enWJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp" width="1456" height="1097" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1097,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:577250,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.robhameetman.com/i/166355744?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!enWJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp 424w, https://substackcdn.com/image/fetch/$s_!enWJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp 848w, https://substackcdn.com/image/fetch/$s_!enWJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp 1272w, https://substackcdn.com/image/fetch/$s_!enWJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F00e0f6fa-7f74-4bf4-9938-44cc971c0623_2464x1856.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Icons are everywhere, yet they&#8217;re probably the easiest thing in your design system to screw up. I&#8217;ve seen so many <code>&lt;Icon&gt;</code> components that overcomplicate the DOM while doing nothing to make accessibility easier to achieve.</p><p>If your <code>&lt;Icon&gt;</code> component wraps your <code>&lt;svg&gt;</code> element in a <code>&lt;div&gt;</code>- especially if that <code>&lt;div&gt;</code> has no classes- you&#8217;re already headed down the wrong path.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><p>I try to avoid having an <code>&lt;Icon&gt;</code> component at all, though there are some approaches that justify it- like using a custom icon font. We&#8217;ll dive into this later, but the prevalence of icon fonts like FontAwesome hints at the key to keeping icons dead simple: treating them like text.</p><h2>Treating Icons Like Text</h2><p>Icons should behave like text because they're fundamentally semantic content used to convey meaning. They&#8217;re usually inline elements that flow with surrounding text, which means they share the following traits:</p><ul><li><p><strong>Context</strong>: Like words, icons represent concepts, actions, or information states. The rule of thumb with buttons, especially in headers or sidebars for navigation, is to either show the user with an icon or tell the user with a label but not both.</p></li><li><p><strong>Scalability</strong>: Icons should scale conformally with text when users adjust font sizes.</p></li><li><p><strong>Color</strong>: Icons typically inherit the color of surrounding text for visual consistency.</p></li><li><p><strong>Alignment</strong>: Icons need to align properly with text baselines and line heights.</p></li></ul><p>Leveraging the browser&#8217;s ability to render text by setting the <code>&lt;svg&gt;</code> element&#8217;s <code>fill</code> attribute to <code>"currentColor"</code> and using <code>em</code> instead of <code>rem</code> for sizing allows these behaviors to emerge organically.</p><h2>Recommended Props</h2>
      <p>
          <a href="https://blog.robhameetman.com/p/accessible-svg-icons">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[How I Built CSS's light-dark() in TypeScript]]></title><description><![CDATA[Leveraging Modern JavaScript to Mimic CSS Behavior Without Headaches]]></description><link>https://blog.robhameetman.com/p/how-i-built-csss-light-dark-in-typescript</link><guid isPermaLink="false">https://blog.robhameetman.com/p/how-i-built-csss-light-dark-in-typescript</guid><dc:creator><![CDATA[Rob Hameetman]]></dc:creator><pubDate>Mon, 04 Aug 2025 08:52:31 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3b40bed0-7772-4406-997b-9b0d33d77e87_2464x1856.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Hi, I&#8217;m Rob. This is a free issue of UI Engineering Excellence. I write for Frontend Engineers and Product Developers on how to be the best at building apps that win in the market. Subscribe if this is useful. Paid readers get early looks and occasional behind&#8209;the&#8209;scenes notes:</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.robhameetman.com/subscribe?"><span>Subscribe now</span></a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wUS6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wUS6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp 424w, https://substackcdn.com/image/fetch/$s_!wUS6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp 848w, https://substackcdn.com/image/fetch/$s_!wUS6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp 1272w, https://substackcdn.com/image/fetch/$s_!wUS6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wUS6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp" width="1456" height="1097" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1097,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1140746,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.robhameetman.com/i/170046011?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wUS6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp 424w, https://substackcdn.com/image/fetch/$s_!wUS6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp 848w, https://substackcdn.com/image/fetch/$s_!wUS6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp 1272w, https://substackcdn.com/image/fetch/$s_!wUS6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F912ebe0c-4d91-4d4e-b53e-c16766aa8124_2464x1856.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This weekend I learned about CSS's <code>light-dark()</code> function, which automatically adapts colors to user preferences without JavaScript.</p><p>Naturally, I wondered if we can recreate this behavior in TypeScript.</p><p>The challenge was deceptively complex because I wanted a solution that could handle live updates while staying memory-safe.</p><p>In this article, I'll show you how I achieved the desired behavior in tokens that maintain string compatibility while responding to system changes automatically.</p><h2>Why It Matters</h2><p>Design tokens are format-agnostic values that create a shared visual language between designers and engineers. Being format-agnostic means they transcend specific implementations like CSS or TypeScript.</p><p>Keeping the underlying logic consistent across these formats gives us three key advantages:</p><ul><li><p><strong>Truthiness</strong><br>Tokens reflect actual design decisions, not CSS/TypeScript tokens or Figma variables, which eliminates interpretation gaps.</p></li><li><p><strong>Consistency</strong><br>No drift/errata between systems or hex value mismatches in different files.</p></li><li><p><strong>Resilience<br></strong>Update a token in one place and it propagates everywhere.</p></li></ul><p>These three cover the critical needs: accuracy, reliability, and adaptability.</p><h2>The Basic Implementation</h2><p>We could easily implement this in TypeScript just by checking the <code>(prefers-color-scheme: dark)</code> query:</p><pre><code>export const lightDark = (light: string, dark: string) =&gt; {
  const query = globalThis.matchMedia('(prefers-color-scheme: dark)');
  const isDark = query?.matches || false;

  return isDark ? dark : light;
};</code></pre><p>The problem is that we&#8217;ll be passing around these values in the <code>theme</code> object.</p><p>The function above returns a value once, which means we would need to invoke it whenever we want to get the current value of that token.</p><h2>More Advanced Requirements</h2><p>In CSS, our theme tokens are defined like this:</p><pre><code>--app-background-color: light-dark(var(--slate-100), var(--slate-900));
--text-color: light-dark(var(--slate-900), var(--slate-100));</code></pre><p>That means that in TypeScript, our theme object needs to be instantiated likewise:</p><pre><code>export interface Theme {
  readonly appBackgroundColor: string;
  readonly textColor: string;
}

export const THEME = Object.freeze&lt;Theme&gt;({
  appBackgroundColor: lightDark(SLATE[100], SLATE[900]),
  textColor: lightDark(SLATE[900], SLATE[100]),
});</code></pre><p>This adds the following requirements to our implementation:</p><ul><li><p>Values returned by <code>lightDark()</code> must be <strong>auto-updated</strong> when the system theme is changed.</p></li><li><p>This solution must not result in <strong>memory</strong> leaks. Basically, if we use event listeners we need to also use something like an <code>AbortController</code> to remove it if/when our <code>theme</code> object is cleaned up by garbage collection.</p></li><li><p><strong>Performance</strong> should be non-blocking. We need to keep in mind our core web vitals; a bad approach could degrade INP and CLS scores.</p></li><li><p><strong>Syntax</strong> and <strong>typing </strong>should be straightforward enough that this solution results in an intuitive developer experience.</p></li></ul><p>In order for values to be auto-updated while maintaining the best syntax and typing, values returned by lightDark() must be string-like.</p><p>What this means is that the value must behave like a string when we pass it around, especially when we interpolate it in template strings (which is important for CSS-in-JS).</p><p>However, since everything in JavaScript is an object, the value can also have a prototype which is a subtype of String and/or use custom properties/methods that regular strings don&#8217;t have.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">&#10084;&#65039; <em>Like what you&#8217;re seeing so far?</em></p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Possible Approaches</h2><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;\\begin{array}{@{}lccccc@{}}\n  \\textbf{Approach}       &amp; \\textbf{Auto-Update}      &amp; \\textbf{Memory}  &amp; \\textbf{Syntax} &amp; \\textbf{Perf}           &amp; \\textbf{Typing}\\\\\n  \\hline\n  \\text{Proxy}            &amp; \\checkmark                &amp; \\checkmark              &amp; \\text{Good}     &amp; \\checkmark    &amp; \\text{Fair}\\\\\n  \\text{Per-Value Getters}&amp; \\checkmark                &amp; \\checkmark              &amp; \\text{Best}     &amp; \\checkmark              &amp; \\text{Best}\\\\\n  \\text{Value Objects}    &amp; \\times                    &amp; \\checkmark              &amp; \\text{Poor}     &amp; \\checkmark\\checkmark    &amp; \\text{Good}\\\\\n  \\text{Event-Based}      &amp; \\times       &amp; \\text{&#9888;&#65038;}               &amp; \\text{Fair}     &amp; \\text{&#9888;&#65038;}               &amp; \\text{Fair}\\\\\n\\end{array}&quot;,&quot;id&quot;:&quot;CXHNRWFXZG&quot;}" data-component-name="LatexBlockToDOM"></div><p>This table summarizes why there are really only two approaches worth considering. An &#10761; is a deal-breaker basically.</p><p>I ended up going with Per-Value Getters but I&#8217;ll give a brief overview of each approach with pros and cons below.</p><h3>Proxy</h3><p>Uses a JavaScript <code>Proxy</code> to create a theme object that dynamically resolves values when accessed. The proxy intercepts property lookups and returns either the light or dark value based on the current system theme.</p><blockquote><p><strong>What Is A </strong><code>Proxy</code><strong><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy"> Object</a>?</strong><br>Think of it like a wrapper object that can handle reactive side-effects. Example: <code>SessionData</code> with <code>Proxy</code>, where the <code>Proxy</code> syncs to the session cookie whenever a property of the <code>SessionData</code> object is updated.</p></blockquote><p><strong>Pros</strong>:<br>&#9989; Single event listener for entire theme<br>&#9989; Optimal memory efficiency<br>&#9989; No per-access computation</p><p><strong>Cons</strong>:<br>&#9888;&#65039; Requires wrapper function (<code>createTheme</code>)<br>&#9888;&#65039; Proxy behavior can be confusing to debug<br>&#9888;&#65039; Less granular garbage collection</p><h3>Per-Value Getters</h3><p>Each value created by <code>lightDark()</code> checks the current state of the dark mode media query.</p><p>We must be cautious with this approach because repeatedly checking the media query on every property access might be inefficient if done very frequently.</p><p>In practice, the media query check is very fast.</p><p><strong>Pros</strong>:<br>&#9989; Plain object syntax (no wrappers)<br>&#9989; Direct access to light/dark values<br>&#9989; Framework-agnostic implementation</p><p><strong>Cons</strong>:<br>&#9888;&#65039; Per-access computation cost<br>&#9888;&#65039; Complex memory management</p><h3>Value Objects</h3><p>Explicit object-oriented approach where <code>lightDark()</code> values are object literals used by a <code>Proxy</code> to toggle between modes.</p><p><strong>Pros</strong>:<br>&#9989; Optimal control and performance<br>&#9989; No hidden listeners<br>&#9989; Easy debugging</p><p><strong>Cons</strong>:<br>&#10060; No automatic theme updates<br>&#10060; Doesn't match CSS behavior<br>&#9888;&#65039; Verbose usage patterns<br>&#9888;&#65039; Manual subscription management</p><p>In a simple application this could be okay, but for me this was a no-go right off the bat since I was going for 1-1 CSS behavior and return values must be string-like.</p><h3>Event-Based</h3><p>Traditional observer pattern with manual subscription and update propagation. Maintains a centralized store that notifies subscribers when theme changes.</p><p><strong>Pros</strong>:<br>&#9989; Explicit control flow<br>&#9989; Framework-agnostic core<br>&#9989; Predictable updates</p><p><strong>Cons</strong>:<br>&#10060; Manual subscription management<br>&#10060; No string-like behavior<br>&#9888;&#65039; Memory leak potential<br>&#9888;&#65039; Update propagation complexity</p><p>This approach <em>might</em> be okay in an application already using similar patterns, like Redux or Signals. But I wanted to do this with as few peer dependencies as possible, so this approach was out.</p><h2>Why Not Use A Proxy Object?</h2><p>When I first started thinking about my solution, I initially wanted to wrap the theme in a <code>Proxy</code> object to check the dark-mode query anytime we accessed a property.</p><p>The main blocker here was that the <code>Proxy</code> knows the <code>theme</code>&#8217;s keys and values but doesn&#8217;t know how those values were instantiated.</p><p>Not every <code>theme</code> token uses <code>lightDark()</code>, and we would need a way to flag token values under the hood to know which ones do and which ones don&#8217;t.</p><p>This is where the &#8220;string-like&#8221; requirement comes from. Early on, I was thinking about using a custom symbol that the <code>Proxy</code> could look for to determine if it should check the dark-mode query.</p><p>As I played around with it, I realized that this approach was over-complicating things a bit. Instead of creating custom symbols, I realized I could streamline things with existing symbols like <code>Symbol.toPrimitive</code>.</p><p><code>Proxy</code> objects themselves can cause problems with reflection and typing because they&#8217;re not the actual objects we want, so I&#8217;m glad I was able to find a workaround.</p><h2>Without Further Ado</h2><p>The final implementation leverages JavaScript&#8217;s new(-ish) <code>FinalizationRegistry</code> (ES2021) for cleanup as well as clever primitive wrapping to create string-like theme values that auto-update when users change their system preferences.</p><h3>The Code</h3><pre><code>'use client';

let query: MediaQueryList | undefined;
let isDark = false;
let refs = 0;

const handleThemeChange = ({ matches }: MediaQueryListEvent) =&gt; {
  isDark = matches;
};

const colors = new FinalizationRegistry(() =&gt; {
  if (--refs &lt;= 0 &amp;&amp; query) {
    query.removeEventListener('change', handleThemeChange);
    query = undefined;
  }
});

export const lightDark = (light: string, dark: string) =&gt; {
  if (!query) {
    query = globalThis.matchMedia('(prefers-color-scheme: dark)');
    isDark = darkModeQuery?.matches || false;

    query?.addEventListener('change', handleThemeChange);
  }

  if (query) {
    const color = Object.assign('', {
      valueOf: () =&gt; isDark ? dark : light,
      toString: () =&gt; color.valueOf(),
      [Symbol.toPrimitive]: (hint: string) =&gt;
        hint === 'number'
          ? Number(color.valueOf())
          : color.valueOf(),
    });

    colors.register(color, null);
    refs++;
    
    return color;
  }

  return light;
};</code></pre><h3>The Demo</h3><p><a href="https://tsplay.dev/mAX7RN">Try it out here</a></p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;89144403-f656-4628-aab5-14f2c3d72c79&quot;,&quot;duration&quot;:null}"></div><h2>How It Works</h2><ul><li><p>The first <code>lightDark()</code> call initializes a single dark mode media query listener, avoiding duplicate listeners across multiple calls.</p></li><li><p><code>Object.assign('', {...})</code> fulfills our &#8220;string-like&#8221; requirement with custom versions of built-in string methods:</p><ul><li><p><code>valueOf()</code> checks the current query state and returns the right value</p></li><li><p><code>toString()</code> ensures string coercion in template strings</p></li><li><p><code>[Symbol.toPrimitive]</code> adds even better coercion for all primitives</p></li></ul></li><li><p><code>FinalizationRegistry</code> tracks when colors are garbage collected. When the last color is cleaned up, it removes the listener for the dark mode query to prevent memory leaks.</p></li><li><p>The <code>refs</code> counter ensures the media listener remains active while any theme object exists. When <code>refs</code> hits zero, resources clean up automatically.</p></li><li><p>If the function is called server-side, only the light-mode color is returned since the dark mode query will be <code>undefined</code>.</p></li></ul><h3>Updating The Theme</h3><ol><li><p>User changes system theme &#8594; browser fires a <code>change</code> event on the query</p></li><li><p><code>handleThemeChange()</code> updates <code>isDark</code> state accordingly</p></li><li><p>Subsequent <code>valueOf()</code> calls return new theme value, so all theme tokens automatically reflect the new state</p></li></ol><h2>Wrapping Up</h2><p>I&#8217;m honestly pretty amazed at how I was able to do this.</p><p>While this solution uses a more modern memory management API, I have a feeling it could be tweaked to use WeakMaps or WeakSets for better compatibility if needed.</p><p>The result is theme values that simply <em>work</em>, adapting to user preferences while maintaining perfect interoperability with existing styling paradigms.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LRLx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LRLx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 424w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 848w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 1272w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LRLx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png" width="300" height="162.12121212121212" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:428,&quot;width&quot;:792,&quot;resizeWidth&quot;:300,&quot;bytes&quot;:37166,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.robhameetman.com/i/154924704?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!LRLx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 424w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 848w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 1272w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h2>Up Next</h2><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;ff14bf83-696b-432d-9e9d-cdda96e7c161&quot;,&quot;caption&quot;:&quot;Hi, I&#8217;m Rob. This is a free issue of UI Engineering Excellence. I write for Frontend Engineers and Product Developers on how to be the best at building apps that win in the market. Subscribe if this is useful. Paid readers get early looks and occasional behind&#8209;the&#8209;scenes notes:&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Architecting Design Systems for Human Perception&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:179249864,&quot;name&quot;:&quot;Rob Hameetman&quot;,&quot;bio&quot;:&quot;I&#8217;m a Staff Design Engineer who builds sexy, scalable enterprise-strength design systems.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd162dbf-8c1b-48bc-b6e7-0969af7da7ca_475x475.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-25T22:53:00.000Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dec40623-1506-4735-bb21-e324828114e3_2464x1856.webp&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://blog.robhameetman.com/p/design-systems-for-human-perception&quot;,&quot;section_name&quot;:&quot;Deeps&quot;,&quot;video_upload_id&quot;:null,&quot;id&quot;:168566184,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;UI Engineering Excellence&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!deTI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc5e8141-3a0f-4181-8804-acdbb61e8707_359x359.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;27207310-0306-4535-8ccf-07ce4c12a597&quot;,&quot;caption&quot;:&quot;Feature teams move slower than they should. What takes a week could take a day. Simple changes somehow break unrelated things. New hires need months to become useful.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;[WIP] Building a Pit of Success with Inductive Reasoning&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:179249864,&quot;name&quot;:&quot;Rob Hameetman&quot;,&quot;bio&quot;:&quot;I&#8217;m a Staff Design Engineer who builds sexy, scalable enterprise-strength design systems.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd162dbf-8c1b-48bc-b6e7-0969af7da7ca_475x475.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-24T22:01:39.994Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!v3zD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F818fc660-922d-4795-992a-dfaeefa0d752_2464x1856.webp&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://blog.robhameetman.com/p/building-a-pit-of-success&quot;,&quot;section_name&quot;:&quot;Deeps&quot;,&quot;video_upload_id&quot;:null,&quot;id&quot;:166478385,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;UI Engineering Excellence&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!deTI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc5e8141-3a0f-4181-8804-acdbb61e8707_359x359.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a1f839c1-9ac9-4a01-b5c1-d9c43bf43918&quot;,&quot;caption&quot;:&quot;Icons are everywhere, yet they&#8217;re probably the easiest thing in your design system to screw up. I&#8217;ve seen so many <Icon> components that overcomplicate the DOM while doing nothing to make accessibility easier to achieve.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;[WIP] Accessible SVG Icons The Right Way&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:179249864,&quot;name&quot;:&quot;Rob Hameetman&quot;,&quot;bio&quot;:&quot;I build Design Systems and Micro-frontends in TypeScript and React with a focus on developer experience / Staff Frontend Engineer / TED curator / Ex-Apple (HIG) / 1.2M+ subs&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd162dbf-8c1b-48bc-b6e7-0969af7da7ca_475x475.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2025-08-24T22:17:59.979Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c54b3810-d9fa-4986-9775-77008fb03c90_2464x1856.webp&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://blog.robhameetman.com/p/accessible-svg-icons&quot;,&quot;section_name&quot;:&quot;Resources&quot;,&quot;video_upload_id&quot;:null,&quot;id&quot;:166355744,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;UI Engineering Excellence&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!deTI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc5e8141-3a0f-4181-8804-acdbb61e8707_359x359.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p>Any thoughts on what else you&#8217;d like to see? Leave a comment or hop into chat and let me know!</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/p/how-i-built-csss-light-dark-in-typescript?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.robhameetman.com/p/how-i-built-csss-light-dark-in-typescript?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><em>If you enjoyed this post, please subscribe, hit the &#10084;&#65039; button, and share/restack &#128257; it with others who might find it helpful!</em></p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How To Write A Code Mockup]]></title><description><![CDATA[What if we delivered Design System components the same way Designers create the very UI we're building?]]></description><link>https://blog.robhameetman.com/p/how-to-write-a-code-mockup</link><guid isPermaLink="false">https://blog.robhameetman.com/p/how-to-write-a-code-mockup</guid><dc:creator><![CDATA[Rob Hameetman]]></dc:creator><pubDate>Thu, 23 Jan 2025 11:33:32 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e525a0d0-6699-4e01-9dad-7c3a58d94570_2048x2048.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Hi, I&#8217;m Rob. This is a free issue of UI Engineering Excellence. I write for Frontend Engineers and Product Developers on how to be the best at building apps that win in the market. Subscribe if this is useful. Paid readers get early looks and occasional behind&#8209;the&#8209;scenes notes:</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.robhameetman.com/subscribe?"><span>Subscribe now</span></a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SmUJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SmUJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp 424w, https://substackcdn.com/image/fetch/$s_!SmUJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp 848w, https://substackcdn.com/image/fetch/$s_!SmUJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp 1272w, https://substackcdn.com/image/fetch/$s_!SmUJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SmUJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp" width="1456" height="1456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1456,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1980222,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.robhameetman.com/i/154924704?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SmUJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp 424w, https://substackcdn.com/image/fetch/$s_!SmUJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp 848w, https://substackcdn.com/image/fetch/$s_!SmUJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp 1272w, https://substackcdn.com/image/fetch/$s_!SmUJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96d1fd6c-955a-4be9-abba-a48a0b89aeb1_2048x2048.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Engineers building great design systems mockup their most impactful and valuable abstractions. They play with critical building blocks and ensure reliability before sharing them with others. And they look at how the code will be used from the perspective of an Engineer without any historical context, making adjustments along the way which create more opportunities for others to make correct assumptions.</p><p>Below is the procedural and mental framework I rolled out for doing just this when adding components to our design system at VividSeats. Our business goals require high delivery velocity for components inner-sourced by contributors across teams as part of ongoing feature work without sacrificing quality or flexibility in branding and themeability, for which our requirements and standards are about as advanced as it gets.</p><p>This process, combined with a healthy dose of scaffolding and automation, is designed to empower any Frontend Engineer at VividSeats to successfully build a design system component to our elevated standards, even without the pre-requisite specialized expertise that comes with a background in design or any focus on foundational work or design systems in previous roles.</p><div><hr></div><p><em>&#128075; Hey, it&#8217;s <a href="https://www.linkedin.com/in/rhameetman/">Rob</a>. Welcome to UI Engineering Excellence. If you enjoy this post, please hit the &#10084;&#65039; button and share/restack &#128257; it with others who might find it helpful!</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/p/how-to-write-a-code-mockup?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.robhameetman.com/p/how-to-write-a-code-mockup?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>Code Mockups For Enterprise-Strength Design System Components</h2><div><hr></div><blockquote><p>&#8505;&#65039; A <strong>code mockup</strong> is both a process and a deliverable. We start by diving deep into requirements outlined in design mockups mapping out multiple potential paths to full viability. From there, we refine and converge on a single pattern, charting a roadmap for how it can evolve as the component grows in complexity. Along the way, we adapt and adjust, ensuring the solution aligns seamlessly with our business goals.</p></blockquote><div><hr></div><h3>1. Identify Requirements &amp; Constraints</h3><ol><li><p><strong>Assess The Design Mockup (assuming we have one)</strong><br>Design mockups aren&#8217;t always a given, but when they are available, they serve as the <strong>source of truth</strong> throughout the development process. Once the component is delivered and live in production, the implementation itself takes over as the definitive reference point.</p></li><li><p><strong>Identify Design Discrepancies &amp; Constraints</strong><br>What design decisions&#8212;such as sizing, spacing, positioning, or alignment&#8212;change across different states? Are there clear boundaries or implicit constraints on how these differences can vary? Which aspects of the component need to be themeable, and which parts of the design remain stable ?</p></li></ol><div><hr></div><blockquote><p><strong>&#127919; Goal</strong>: Gain just enough understanding to confidently start mocking up code.</p></blockquote><div><hr></div><h3>2. Choose A Component Pattern</h3><p>Our UI composition leans heavily on 3 core patterns:</p><p><strong>Standalone Components</strong><br>The component functions as a self-contained unit, with its complexity scaling based on the number and type of props&#8212;think Checkbox, Text, etc.<br><em>Example:</em></p><pre><code><code>&lt;Checkbox id="1" checked={checkAll}&gt;{label1}&lt;/Checkbox&gt;
&lt;Checkbox id="2" checked={checkAll}&gt;{label2}&lt;/Checkbox&gt;</code></code></pre><div><hr></div><p><strong>Compound Components<br></strong>The component is multifaceted with specialized, often optional, subcomponents that are not reusable elsewhere.<br><em>Example:</em></p><pre><code><code>&lt;Checkbox.Group checked={checkAll}&gt;
 &lt;Checkbox&gt;{label1}&lt;/Checkbox&gt;
 &lt;Checkbox&gt;{label2}&lt;/Checkbox&gt;
&lt;/Checkbox.Group&gt;</code></code></pre><div><hr></div><p><strong>Base Components<br></strong>The component is reduced to essential presentation logic common across all variants and then used as the root node in explicit components for specialized variants.<br><em>Example:</em></p><pre><code><code>&lt;SuccessCheckbox /&gt;
&lt;ErrorCheckbox /&gt;
&lt;InfoCheckbox /&gt;
&lt;WarningCheckbox /&gt;</code></code></pre><div><hr></div><blockquote><p><strong>&#129504; K</strong><em><strong>eep In Mind: </strong>Most <a href="https://atomicdesign.bradfrost.com/chapter-2/">organism-level components, as well as template/page/view-level components</a> in the Design System, become <strong>Compound Components</strong>. These typically serve as base components for variants that live outside the Design System. On rare occasions, a compound component may also act as a base for variant components that remain within the Design System. When this happens, we refer to the pattern as a <strong>Compound-as-Base Hybrid.</strong></em></p></blockquote><div><hr></div><h3>3. Mock Up Multiple Approaches</h3><p>Just as Designers draft and explore multiple solutions, Engineers contributing to the Design System should draft at least two or three usage mockups for the chosen pattern. This allows us to evaluate how seamlessly Engineers focused on feature work can leverage what we&#8217;re building to address common design and UX challenges. Key areas we often explore include:</p><ol><li><p><strong>Pattern Adequacy</strong><br>How many patterns should we evaluate for this component? More complex components are often compound components, while simpler, atomic components usually offer more flexibility. For example, <code>&lt;Button /&gt;</code> and <code>&lt;Icon /&gt;</code> are commonly used as base components inside and outside the Design System. Should we allow their variants to emerge organically as the system evolves, or explicitly define them upfront? If explicit, which variants make the most sense to include?</p></li><li><p><strong>Design Responsibility</strong><br>How many assumptions do components, subcomponents, and variants make about styling their content? To what extent are subcomponents or variants allowed to be aware of or depend on the specifics of their content?</p></li><li><p><strong>Structural Flexibility</strong><br>How easily should we be able to rearrange or conditionally render parts of the component? What is the visual or spatial &#8220;scope&#8221; in these cases&#8212;specific content, entire sections, full tabs? Do the variants need to be reusable enough to justify creating discrete components for each?</p></li><li><p><strong>Prop Composition</strong><br>How does this component's contract influence the broader system? Does it introduce any new standardized props? If we adjust the type signature of existing standardized props to enhance shared functionality across components, will we need to revisit and update other components using the same prop to maintain the library&#8217;s integrity?</p></li></ol><div><hr></div><blockquote><p><strong>&#9757;&#65039; Tip:</strong> Keep your mockups lean and to-the-point. Prioritize usage examples and focus on how the code &#8220;feels&#8221; to read and write when achieving the desired state.</p></blockquote><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption"><em>&#10084;&#65039; Like what you&#8217;re seeing so far?</em></p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h3>4. (Optional) Mock Up Props</h3><ol><li><p><strong>List Standardized Props</strong><br>Which props from other components are reusable in this component?</p></li><li><p><strong>List Discrete Props</strong><br>Identify the props unique to this specific component. Which of these are required, and which are optional? What are their default values? Can we include any pass-through props without exposing underlying implementation details? If using a base component pattern, determine which base component props are abstracted by and omitted from the variant components.</p></li><li><p><strong>Limit Style/Design Props</strong><br>Are we helping Engineers focused on feature work make accurate assumptions? For example, if the component requires flexible width, should we provide a <code>width</code> prop for explicit, potentially complex values, or opt for a simpler <code>fullWidth</code> prop to streamline usage?</p></li><li><p><strong>Explore Multiple Techniques</strong><br>When design decisions like alignment, size, and roundedness are tightly coupled, should we simplify with a single prop or maintain flexibility by using granular props for each decision? What are the trade-offs, and how does each approach look in practice?</p></li><li><p><strong>Customize Discrete Events</strong><br>Can we make event handling more granular and reusable for common scenarios? For example, instead of accepting a generic <code>onKeyDown()</code> handler to augment functionality when the &#8220;Tab&#8221; key is pressed, could we introduce an <code>onPressTab()</code> prop that leverages a custom <code>TabEventHandler</code> to handle more specific custom <code>TabEvents</code>? <em>(Hint: You best believe we can!)</em></p></li><li><p><strong>Standardize Type Signatures</strong><br>Do we need to introduce new utility hooks or types to support newly standardized props, such as custom event handlers?</p></li></ol><div><hr></div><blockquote><p><strong>&#127919; Goal:</strong> Make it easy for any consumer (including your future self) to intuitively understand how to use this component and confidently make accurate assumptions, even when facing gaps in technical expertise or domain knowledge.</p></blockquote><div><hr></div><h3>5. (Optional) Mock Up Reusable Utils</h3><ol><li><p><strong>Identify or Create Helper Hooks, Utilities, or Types</strong><br>e.g. <code>useVerticalAlignment()</code>, <code>useQueryParamState()</code>, <code>EscapeEvent/EscapeEventHandler/useEscapeEvents()</code>, or defining notable constants and enums.</p></li><li><p><strong>Centralize Reusable Utils<br></strong>Consolidate logic or styling transformations to streamline the process of building Design System components and reduce duplication.</p></li><li><p><strong>Explore Multiple Options (As Needed)</strong></p><p>For particularly large or complex enums, would breaking them into smaller pieces improve usability? How should functional hooks, like those for filtering and sorting, be composed to handle overlapping or chained behaviors effectively? Consider the trade-offs and implementation details for these approaches.</p></li></ol><div><hr></div><blockquote><p><strong>&#127919; Goal</strong>: Keep the code DRY and self-documenting while avoiding duplicated logic in each component.</p></blockquote><div><hr></div><h3>6. (Required) Review With Maintainers</h3><ol><li><p><strong>Share Final Mockup / Chosen Approach</strong></p><p>Present usage examples to the Design System group. If the work will be carried out by another Engineer, ensure they&#8217;re involved in the review process.</p></li><li><p><strong>Gather Feedback</strong></p><p>Verify that the proposed implementation adheres to Design System guidelines, naming conventions, WCAG compliance, and other key standards.</p></li><li><p><strong>Discuss Future Use</strong></p><p>Consider whether the pattern or component could be valuable for other teams or projects. Explore how distribution logic might evolve as the business&#8217;s brand composition changes.</p></li><li><p><strong>Achieve Consensus</strong></p><p>Secure approval from the Design System maintainers and move forward with development.</p></li></ol><div><hr></div><blockquote><p><strong>&#127919; Goal</strong>: Validate the approach and maintain consistency with system-wide standards.</p></blockquote><div><hr></div><h3>7. Make Adjustments</h3><ol><li><p><strong>Tweak the Pattern(s)</strong></p><p>Make minor adjustments as unforeseen challenges arise.</p></li><li><p><strong>Strategically Plan Rewrites</strong></p><p>Identify when a shift from one pattern to another might be necessary. If the component becomes overly complex, should it be broken into smaller pieces or rewritten using a more sophisticated pattern?</p></li><li><p><strong>Add Snippets in Storybook</strong></p><p>Create a Storybook story for each design/state variation from the design mockup. Ensure the resulting code snippets precisely match the code mockup for each variant.</p></li></ol><div><hr></div><blockquote><p><strong>&#127919; Goal</strong>: Deliver a polished, thoughtfully refined implementation, ready for actual build or integration.</p></blockquote><div><hr></div><h3>Summary</h3><ol><li><p><strong>Identify Requirements &amp; Constraints</strong></p></li><li><p><strong>Choose a Component Pattern</strong></p></li><li><p><strong>Mock Up Multiple Approaches</strong></p></li><li><p><strong>(Optional) Mock Up Props</strong></p></li><li><p><strong>(Optional) Mock Up Reusable Utils</strong></p></li><li><p><strong>(Required) Review With Maintainers</strong></p></li><li><p><strong>Make Adjustments</strong></p></li></ol><div><hr></div><p><em>&#128075; <a href="https://www.linkedin.com/in/rhameetman/">Connect with me on LinkedIn.</a></em></p><p><em>PS: If you&#8217;re enjoying UI Engineering Excellence, hitting the &#10084;&#65039; button and sharing/restacking &#128257; goes a long way in helping me grow the publication. Please take a moment to help spread the word. Thank you!</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/p/how-to-write-a-code-mockup?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.robhameetman.com/p/how-to-write-a-code-mockup?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LRLx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LRLx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 424w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 848w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 1272w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LRLx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png" width="300" height="162.12121212121212" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:428,&quot;width&quot;:792,&quot;resizeWidth&quot;:300,&quot;bytes&quot;:37166,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.robhameetman.com/i/154924704?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LRLx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 424w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 848w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 1272w, https://substackcdn.com/image/fetch/$s_!LRLx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b509d7-f748-4f7e-92db-a8ed3d1f3352_792x428.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p>]]></content:encoded></item><item><title><![CDATA[[TEMPLATE] Recap: [BLOG] in [YEAR]]]></title><description><![CDATA[The year&#8217;s most-read articles, some personal favorites, and a look back at a busy year in the design tech]]></description><link>https://blog.robhameetman.com/p/template-recap-blog-in-year</link><guid isPermaLink="false">https://blog.robhameetman.com/p/template-recap-blog-in-year</guid><dc:creator><![CDATA[Rob Hameetman]]></dc:creator><pubDate>Wed, 01 Jan 2025 06:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gT76!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Hi, I&#8217;m Rob. This is a free issue of [BLOG]. I write for [AUDIENCE] on how to [GOAL TO ACHIEVE]. Subscribe if this is useful. Paid readers get early looks and occasional behind&#8209;the&#8209;scenes notes:</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.robhameetman.com/subscribe?"><span>Subscribe now</span></a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gT76!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gT76!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp 424w, https://substackcdn.com/image/fetch/$s_!gT76!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp 848w, https://substackcdn.com/image/fetch/$s_!gT76!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp 1272w, https://substackcdn.com/image/fetch/$s_!gT76!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gT76!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp" width="1456" height="1097" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1097,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:895966,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.robhameetman.com/i/171158859?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gT76!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp 424w, https://substackcdn.com/image/fetch/$s_!gT76!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp 848w, https://substackcdn.com/image/fetch/$s_!gT76!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp 1272w, https://substackcdn.com/image/fetch/$s_!gT76!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fab53780c-e9a1-4c60-aa0a-7ec9819d167b_1600x1205.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Edit blog/covers/annual.psd to change the BLOG and update the year. Save as WebP (Lossless) with EXIF data and upload. Delete this caption if necessary.</figcaption></figure></div><p>As [YEAR]&#8230;</p>
      <p>
          <a href="https://blog.robhameetman.com/p/template-recap-blog-in-year">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[[TEMPLATE] New Post]]></title><description><![CDATA[A template for faster outlines and consistent content strategy.]]></description><link>https://blog.robhameetman.com/p/template-post</link><guid isPermaLink="false">https://blog.robhameetman.com/p/template-post</guid><dc:creator><![CDATA[Rob Hameetman]]></dc:creator><pubDate>Wed, 01 Jan 2025 06:00:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!YI39!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Hi, I&#8217;m Rob. This is a free issue of UI Engineering Excellence. I write for Frontend Engineers and Product Developers on how to be the best at building apps that win in the market. Subscribe if this is useful. Paid readers get early looks and occasional behind&#8209;the&#8209;scenes notes:</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.robhameetman.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.robhameetman.com/subscribe?"><span>Subscribe now</span></a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YI39!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YI39!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp 424w, https://substackcdn.com/image/fetch/$s_!YI39!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp 848w, https://substackcdn.com/image/fetch/$s_!YI39!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp 1272w, https://substackcdn.com/image/fetch/$s_!YI39!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YI39!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp" width="1456" height="1097" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1097,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:93196,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.robhameetman.com/i/171842138?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YI39!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp 424w, https://substackcdn.com/image/fetch/$s_!YI39!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp 848w, https://substackcdn.com/image/fetch/$s_!YI39!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp 1272w, https://substackcdn.com/image/fetch/$s_!YI39!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a0b662a-fade-413d-b0ca-7615df49aec0_2464x1856.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Edit blog cover and Save As WebP (Lossless) with EXIF data and upload. Make s&#8230;</figcaption></figure></div>
      <p>
          <a href="https://blog.robhameetman.com/p/template-post">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>