<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Tony Ward</title>
        <link>https://tonyward.dev</link>
        <description>I enjoy writing about Design Systems and Software Engineering.</description>
        <lastBuildDate>Mon, 08 Jun 2026 12:42:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Tony Ward</title>
            <url>https://tonyward.dev/icon.png</url>
            <link>https://tonyward.dev</link>
        </image>
        <copyright>All rights reserved 2026</copyright>
        <item>
            <title><![CDATA[Balancing Stability and Innovation in Design Systems]]></title>
            <link>https://tonyward.dev/articles/stability-and-innovation</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/stability-and-innovation</guid>
            <pubDate>Wed, 18 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Design Systems are <a href="https://bradfrost.com/blog/link/ship-faster-by-building-design-systems-slower-big-medium/" target="_blank" rel="noopener noreferrer">critical infrastructure</a>. They should be taken seriously.  I like having a conversation about each Design System change to ensure it fits with the overall vision–while also making sure it won't break production for our customers. I like pushing for 100% test coverage. I like having visual regression testing. These things help ensure your critical infrastructure is well tested–you'll be more confident in your changes with these tools and processes in place.</p>
<p>But while Design Systems <a href="https://bigmedium.com/ideas/design-system-pace-layers-slow-fast.html" target="_blank" rel="noopener noreferrer">should move slow</a> and be intentional about every <em>external</em> change, there's also room for <em>internal innovation</em>. Internal innovation can happen much faster than shipping features, as the feedback loop is much tighter–it's immediately affecting your internal Design System team. Internal innovation in a Design System is a great way to prove out ideas before deciding to ship them more broadly.</p>
<p>We're system thinkers. We're good at generalizing things and thinking about how to make something general-purpose. So this type of work fits really well with Design System folks' skillsets, in my opinion.</p>
<p>What exactly do I mean by "internal innovation"? Anything that would speed up your Design System team to ship features. Or something that'd help improve the system, increase accessibility, or raise/ensure the quality bar. And bonus points if it's helpful and could be generalized for teams <em>outside</em> of your group. You may also find that opening the door for this leads to benefits to your users as well.</p>
<a id="example-filling-gaps" href="#example-filling-gaps"><h2>Example: Filling gaps</h2></a>
<p>Glide Core is archived on GitHub, but you can still view the code. We decided to <a href="https://github.com/CrowdStrike/glide-core/blob/main/src/library/required.ts" target="_blank" rel="noopener noreferrer">create</a> a <code><span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">required</span></span></code> decorator to enforce Web Component attributes (or properties) to be required. This is something that Lit doesn't provide out of the box (for good reason).</p>
<p>We wanted to ensure our form elements always have a label for accessibility reasons. Using the <code><span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">required</span></span></code> decorator allowed us to enforce that.</p>
<p>This is an example of something that starts in the Design System, but could be generally helpful for almost anyone using Lit. And once again–it's good for accessibility, which does impact users.</p>
<a id="example-shared-eslint-rules" href="#example-shared-eslint-rules"><h2>Example: Shared ESLint rules</h2></a>
<p>I recommend <a href="/articles/driving-consistency-with-eslint-rules">writing your own ESLint rules</a>. There are a ton of advantages. These lint rules may begin in your Design System, but may be helpful generally to others as well. Just like the decorator above.</p>
<p>ESLint rules may help nit-picks on PRs. If a robot can automate opinions, it's less time spent in a PR review going back and forth on something.</p>
<a id="example-new-tooling" href="#example-new-tooling"><h2>Example: New tooling</h2></a>
<p>The web development ecosystem changes quickly. New tools land. Some of them are great, others are noise–that's another topic. But say you want to switch to a different test runner internally that'll give some benefit to your team. Doing this internally is a great way to prove it out, without negative impact on your users.</p>
<p>For example, say you're on jest. Switching to Playwright would mean you could run your tests in a real browser. You could configure visual regression testing. You could make use of their accessibility testing. This type of change also has an impact on end users–they benefit from a more stable and reliable product. +1 to the "critical infrastructure" comment.</p>
<p>Additionally, making this switch may give you confidence to propose this change more broadly across the organization.</p>
<blockquote>
<p>"Switching to Playwright meant we improved our quality by testing in real browsers and interacting with the elements in tests just like our users. We also took advantage of visual regression and accessibility testing.".</p>
</blockquote>
<p>Sounds pretty cool to me.</p>
<a id="example-replacing-dependencies" href="#example-replacing-dependencies"><h2>Example: Replacing dependencies</h2></a>
<p>Using a third-party package allows you to use something off the shelf and get going. But not every package solves your exact needs. Some may no longer be maintained. Ideally you can contribute to open source software to help make the world better for everyone. But if not, building internal tools that replace unnecessary dependencies means less external dependencies and less risk. Especially with the recent supply chain attacks on npm packages.</p>
<p>Lowering our dependence on third-party packages is a good thing, in my opinion. Browser features keep getting better. Just use the platform (where you can), bro, please.</p>
<a id="you-get-the-idea" href="#you-get-the-idea"><h2>You get the idea</h2></a>
<p>Above are some examples that came immediately to mind. But hopefully you get the idea. How can you retain the quality and stability our customers expect with a Design System being critical infrastructure while also keeping up with the times and potentially making life better for your team? For your users? For other engineers in your organization? I think this is how. Embrace a model of internal innovation, while not negatively impacting the Design System externally.</p>
<p>Take a look at your Design System. How's your team doing? Do you feel like there are things the Design System could innovate on to improve processes for your team? Are there opportunities to improve design consistency or testing practices? Are there things currently private to the Design System that could be generally used and helpful to others?</p>
<p>Spending time on innovation keeps the train moving. It may also scratch the "I have an idea itch" for some of your engineers. While some of these things impact the Design System team only (which is still good!), it could lead to great benefits to your Design System consumers. And even your product's users. And that's cool.</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Design Systems and AI]]></title>
            <link>https://tonyward.dev/articles/design-systems-and-ai</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/design-systems-and-ai</guid>
            <pubDate>Tue, 14 Oct 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Design Systems and AI have been on my mind all year. I thought I'd jot down some of my general thoughts on how to best serve Design System consumers by leveraging AI. This is something I'm experimenting with and will report back on as I learn more.</p>
<p>My ideal scenario for product engineers is to be able to go into Figma and copy the frame URL for a piece of UI that was built in Figma using the Design System. They'd paste the URL into an AI tool. The AI tool would have all of the context of the Design System – it'll know about all of the components and how to use them, it'll know about the variables, have access to all of the documentation, etc. – from there, the tool will generate the provided UI for the engineer.</p>
<p>It obviously won't be <em>all</em> of the code – it'll handle the visual building blocks though, which saves a bunch of time. Then the product engineers can worry about the "glue code". They'll handle data fetching, conditionally rendering certain things, and other scenarios. But they'll have a <em>great</em> starting point to spring off from. My thinking is that this would help accelerate development velocity and expand the usage of the existing Design System.</p>
<a id="feed-figma-context" href="#feed-figma-context"><h2>Feed Figma Context</h2></a>
<p>The first thing that comes to mind is feeding Figma as much information as possible. I've <a href="/articles/figma-dev-resources">written</a> about Figma Dev Resources before - this allows you to associate links to Figma components that show up in Dev Mode. It's helpful because it gives developers additional information.</p>
<p>The next logical step is to use Figma's code syntax feature for variables. All of those Figma variables you have? <a href="/articles/figma-variables-to-css-variables">Convert them to CSS variables</a>. Then write them back into Figma so that they show up in Dev Mode as the actual CSS variables you've generated. This way, when a product engineer is viewing designs, they can see the exact CSS variable names rather than the Figma variable name – these are normally slightly different due to syntax difference.</p>
<p>Lastly, wire up <a href="https://www.figma.com/blog/introducing-code-connect/" target="_blank" rel="noopener noreferrer">Figma Code Connect</a>. I'm working with Web Components. So my plan is to <a href="https://jamesiv.es/blog/frontend/javascript/2025/09/21/custom-elements-manifest-and-figma-code-connect" target="_blank" rel="noopener noreferrer">use the Custom Elements Manifest</a> to automate this. But no matter how you get Code Connect wired up, having these snippets available will save product engineers time. They can literally copy and paste snippets right into their editor for the component(s) in the exact designed state.</p>
<a id="figma-mcp" href="#figma-mcp"><h2>Figma MCP</h2></a>
<p>So now you're probably wondering "now what?". Figma Dev Resources, leveraging code syntax for variables, and Code Connect are all great. But what did that do for us exactly? Not only does this help Dev Mode and provide resources for your product engineers, feeding Figma all of this information gave it a lot of context. If you've been working with AI lately, you know how important context is for LLMs.</p>
<p>The next logical step is using Figma's MCP server. It's currently in beta.</p>
<p>Now that you've provided Figma all of this context on your Design System, I think we're <em>really</em> close to making my ideal scenario a reality with the MCP server. Time will tell, but I hope it'll do well. In theory, it should have a big chunk of the information it needs.  Now of course, it may not gracefully handle cases that <em>aren't</em> using your Design System.  But if it can get folks 70-80% of the way there, that's a huge win. This could go a long way in helping designers and developers.</p>
<a id="design-system-mcp" href="#design-system-mcp"><h2>Design System MCP</h2></a>
<p>What else could we automate and leverage AI for?</p>
<p>I'm thinking you may be able to create your own MCP server with a distinctly different purpose from Figma's. The role of a new MCP server wouldn't be translating the designs to code, but rather focusing on component usage and best practices. Going back to the Custom Elements Manifest, it could read from it and have all of the information it needs on the component APIs. It would know the attributes, properties, methods, etc., that are available. If you're not using a Custom Elements Manifest, there are other ways to get this information about your components (especially with React + TypeScript).</p>
<p>Now add on your Design System documentation on top of that. Best practices, when to use one component over another. All of this is additional context that would help it make decisions and increase confidence.</p>
<p>Folks are already using tools like Claude Code to build UIs. With this MCP server, my thinking is you could promote usage of your Design System. Rather than have it generate custom components from scratch, use what is already available, following the patterns that have already been defined.</p>
<p>Additionally, my hope is that providing an MCP server with this information would cut down on support requests. Fielding Design System support questions is really important. As a Design System engineer, we want to ensure our consumers get <em>correct</em> answers in a timely manner. If you can ask your AI tool instead, it very well may save the Design System team some time. And by providing it as much context as possible, hopefully the answer would be more correct and it'd cut down on hallucinations.</p>
<a id="we-ll-see" href="#we-ll-see"><h2>We'll See</h2></a>
<p>I can see the benefits of feeding LLMs with as much context about your Design System as possible. Not only may it increase velocity, I hope it'll also improve consistency in applications built and cut down on Design System support.</p>
<p>Overall, this is all just me rambling and some general thoughts I've been thinking about all year. I have no idea in reality if it'll work at all! But to me, that's the fun part. It's time to go find out and report back.</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Design Systems Education: Web Components]]></title>
            <link>https://tonyward.dev/articles/design-systems-education-web-components</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/design-systems-education-web-components</guid>
            <pubDate>Mon, 01 Sep 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I'm a big fan of the Design System Office Hours podcast from Davy and PJ. I recently listened to their <a href="https://officehours.systems/episodes/design-systems-education" target="_blank" rel="noopener noreferrer">Design Systems Education</a> episode.</p>
<blockquote>
<p><em>"Davy and PJ discuss the challenges and strategies for design system training, especially when it comes to Figma. We explore the line between providing direct tool training and focusing on how the design system integrates with those tools."</em></p>
</blockquote>
<p>While I was on a walk listening, I kept nodding along. All of it is so relatable. But in particular I can relate to the education piece falling onto the Design System team.</p>
<p>I, too, <em>really</em> enjoy recording videos for training. But as they mentioned, it's annoying how tools and features change in a short amount of time. After a few months, your content is suddenly outdated. What a bummer.</p>
<p>Then I got to thinking about the engineering side of this problem. One <strong>huge</strong> advantage of building your Design System with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components" target="_blank" rel="noopener noreferrer">Web Components</a> – and to be clear, I'm talking the built-in-the-browser Web Components – is that browsers don't have a lot of churn. Less frequent changes would mean not needing to re-record videos every few months. That sure sounds nice!</p>
<a id="low-and-slow" href="#low-and-slow"><h2>Low and Slow</h2></a>
<p>And while this <a href="https://bsky.app/profile/davatron5000.bsky.social/post/3lvydfnaddk2v" target="_blank" rel="noopener noreferrer">Bluesky post</a> from Dave Rupert was about a different topic, he uses a phrase I'll now use forever:</p>
<blockquote>
<p><em>If you’ve heard me talk about #webcomponents, you’ve probably heard me talk about how improvements and changes are slow… slow like brisket. Low heat applied over a long time (in this case 9mo after issue identified) but now all boats float and the platform we all build on got better.</em></p>
</blockquote>
<p>mmmmm brisket (literally took a brisket cooking class a month ago, still thinking about it).</p>
<p>Back on track – I do believe Web Components with Design Systems are a bit of an insurance policy overall. And when it comes to the <em>education</em> piece of Design Systems, it's even more insurance. Core browser technology is cooked low and slow, like a brisket. You're guaranteed a great amount of stability over time. And as the browsers get better, guess what? So does your stuff!</p>
<p>Compare that to the churn you get when you pick something like React. I'm not saying React is "bad", of course, it's just the reality. Frameworks move at a much faster pace, which is what some organizations want/need.</p>
<p><a href="https://blog.jim-nielsen.com/2025/sit-on-your-ass-web-dev/" target="_blank" rel="noopener noreferrer">This article</a> from Jim Nielsen is along similar lines, in my mind, to this topic.</p>
<a id="learn-the-platform" href="#learn-the-platform"><h2>Learn the Platform</h2></a>
<p>Learn more about the platform and use it as much as you can. It'll make you a better developer. Not only is it great for your Design System, it's also good for your career.</p>
<p>So if your Design System is built on "the platform", your education content won't be outdated as quickly. Sure, there will be new ways to do things, but the core fundamentals will be the same. And that has a lot of advantages. I'll report back in another year or two, as I'm currently setting up education content for our Web Component Design System, Glide.</p>
<p>If you'd like to learn more about why I think Web Components are great for Design Systems, <a href="/articles/web-components-design-system">I have an article for you</a>. Or if you want a quick <a href="/articles/web-components-for-framework-devs">intro to Web Components</a>, I've got that too. And as always, if you want to chat about Design Systems or Web Components, Bluesky or LinkedIn is a great way to get a hold of me.</p>
<p>Anyway, that's all I've got. See ya next time!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[A New Way to Audit Design System Usage]]></title>
            <link>https://tonyward.dev/articles/a-new-way-to-audit-usage</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/a-new-way-to-audit-usage</guid>
            <pubDate>Mon, 11 Aug 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>My wife, <a href="https://www.linkedin.com/in/haley-ward/" target="_blank" rel="noopener noreferrer">Haley Ward</a>, and I went to a Design Systems conference called <a href="https://www.knapsack.cloud/patterns" target="_blank" rel="noopener noreferrer">Knapsack Patterns</a> a few months ago. Someone mentioned <a href="https://www.designsystemscollective.com/measuring-design-system-adoption-building-a-visual-coverage-analyzer-b5d9ae410d42?gi=28215cef8272" target="_blank" rel="noopener noreferrer">Maya Hampton's article</a> on a tool that highlights all of their Design System components on the page. I thought this was an excellent idea.</p>
<a id="why-this-matters" href="#why-this-matters"><h2>Why This Matters</h2></a>
<p>The words "Design System" and "adoption" in a conversation with higher-ups are inevitable. It's important in organizations where you really have to sell the value of a Design System. It makes sense they care about metrics and adoption – they want to ensure their investment is paying off.</p>
<p>And while I do think tracking metrics is good, I don't think it tells the full story. In my personal opinion, it's better to measure adoption <strong>visually</strong> and realistically – by going through your product and explicitly calling out the components being used. A "large number" of usage is great, but what if those pages aren't used frequently? Or what if they're "internal" pages?</p>
<p>Maybe these cases still speed up design and development. But I want to ensure our Design System components are front and center with our customers. That the organization is getting the most bang for their buck. And this idea of highlighting all components on a page help you tell your story on why the investment is worth it.</p>
<p>Rather than tallying up usages, you can show something more tangible.</p>
<div><div><img alt="An image showing Design System components highlighted on a page of an app." loading="lazy" width="600" height="400" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 160'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/wAARCAAEAAgDAREAAhEBAxEB/9sAQwAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47/9sAQwEKCwsODQ4cEBAcOygiKDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDz6fMczEHpkVTXQu9pXP/Z'/%3E%3C/svg%3E&quot;)" src="/_next/static/media/highlight-example.1y07dca6gfarm.jpg"></div></div>
<blockquote>
<p>"See this page? All components highlighted with a red border are our Design System components. We estimate using our components saved $x.xx/hour, etc., etc.,"</p>
</blockquote>
<p>Additionally, as a visual person, I'd rather <em>see</em> the usage in the UI rather than look at a raw number. It's more meaningful that way. And many others feel the same way, I imagine. So go highlight them!</p>
<p><em>(I use the word "highlighting", but you can really do whatever. I've used <code><span><span style="color:var(--sh-identifier)">outline</span></span></code> or <code><span><span style="color:var(--sh-identifier)">border</span></span></code> CSS properties to do this with success. There are probably other things you can do too!)</em></p>
<a id="browser-extension" href="#browser-extension"><h2>Browser Extension</h2></a>
<p>One improvement that comes to mind is turning this into a Chrome extension. Pull up a page, click the extension, and voila – see all of your components highlighted.</p>
<p>The advantages of this are that any non-technical person can use this tool to perform an audit. Normal coding methods require a developer. But not this! At least after it's "completed".</p>
<p>The disadvantage is similar to it being just a script or a development-only tool – it's time consuming. Someone has to know how to get to every single page. I'm a developer, I want to automate this!</p>
<a id="tie-into-existing-tests" href="#tie-into-existing-tests"><h2>Tie Into Existing Tests</h2></a>
<p>Because Haley and I are married and both in Design Systems, we started brainstorming how we could take it one step further. Then we landed on something I quite like – tie this functionality into your existing end-to-end tests.</p>
<p>Most end-to-end tests are already navigating through your application. Maybe even going to very specific pages that may be difficult to reach, depending on the data or path to get there.</p>
<p>As you hit areas you care about, create a custom function that will:</p>
<ul>
<li>Highlight all Design System components on a page</li>
<li><a href="https://playwright.dev/docs/test-snapshots" target="_blank" rel="noopener noreferrer">Take a screenshot</a></li>
<li>Turn off the highlighting</li>
</ul>
<p>Store all of these images in a directory. Then you can provide a "report" of sorts to anyone who wants to know about Design System usage.</p>
<p>By tying in to your existing end-to-end tests, it means you get this auditing <em>almost</em>
"for free". I dig it.</p>
<a id="now-what?" href="#now-what?"><h2>Now What?</h2></a>
<p>After you get some of this tooling in place, you'll have a way to check which parts of the application(s) are using your Design System.</p>
<p>Designers won't have to guess if they see a component that <em>looks</em> like a Design System component, but isn't quite right. If it doesn't have a red border, it's hand-rolled! Go chat with the team about why.</p>
<p>You can reason about costs easier – "this non-Design System component is estimated to cost $x.xx in maintanence, but if we switch to the Design System component that goes away". And on the flip side, as mentioned above, you can estimate cost savings of building new features and applications.</p>
<p>It also opens the door to notice patterns in your applications. Just like going to every page manually is time consuming, it's also difficult to spot patterns by popping around the application yourself. If you have an image of <em>every</em> part of the application handy, although it's a lot to sort through, you could more easily recognize patterns by flipping through everything.  This is probably better done in Figma using designs, but sometimes pages exist in code that don't in design. So this gives you the full view – code is the source of truth and what customers interact with at the end of the day, not your designs.</p>
<p>Overall, I think this goes a long way and could help teams track and measure adoption in a more meaningful way than "numbers should go up!". Let me know what you think!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Syncing Figma Dev Resources]]></title>
            <link>https://tonyward.dev/articles/figma-dev-resources</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/figma-dev-resources</guid>
            <pubDate>Thu, 29 May 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Developers being unsure of what components in their Figma designs are from the Design System is a common issue I've seen across multiple organizations. They often ask "What <em>coded</em> component is this?". This is where I think Dev Mode is valuable.</p>
<p>I've noticed product developers rarely use Dev Mode to its full potential. Many don't read <a href="https://help.figma.com/hc/en-us/articles/15023124644247-Guide-to-Dev-Mode" target="_blank" rel="noopener noreferrer">the docs</a>, as they're more concerned with building their feature on a tight deadline. I feel for them! They may not know how to tell which UI Kit a Figma component comes from or how to go to the main instance of a component. Their skillset is writing code and most have only surface-level knowledge of how to use Figma. Once again, I totally feel for them and understand why that is.</p>
<p>These questions continue to come up, across multiple organizations and engineering levels:</p>
<blockquote>
<p><em>"Where does this component come from?"</em></p>
</blockquote>
<blockquote>
<p><em>"Is this component already built?"</em></p>
</blockquote>
<blockquote>
<p><em>"Do I need to build this myself?"</em></p>
</blockquote>
<blockquote>
<p><em>"Is this a Design System component?"</em></p>
</blockquote>
<blockquote>
<p><em>"Is there documentation or links to where I can find/use these components in code?"</em></p>
</blockquote>
<p>This is where I've been experimenting with <a href="https://help.figma.com/hc/en-us/articles/15023231995927-Link-Dev-resources-to-layers-in-Dev-Mode" target="_blank" rel="noopener noreferrer">Figma dev resources</a>. They provide a great way to add supplemental links to your Figma components to help answer these questions. Not only are they useful on their own, but they also encourage developers to use Dev Mode, which can help them learn more about its features. Which in turn, will likely improve the design and developer process and communication — that's the goal, at least. Especially if your organization is already paying for Dev Mode seats. May as well put them to good use!</p>
<p>With dev resources in place, developers can view your designs in Dev Mode and click through the components. When they come across a component with available resources, they'll see the links in the Dev Mode panel. The panel gives them more information and <em>resources</em> around implementation. I find dev resources especially helpful for Design System components, showing how to assemble smaller building blocks together to build larger parts of the UI.</p>
<a id="the-source-of-truth-is-code" href="#the-source-of-truth-is-code"><h2>The Source of Truth is Code</h2></a>
<p>You can manually add dev resources to any component via Figma's UI, but I believe making code the source of truth and using their API is a slightly better process. Admittedly, both paths are laborious and time consuming, but once it's all in place, your consumers will appreciate them. And will hopefully be low maintenance after the initial investment.</p>
<p>But having them defined in code means that when changes are made to a component via a pull request, the developer can also update the dev resources that would be helpful to Design System consumers in the same PR. It means designers aren't manually adding resources to components without developers knowing and gives Design System developers more control and insight on how to best fulfill the needs of other developers using the system.</p>
<p>So how do we write these resources to Figma? Their API.</p>
<a id="figma-s-api" href="#figma-s-api"><h2>Figma's API</h2></a>
<p>Figma has great <a href="https://www.figma.com/developers/api#dev-resources" target="_blank" rel="noopener noreferrer">documentation</a> on their <code><span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">dev</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">resources</span></span></code> endpoint.</p>
<p>By keeping code as the source of truth, it means we can programmatically create, update, and delete resources. You can check out <a href="https://github.com/CrowdStrike/glide-core/blob/main/src/figma/dev-resources/run.ts" target="_blank" rel="noopener noreferrer">the code</a> I wrote for this process.</p>
<p>But here's the gist:</p>
<ul>
<li>Fetch all existing resources from Figma's API.</li>
<li>Keep a list of resources in code associating the Figma node ID to the links we want associated to that node/component.<!-- -->
<ul>
<li>We link to Storybook and the component's source code in GitHub.</li>
</ul>
</li>
<li>Using the local list of resources as our source of truth, compare against the ones returned from Figma's API. Bucket them into new resources, resources to update, and resources to delete.</li>
<li>Hit Figma's endpoints (<code><span><span style="color:var(--sh-class)">POST</span></span></code>, <code><span><span style="color:var(--sh-class)">PUT</span></span></code>, <code><span><span style="color:var(--sh-class)">DELETE</span></span></code>) with these bucketed items.<!-- -->
<ul>
<li>Create (<code><span><span style="color:var(--sh-class)">POST</span></span></code>) and Update (<code><span><span style="color:var(--sh-class)">PUT</span></span></code>) allow for batching node IDs, but deleting does not. The link I have above has some delays to ensure you don't get rate limited. If this happens, you'll get a 429 response status code and have to wait a while before making more delete requests.</li>
</ul>
</li>
</ul>
<a id="design-development" href="#design-development"><h2>Design 🤝 Development</h2></a>
<p>With dev resources in place, your consumers can use Dev Mode to get more information about the Design System components and how to use them in code via your documentation. It'll help ease the design to development handoff, by providing more information to your developers.</p>
<p>Wiring up Code Connect is up next for me. It's a bigger fish to fry. So sit tight and I'll share my experiences there as well!</p>
<blockquote>
<p>Want to sync more things between Figma and code? Check out my <a href="https://www.tonyward.dev/articles/figma-variables-to-css-variables" target="_blank" rel="noopener noreferrer">syncing Figma variables to CSS variables</a> article.</p>
</blockquote>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Disrupting Visual Regression Testing]]></title>
            <link>https://tonyward.dev/articles/visual-regression-testing-disruption-2</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/visual-regression-testing-disruption-2</guid>
            <pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A few months ago I <a href="https://www.tonyward.dev/articles/visual-regression-testing-disruption" target="_blank" rel="noopener noreferrer">wrote</a> how visual regression testing is ripe for some disruption. It's clunky, the paid offerings are very expensive, and there are cross-OS issues. On top of that, not a lot of folks have documented ideas on how to solve these issues without using something like Docker.</p>
<p>At the end of that article, I floated an idea <a href="https://www.linkedin.com/in/haley-ward/" target="_blank" rel="noopener noreferrer">Haley Ward</a> and I had cooked up. A few days later she <a href="https://medium.com/@haleywardo/streamlining-playwright-visual-regression-testing-with-github-actions-e077fd33c27c" target="_blank" rel="noopener noreferrer">wrote some GitHub Actions</a> to prove it out. I'm happy to report back that it's generally working as expected.</p>
<p>But I'm also here to write about another solution. One that my team adopted over in <a href="https://github.com/CrowdStrike/glide-core" target="_blank" rel="noopener noreferrer">Glide Core</a>, the Web Component Design System from CrowdStrike. All thanks to <a href="https://github.com/clintcs" target="_blank" rel="noopener noreferrer">clintcs</a> for working through all of this and implementing it, it's really nice!</p>
<a id="the-problem" href="#the-problem"><h2>The Problem</h2></a>
<p>As a quick recap, the main issue around Playwright's visual regression testing is cross-OS compatibility. For example, you're developing on a Mac, CI is in Linux. When you check in your images and let CI run, there will be a pixel diff leading to failing tests—even if there were no visual changes.</p>
<p>You could adjust the pixel diff threshold to account for this, but this could mean you're missing out on <em>real changes</em> you actually want to be notified about. When it comes to Design System work in particular, this is crucial. Feel free to read my other article for more information if you're curious.</p>
<a id="the-solution" href="#the-solution"><h2>The Solution</h2></a>
<p>Similar to Haley's solution, CI has to be the source of truth. You can still run the Playwright tests locally, but <strong>don't check in any of the images it generates</strong>. We push everything over to CI.</p>
<p>This process requires access to a storage mechanism for the images Playwright generates. AWS, Cloudflare, or any other provider will do. We use Cloudflare for open source projects, so I'll be using/saying R2 from here on out.</p>
<p>We also deploy Playwright's test report and add it as a comment on the PR. This helps with our review process. A reviewer of a PR will review the code, view the changes in Storybook, and review the visual test report.</p>
<div><div><img alt="An image of a comment on a GitHub Pull Request displaying a warning sign and a URL to review a test report" loading="lazy" width="1612" height="336" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/visual-test-report.0l2u7jfyxw14y.png"></div></div>
<p>From a high-level, here is how we solved the visual testing problems described in the other article:</p>
<ul>
<li>Store baseline images from Playwright in R2.</li>
<li>On PR creation, fetch the baseline images before running your tests.</li>
<li>Run your visual tests.<!-- -->
<ul>
<li>Because your baseline was established from R2, the PR opened will compare against the baseline on <code><span><span style="color:var(--sh-identifier)">main</span></span></code>.</li>
<li>We leverage <a href="https://playwright.dev/docs/test-sharding" target="_blank" rel="noopener noreferrer">sharding</a> to speed up the process.</li>
</ul>
</li>
<li>Deploy the test report (optional).</li>
<li>Approving the PR is approving the visual updates that come with that PR.<!-- -->
<ul>
<li>Rather than having an explicit "I approve of these visual updates" on top of approving the PR itself, Clint had the great thought that approving the PR means approving everything in that PR - the code, any visual updates, all of it. This greatly simplifies things.</li>
</ul>
</li>
<li>On PR merge to <code><span><span style="color:var(--sh-identifier)">main</span></span></code>, update the baseline images in R2 to reflect these changes.</li>
</ul>
<p>We added a <a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue" target="_blank" rel="noopener noreferrer">merge queue</a> to prevent issues with multiple PRs going in at the same time. Without a merge queue, you could clobber the baseline images by mixing up the order of when PRs merge. A merge queue ensures only one PR is merged into <code><span><span style="color:var(--sh-identifier)">main</span></span></code> at a time. This allows for the baseline images to be fully updated before moving on to the next <code><span><span style="color:var(--sh-identifier)">main</span></span></code> merge.</p>
<p>If you'd rather read the code, check out our workflows <a href="https://github.com/CrowdStrike/glide-core/tree/main/.github/workflows" target="_blank" rel="noopener noreferrer">here</a>. The power of open source!</p>
<p>Hopefully this article helps save others time and effort when they want to add visual regression testing to their project.</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Introducing the Glide Core Design System]]></title>
            <link>https://tonyward.dev/articles/introducing-glide-core</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/introducing-glide-core</guid>
            <pubDate>Sat, 01 Mar 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I'm super excited to announce the pre-1.0 version of <a href="https://www.github.com/crowdstrike/glide-core" target="_blank" rel="noopener noreferrer">Glide Core</a>. Glide Core, or simply Glide as I'll refer to it from now on, is a Web Component Design System using <a href="https://lit.dev" target="_blank" rel="noopener noreferrer">Lit</a>. It serves as a foundational layer for building user interfaces and experiences at CrowdStrike. You can browse our <a href="http://glide-core.crowdstrike-ux.workers.dev" target="_blank" rel="noopener noreferrer">Storybook</a> to see what components we have thus far.</p>
<p>I've been working on Glide since November 2023 at CrowdStrike. We've made a lot of progress and I'm excited to see where we'll be this time next year. There's a lot I could deep dive on, but I thought this article could start with a high-level overview of some of our decisions, values, and technology. I'm hoping to write more around the points listed below in the near future. Stay tuned!</p>
<a id="why-web-components" href="#why-web-components"><h2>Why Web Components</h2></a>
<p>I've already <a href="https://www.tonyward.dev/articles/web-components-design-system" target="_blank" rel="noopener noreferrer">written about this</a> pretty extensively, but Web Components are great due to interoperability. CrowdStrike is a large organization and supports many different tech stacks. Rather than having to write a Design System for each framework we use, we can leverage Web Components to write components once and use them everywhere.</p>
<a id="css-custom-properties-and-design-tokens" href="#css-custom-properties-and-design-tokens"><h2>CSS Custom Properties and Design Tokens</h2></a>
<p>We make heavy use of Design Tokens to allow consumers to set their own theme for components. By default, we ship with our product themes of light and dark modes. These are all driven by CSS custom properties, or CSS variables. These variables are considered part of the public API of each component.</p>
<p>To keep things in sync between code and design, we have a script that pulls all of our variables from Figma and generates CSS variables from them. You can <a href="https://www.tonyward.dev/articles/figma-variables-to-css-variables" target="_blank" rel="noopener noreferrer">read more</a> about this process. Following this pattern allows us to update all tokens with a script and continue to bridge the gap between design and development. As design updates tokens on their end, we run a script to update the code side of things.</p>
<p>I'm actually <a href="https://github.com/CrowdStrike/glide-core/pull/732" target="_blank" rel="noopener noreferrer">in the process</a> of migrating to new semantic tokens for all of our components that align with our reworked Glide Figma UI Kit. You can check out the linked PR and see how our work progresses over time. I'm looking forward to getting this in!</p>
<a id="preventing-misuse" href="#preventing-misuse"><h2>Preventing Misuse</h2></a>
<p>We do a lot to help push folks in the right direction around how our components should or should not be used. Consumers sometimes do crazy things in an attempt to get a component to work a particular way. Sometimes it's due to a design going off the known path. Other times it may be to meet a deadline. Sometimes folks just don't know what they don't know.</p>
<p>Our team has seen this so go wrong so many times. We started coming up with ideas on how to help prevent these types of scenarios. They're costly and create technical debt for everyone involved. The reason we care so much about preventing misuse is because we want to limit the amount of technical debt generated by misuse. It adds up quickly if you don't keep tabs on things!</p>
<p>Rather than folks hacking components together to meet a deadline or to fulfill an incorrect design, our guardrails turn these situations into conversations rather than non-scalable work arounds. Does this require more time? Yes. But it's our job to understand these snowflake use cases, determine if the existing Design System components can fulfill their requirements instead, and either adjust our components or provide guidance on how they can accomplish their goals <em>outside</em> of the Design System.</p>
<blockquote>
<p>Why are they wanting a component to act that way? Can the existing component work for their use case? How frequently does this pattern show up through other parts of the products? Do we see this being a new pattern throughout?</p>
</blockquote>
<p>We don't want to block product teams from building what they need, but we also don't want them to misuse our existing components and then be surprised-Pikachu-face when things break with an upgrade. We also don't <a href="https://bradfrost.com/blog/post/dont-put-crap-in-the-design-system/" target="_blank" rel="noopener noreferrer">want to put crap in the Design System</a>.</p>
<p>It's a delicate balance. But we think we've found a sweet spot for it all. Here are some ways we prevent misuse.</p>
<a id="closed-shadow-roots" href="#closed-shadow-roots"><h3>Closed Shadow Roots</h3></a>
<p>Closed shadow roots seem to be somewhat controversial, but for a Design System I'm a big believer that it makes sense. Our components should be treated as black boxes - they receive inputs via attributes, properties, and methods and render DOM. We want our Input component to be treated like the native <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">input</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span></span></code>. Folks shouldn't need to dip their hands into our cookie jar to get things to work — instead use the public API of the component, just like you do with native. If that's insufficient, then it could be a bug or an improvement on our end. Once again, rather than hacking around things, let's chat!</p>
<p>If you're interested in reading more about my ramblings on this topic, <a href="https://www.tonyward.dev/articles/web-components-closed-roots-for-design-systems" target="_blank" rel="noopener noreferrer">you can here</a>. There are some issues with testing around closed shadow roots, especially when it comes to Playwright, but that's why we open up the shadow roots up in test environments. The key is that we don't want folks mucking with our internals in production code.</p>
<a id="slot-assertions" href="#slot-assertions"><h3>Slot Assertions</h3></a>
<p>For components like Menu, we expect Menu Options. Those Menu Options should be either Menu Buttons or Menu Links. Nothing else. These components have design guidelines and UI/UX requirements established by our Design Architects.</p>
<p>Because we only expect a Menu Button or Menu Link as children of Menu Options, we can assert that's the case. If someone puts something random in the Menu Options slot that we don't support, we throw an error in development mode. This goes back to my point above — if someone needs something else, it turns into a conversation rather than random content that may not fit the designed vision.</p>
<p>Some folks ask "why throw instead of log"? Logs get ignored way too frequently. Components throwing during development are much more likely to draw attention to the problem. So that's what we do.</p>
<a id="required-assertions" href="#required-assertions"><h3>Required Assertions</h3></a>
<p>Lit doesn't expose a way to mark a reactive property as required and enforce it, so we wrote <a href="https://github.com/CrowdStrike/glide-core/blob/main/src/library/required.ts" target="_blank" rel="noopener noreferrer">a decorator</a> to do it for us. Use cases for this are mostly around accessibility — we want folks to be forced to provide things like labels. Of course, they can provide garbage, but we always assume the best intent.</p>
<a id="no-css-parts" href="#no-css-parts"><h3>No CSS parts</h3></a>
<p>We don't expose any CSS parts (except for a few internal uses) from our components. Instead, we rely on CSS variables as part of our public API. I wrote about this previously <a href="https://www.tonyward.dev/articles/web-components-design-system#part" target="_blank" rel="noopener noreferrer">as well</a>. We don't want folks to completely override the styling of our components, only what we expose via CSS variables so that the overall UI/UX is maintained. Folks are simply slapping a different coat of paint on things as needed.</p>
<p>Because all of our decisions come from Design, we can get away with this. So far it seems to be working out very well, mostly because we don't need a high level of customization with our components — we support the known cases we have, and that's it.</p>
<a id="disallow-extending" href="#disallow-extending"><h3>Disallow Extending</h3></a>
<p>Our components use an <code><span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">final</span></span></code> <a href="https://github.com/CrowdStrike/glide-core/blob/main/src/library/final.ts" target="_blank" rel="noopener noreferrer">decorator</a>. This decorator prevents folks from extending our components (using <code><span><span style="color:var(--sh-keyword)">extends</span></span></code>). We don't want people extending them because although it may work, it could be a can of worms when it comes to support and upgrading. Instead, we prefer product teams to use our components as-is.</p>
<p>If that doesn't work for some reason, we chat with them one-on-one to learn more about their use cases. If we recognize this requires a snowflake component that is close to another one of ours, we work with them to get the shared functionality they need, decorate the component for tracking, and they're on their way.</p>
<p>Over time, we check in with these different groups that are making these kind-of-like-this-Design-System-component-but-not-quite and assess. Is this component being used frequently across the same or different products? Is it really a snowflake? Should this functionality go in the base Design System component?</p>
<p>This is all in the name of following Josh Clark's <a href="https://bigmedium.com/ideas/design-system-pace-layers-slow-fast.html" target="_blank" rel="noopener noreferrer">ship faster by building design systems slower</a>. Developers these days are too obsessed with creating abstractions for everything way too early. Sometimes letting things grow organically, seeing where they go in a few months, and for the short-term copy/pasting <em>some</em> logic is worth the tradeoff over an unnecessary, unwieldy abstraction.</p>
<a id="uniformity" href="#uniformity"><h2>Uniformity</h2></a>
<p>One of my favorite parts about popping into a repository is a consistent developer experience. I love not being able to tell who wrote which file. I like popping into one component file, reading it top to bottom, popping over to a second component, and seeing similar patterns throughout. This makes following the codebase and contributing to it a lot easier. I really enjoy things being uniform, following the same patterns, and being laid out as similarly as possible.</p>
<p>If you're a designer reading this section, you probably have similar feelings around how Figma files are structured.</p>
<p>Custom ESLint rules go a long way. We use Stylelint, Prettier, Stylistic, existing ESLint rules, and our own ESLint rules. Looking to write your own ESLint plugin? You guessed it — I wrote about this last year and <a href="https://www.tonyward.dev/articles/driving-consistency-with-eslint-rules" target="_blank" rel="noopener noreferrer">here's the link</a>. We automate as much as we can.</p>
<a id="documentation" href="#documentation"><h2>Documentation</h2></a>
<p>Our current documentation uses Storybook and focuses on Component APIs. We have larger plans in motion to build a more full fledged documentation site. Due to following native as closely as possible, our components shoud feel familiar to most folks already. I'm very impressed with <a href="https://playbook.ebay.com/design-system" target="_blank" rel="noopener noreferrer">Playbook by eBay</a> and other documentation sites and hope we will get there one day.</p>
<a id="testing" href="#testing"><h2>Testing</h2></a>
<p>We use Web Test Runner at the moment for all of our tests. We like that it tests the components in a browser, like a user would interact with them. We have considered switching over to vitest's browser mode, but we still think it could use some improvements. We also have over 1,000 tests to migrate. We'll continue to review for the next few months, but Web Test Runner is working well for us at the moment.</p>
<p>We have a 100% test coverage bar, which is maybe controversial to some. You can read my thoughts on it <a href="https://www.tonyward.dev/articles/design-system-code-coverage" target="_blank" rel="noopener noreferrer">here</a>. It seems to be working out well for us!</p>
<p>We're in the process of adding visual regression testing as well. There are some <a href="https://www.tonyward.dev/articles/visual-regression-testing-disruption" target="_blank" rel="noopener noreferrer">documented limitations</a> with the current tools, but I'm excited to give some of those proposals a try. We will see how things develop! This is being actively worked on at the moment, and I'm sure it's something I'll write about once we land on something that works well for us.</p>
<a id="guiding-principles" href="#guiding-principles"><h2>Guiding Principles</h2></a>
<p>I initially had this section at the top of the article, but figured maybe it'd be better towards the bottom. If you've stuck with me thus far, thank you! Let's dive into the principles that help us make decisions around the Design System.</p>
<ul>
<li>Design Architects drive our roadmap.<!-- -->
<ul>
<li>We have Design Architects that have all of the context of products being built with Glide. They understand the product flows and how our Design System building blocks should work on the micro and macro scale. We allow these Architects to drive requirements and our roadmap for building components. We have back and forth on features and functionality, but they own the UI/UX of all components and we build them to their specifications. Each Pull Request with visual or experience updates also goes through a Design Review Process before we merge the components into <code><span><span style="color:var(--sh-identifier)">main</span></span></code>.</li>
<li>Having folks in this role ensures that our components are being built to help speed up product development, while also ensuring the best experience for our users. It also allows for this group of designers to keep tabs on all of the designs currently being built to help identify patterns and room for improvement.</li>
</ul>
</li>
<li>Our users expect consistency in our components when it comes to UI (user interface), UX (user experience), and DX (developer experience) perspectives.<!-- -->
<ul>
<li>We rely on the Design Architects for the UI/UX consistency piece.</li>
<li>From an internal developer experience perspective, we enforce consistency with ESLint, Prettier, Styelint, and Stylistic when it comes to being <em>internally</em> consistent. I described this above in <a href="#uniformity">uniformity</a>.</li>
<li>Externally, our customers/users expect attributes, properties, and methods to be consistent.<!-- -->
<ul>
<li>How we name attributes, properties, and methods should be identical if the ultimate action is the same across multiple components (e.g., a tooltip opening via an <code><span><span style="color:var(--sh-identifier)">open</span></span></code> attribute is the same as a Drawer, Modal, Dropdown, or Popover opening, so the attribute should be named the same across all of these components).</li>
</ul>
</li>
</ul>
</li>
<li>We follow native as much as possible, where it makes sense, and document when and why we need to break from native behavior.</li>
<li>We leverage the platform rather than using third-party libraries when we can. We are okay with progressive enhancements when browser support is lacking.</li>
<li>We ensure our components are accessible and respect user motion preferences.<!-- -->
<ul>
<li>We've got quite a bit of animation in comparison to other Design Systems. If folks want reduced motion, we turn them off.</li>
<li>We have an accessibility engineer on our team to help with all-things-accessibility.</li>
</ul>
</li>
<li>We always "tend to the garden" and make things nicer than we last left them.<!-- -->
<ul>
<li>We're constantly updating the codebase to follow newly established patterns and apply learnings over time.</li>
</ul>
</li>
<li>We prefer consistency over flexibility.<!-- -->
<ul>
<li>Rather than being infinitely flexible for all sorts of use cases, our components are laser focused on the known use cases. By being more consistent, it makes decisions a lot easier. It also makes the components easier to reason about. By adding flexibility, it can lead to inconsistent experiences and misuse. Instead, we prefer locking things down as much as possible. We limit flexibility in favor of consistency.</li>
</ul>
</li>
</ul>
<p>These principles help us make decisions about our Design System. Like all things, we revisit them and decide if they're serving us well, need to be amended, or completely removed. As time goes on, we continue to improve the process.</p>
<a id="give-it-a-spin!" href="#give-it-a-spin!"><h2>Give it a Spin!</h2></a>
<p>That's all I've got for now. Go give Glide a spin and let us know what you think. Take a look at our source code. Feel free to ask me questions on <a href="https://bsky.app/profile/tonyward.dev" target="_blank" rel="noopener noreferrer">Bluesky</a> or <a href="https://www.linkedin.com/in/ynotdraw" target="_blank" rel="noopener noreferrer">LinkedIn</a>. Until next time!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[The Web is Awesome in 2025]]></title>
            <link>https://tonyward.dev/articles/the-web-is-awesome-2025</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/the-web-is-awesome-2025</guid>
            <pubDate>Sun, 16 Feb 2025 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>A bit late, but happy 2025! As I look back to when I started my career, I can't help but be very impressed with how far the web has come. It's a great time to be a frontend engineer. It seems as though web features have really accelerated in the last 3-4 years across all browsers. It's very cool to see.</p>
<p>And it looks like they aren't letting off the accelerator. <a href="https://web.dev/blog/interop-2025" target="_blank" rel="noopener noreferrer">Interop 2025</a> is looking great. Their dashboard can <a href="https://wpt.fyi/interop-2025" target="_blank" rel="noopener noreferrer">be found here</a>. I'm really excited about everything on the list, but in particular I'm most excited about anchor positioning and view transitions getting support across all browsers.</p>
<p>Anchor positioning will help me build tooltips and popovers without <a href="https://floating-ui.com" target="_blank" rel="noopener noreferrer">floating UI</a>. View Transitions will help with animations and making the web feel more like native apps. <a href="https://codepen.io/ynotdraw/pen/XJrGeaZ" target="_blank" rel="noopener noreferrer">This example</a> is one of my favorites — this is the type of interaction you see on a mobile device. Same for this <a href="https://codepen.io/dannymoerkerke/full/VYZRYpy" target="_blank" rel="noopener noreferrer">credit card example</a>. <a href="https://bsky.app/profile/dannymoerkerke.bsky.social" target="_blank" rel="noopener noreferrer">Danny Moerkerke</a> did such an excellent job with these and posted them on <a href="https://codepen.io/dannymoerkerke" target="_blank" rel="noopener noreferrer">codepen</a> (thank you!). These are SO GOOD! And exactly what I'm excited about with view transitions.</p>
<p>Another thing I'm interested in for this year is the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API" target="_blank" rel="noopener noreferrer">Navigation API</a> and how it might change the way we build web apps in the near future. It doesn't necessarily affect me much day to day in my current work, but I'm really interested to see how it develops and what changes come from it.</p>
<p>And these items are <em>only</em> from Interop! There is a ton of work going on that isn't on this list. With Custom Elements / Web Components in particular, I'm really excited for Scoped Custom Element Registries, cross shadow-root aria, and many other enhancements. <a href="https://tc39.es/proposal-temporal/docs/" target="_blank" rel="noopener noreferrer">Temporal</a> is another thing I'm very excited about — dealing with dates, times, and timezones in JavaScript is such a pain, but Temporal is promised to be a huge improvement and is <a href="https://developer.mozilla.org/en-US/blog/javascript-temporal-is-coming/" target="_blank" rel="noopener noreferrer">coming soon™</a>. I'm <em>super</em> pumped for <a href="https://developer.chrome.com/blog/rfc-customizable-select" target="_blank" rel="noopener noreferrer">customizable Select</a> as well.</p>
<p>I'm very thankful for everyone doing the work to make web tech better every year. Thank you all! And I can't wait to see where we're at with things this time next year.</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[2024 in Review]]></title>
            <link>https://tonyward.dev/articles/2024-review</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/2024-review</guid>
            <pubDate>Tue, 31 Dec 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Wow, this year has really flown by. It feels like this was a <em>huge</em> growth-year. 2024 has been great for our family and I'm excited to see what 2025 brings.</p>
<p>Here are some things I've reflected on today around 2024.</p>
<a id="professionally" href="#professionally"><h2>Professionally</h2></a>
<p>In 2024, I became the lead of 7 full-time engineers working on a new Web Component based Design System using <a href="https://lit.dev" target="_blank" rel="noopener noreferrer">lit</a>. My team and I threw ourselves into the deep end, learning Web Components in late 2023. I'm really proud of what we've built. I'm looking forward to open sourcing it in Q1 of 2025.</p>
<p>I also got promoted to Senior II this year at CrowdStrike (their ladder is: Eng I, Eng II, Eng III, Senior I, Senior II, Principal). This year I learned how important it is to take ownership of big problems. Even if you're wrong or make a mistake, the fact that you own a problem and are willing to iterate towards a solution goes a long way.</p>
<p>This is one piece of advice I'd give anyone looking to advance their career - if you hear of a problem at work that piques your interest, don't be afraid to sign yourself up to solve it. You'll learn <strong>a ton</strong>. Working with different stakeholders and being the "captain of a ship" provides a lot of risk/reward. It's not always sunshine and rainbows, but it definitely helps advance your career and widens your impact at an organization.</p>
<a id="personally" href="#personally"><h2>Personally</h2></a>
<p>Now on to some personal stuff...</p>
<a id="writing" href="#writing"><h3>Writing</h3></a>
<p>I wrote (here!) at least once a month since February. Check out my previous articles if you're interested! I really enjoy it. I hope to keep it up in 2025, but maybe not as frequently.</p>
<ul>
<li><a href="https://www.tonyward.dev/articles/web-components-design-system" target="_blank" rel="noopener noreferrer">Web Components 🤝 Design Systems</a></li>
<li><a href="https://www.tonyward.dev/articles/roaming-shapes" target="_blank" rel="noopener noreferrer">Roaming Shapes for Empty States</a></li>
<li><a href="https://www.tonyward.dev/articles/productize-your-design-system" target="_blank" rel="noopener noreferrer">Productize Your Design System</a></li>
<li><a href="https://www.tonyward.dev/articles/improving" target="_blank" rel="noopener noreferrer">Improving</a></li>
<li><a href="https://www.tonyward.dev/articles/measuring-design-system-success" target="_blank" rel="noopener noreferrer">Collecting Feedback &amp; Measuring the Success of a Design System</a></li>
<li><a href="https://www.tonyward.dev/articles/driving-consistency-with-eslint-rules" target="_blank" rel="noopener noreferrer">Driving Consistency with Custom ESLint Rules</a></li>
<li><a href="https://www.tonyward.dev/articles/web-components-for-framework-devs" target="_blank" rel="noopener noreferrer">Web Components for Framework Developers</a></li>
<li><a href="https://www.tonyward.dev/articles/web-components-closed-roots-for-design-systems" target="_blank" rel="noopener noreferrer">Closed Shadow Roots for Design Systems</a></li>
<li><a href="https://www.tonyward.dev/articles/design-system-code-coverage" target="_blank" rel="noopener noreferrer">Design System Code Coverage</a></li>
<li><a href="https://www.tonyward.dev/articles/visual-regression-testing-disruption" target="_blank" rel="noopener noreferrer">It's Time to Disrupt Visual Regression Testing</a></li>
<li><a href="https://www.tonyward.dev/articles/managing-releases-with-changesets" target="_blank" rel="noopener noreferrer">Managing Releases with Changesets</a></li>
<li><a href="https://www.tonyward.dev/articles/figma-variables-to-css-variables" target="_blank" rel="noopener noreferrer">Syncing Figma Variables to CSS Variables</a></li>
</ul>
<a id="health" href="#health"><h3>Health</h3></a>
<p>I started taking my health more seriously. I went to the doctor more often for longstanding issues or concerns I've had based on my family history. I'm getting older, and unfortunately I can begin feeling it.</p>
<a id="gaming" href="#gaming"><h3>Gaming</h3></a>
<p>Gaming kind of fell off for me in 2024. I played Final Fantasy VII Rebirth and really loved it, but no other games really pulled me in. I'm not sure if I'm getting older and less interested in them, or if it was just a slow year for gaming. I assume the latter. In the last month I have been playing Marvel Rivals though, and that's been great! I'm looking forward to Death Stranding 2, Intergalactic: The Heretic Prophet, and a new title from Sony Santa Monica.</p>
<a id="tv-and-film" href="#tv-and-film"><h3>TV and Film</h3></a>
<p><a href="https://www.arcane.com/en-us/" target="_blank" rel="noopener noreferrer">Arcane</a> season two was amazing. It is definitely one of my top 5 television shows of all time. I thought <a href="https://www.rottentomatoes.com/tv/fallout" target="_blank" rel="noopener noreferrer">Fallout</a> was excellent too. I loved the humor and vibe it had throughout. I watched Sonic 1, 2, and 3 this year and was blown away by how much I actually loved it. I think it's my favorite video game adaptation for a movie... ever? In 2025, I'm looking forward to Severance season two and The Last of Us to continue.</p>
<a id="music" href="#music"><h3>Music</h3></a>
<p>When it comes to music, I'm still listening to a lot of Sleep Token. I recently started listening to Bilmuri, despite <em>hating</em> country, but now I think I may like it. Here are my top artists according to Apple Music:</p>
<ol>
<li><a href="https://music.apple.com/us/artist/bad-omens/467610583" target="_blank" rel="noopener noreferrer">Bad Omens</a></li>
<li><a href="https://music.apple.com/us/artist/glass-animals/528928008" target="_blank" rel="noopener noreferrer">Glass Animals</a></li>
<li><a href="https://music.apple.com/us/artist/sleep-token/1153723033" target="_blank" rel="noopener noreferrer">Sleep Token</a></li>
<li><a href="https://music.apple.com/us/artist/bilmuri/1072442999" target="_blank" rel="noopener noreferrer">Bilmuri</a></li>
<li><a href="https://music.apple.com/us/artist/falling-in-reverse/439592053" target="_blank" rel="noopener noreferrer">Falling in Reverse</a></li>
<li><a href="https://music.apple.com/us/artist/low-roar/475538413" target="_blank" rel="noopener noreferrer">Low Roar</a></li>
<li><a href="https://music.apple.com/us/artist/nobuo-uematsu/475970248" target="_blank" rel="noopener noreferrer">Nobuo Uematsu</a></li>
<li><a href="https://music.apple.com/us/artist/babymetal/477101776" target="_blank" rel="noopener noreferrer">BABYMETAL</a></li>
<li><a href="https://music.apple.com/us/artist/disembodied-tyrant/1549770060" target="_blank" rel="noopener noreferrer">Disembodied Tyrant</a></li>
<li><a href="https://music.apple.com/us/artist/bluey/1538988888" target="_blank" rel="noopener noreferrer">Bluey (lol)</a></li>
</ol>
<p>This year was awesome for music. So many great songs. It was next on the list, but holy shit, Arcane's season two album was SO GOOD! So many catchy tunes.</p>
<p>Disembodied Tyrant is probably one of my favorite bands now. <a href="https://www.youtube.com/watch?v=R3m8nIJL97g" target="_blank" rel="noopener noreferrer">Musician Mansion Season Two</a> was great - I want more! I think I may start playing guitar again, but we'll see. I always talk myself out of it, but I think it'd be fun to get back into it again.</p>
<a id="vacations" href="#vacations"><h3>Vacations</h3></a>
<p>We took a vacation to Disney World earlier in 2024. We drove down to Nashville as well, which was fun. As a family, we've spent a lot of fun time together and really enjoyed each other's company. I'm looking forward to more family time in 2025, and hopefully some more vacations!</p>
<a id="social-media" href="#social-media"><h3>Social Media</h3></a>
<p>I dropped Twitter after Elon took over and haven't had Facebook since 2017. I've been pretty anti social-media for a while now, but Bluesky is feeling like old tech Twitter again. I'm having a great time. I hope it stays this way! You can find me <a href="https://bsky.app/profile/tonyward.dev" target="_blank" rel="noopener noreferrer">here</a>. I'm hopeful that Bluesky can remain awesome, but I'm also ready to drop it at any moment.</p>
<a id="house-work" href="#house-work"><h3>House Work</h3></a>
<p>Now that my son is four years old and I have a bit more free time on my hands, I started fixing more things myself rather than paying someone. For example, fixing some drywall and painting, caulking, doing yard work, and the like. I've gotten more comfortable fixing things in our house, which has been great. It turns out a lot of it is pretty easy if you watch a few YouTube videos and have the right tools. It's funny to see the progression of "I have no idea what I'm doing" → "oh, I think I'm doing it" → "shit, I messed up" → "omg I did it!".</p>
<p>Am I a real dad now?</p>
<a id="2025" href="#2025"><h2>2025</h2></a>
<p>Overall, this year was great and I'm looking forward to 2025. I can't wait to see what our little family ends up doing next year. I'm looking forward to work and seeing how our Design System grows. I'm hoping to pick up a hobby (hopefully back to guitar) and gaming some more.</p>
<p>I'm hoping to spend more time with friends and family. Over the past four years, I learned a lot about prioritizing time with folks you care about as they won't be here for forever. It's also good to get face time with people, despite me becoming a bit reclusive the past few years.</p>
<p>Onward to 2025 🫡</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Syncing Figma Variables to CSS Variables]]></title>
            <link>https://tonyward.dev/articles/figma-variables-to-css-variables</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/figma-variables-to-css-variables</guid>
            <pubDate>Wed, 20 Nov 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Would you like to keep your design tokens in sync between Figma and code? I thought I'd share some of my experiences doing so over the years and talk through it from a high level. This isn't specific to any single company, and every company configures their tokens in Figma differently. The Figma APIs and some JavaScript/TypeScript are all you need to make this syncing a reality.</p>
<p>This article originated from a <a href="https://bsky.app/profile/tonyward.dev/post/3lbdqitcx4c2o" target="_blank" rel="noopener noreferrer">post</a> I did over on Bluesky. <a href="https://darn.es" target="_blank" rel="noopener noreferrer">David Darnes</a> suggested I write a blog post about it, so here we are (thank you for the suggestion!)!</p>
<p>Generating your CSS variables from Figma variables ensures you're always in sync with design. Once you have it in place, you can run it via a cron job, or as a manual script. It removes the manual, laborious process of poking around Figma's UI and copy/pasting the variables into your CSS. Instead, run a script and let the computer do it! There's definitely an open source opportunity here to do a lot of the "token processing" for you. But we'll get into that another time.</p>
<p>Maybe you're using primitive and semantic tokens? Or maybe it's a wild west with how your variables are defined? No matter how things are structured, we can still map your tokens to CSS variables.</p>
<blockquote>
<p><strong>Update March 16th, 2025:</strong> If you prefer looking at a PR instead, <a href="https://github.com/CrowdStrike/glide-core/pull/732" target="_blank" rel="noopener noreferrer">here is one</a> I recently did.</p>
</blockquote>
<blockquote>
<p><strong>Update Feb 20th, 2025:</strong> Turns out that Figma has <a href="https://www.figma.com/community/file/1270821372236564565" target="_blank" rel="noopener noreferrer">a FigJam file</a> outlining a lot of this process. I had no idea when writing this article! Their guidance doesn't handle recursive token lookups, but it's pretty darn close to what you probably want/need. You may be able to use most of <a href="https://github.com/figma/variables-github-action-example?tab=readme-ov-file" target="_blank" rel="noopener noreferrer">this repository</a>, or at a minimum, use it as a guide! If you'd like a bit of a deeper dive, please continue!</p>
</blockquote>
<a id="fetching-the-variables" href="#fetching-the-variables"><h2>Fetching the Variables</h2></a>
<p>To get the variables out of Figma, you can hit their REST API directly. I've been using the <code><span><span style="color:var(--sh-identifier)">local</span></span></code> endpoint <a href="https://www.figma.com/developers/api#get-local-variables-endpoint" target="_blank" rel="noopener noreferrer">as described here</a> to get all of the variables from a given file.</p>
<p>You can use <a href="https://www.figma.com/developers/api#get-published-variables-endpoint" target="_blank" rel="noopener noreferrer">the published variables</a> instead if you like, and the response is <em>slightly</em> different. Local gives you both local variables <em>and</em> remote, so this normally works best for situations I've been in.</p>
<p>You can fetch the variables in node, but here's how you'd <code><span><span style="color:var(--sh-identifier)">curl</span></span></code> it if you want to see what the response looks like. You pull the file ID from the URL.</p>
<pre><code><span><span style="color:var(--sh-identifier)">curl</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">X</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">GET</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">\</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">https</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-comment)">//api.figma.com/v1/files/YOUR_FILE_ID/variables/local\?scopes\=file_variables:read</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">H</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">X-FIGMA-TOKEN: YOUR_FIGMA_TOKEN</span><span style="color:var(--sh-string)">'</span></span>
<span></span></code></pre>
<p>You'll get back a big ol' JSON blob to parse through that is in the following shape.</p>
<pre><code><span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">status</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Number</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">error</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">meta</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">variables</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-identifier)">variableId</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">key</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">variableCollectionId</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">resolvedType</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">BOOLEAN</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">FLOAT</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">STRING</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">COLOR</span><span style="color:var(--sh-string)">'</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">valuesByMode</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">          </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-identifier)">modeId</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Number</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Color</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">VariableAlias</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">remote</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">description</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">hiddenFromPublishing</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">scopes</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">VariableScope</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">codeSyntax</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">VariableCodeSyntax</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">variableCollections</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-identifier)">variableCollectionId</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">key</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">modes</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span></span>
<span><span style="color:var(--sh-space)">          </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">            </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">modeId</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">            </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">          </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">defaultModeId</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">remote</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">hiddenFromPublishing</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">variableIds</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">String</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">deletedButReferenced</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<a id="parsing-the-data" href="#parsing-the-data"><h2>Parsing the Data</h2></a>
<p>Here is where you can put on your data processing / computer science hat. We need to associate the name of a variable with the raw CSS value by iterating through the keys in this large object.</p>
<p><code><span><span style="color:var(--sh-identifier)">variables</span></span></code> in this response are pretty self explanatory. They are the raw variables from Figma. This houses all of the CSS values you'll need.</p>
<p><code><span><span style="color:var(--sh-identifier)">variableCollections</span></span></code> on the other hand, you may not be familiar with as a developer.</p>
<blockquote>
<p>A collection is a set of variables and modes. Collections can be used to organize related variables together. For example, use one collection to localize text in different languages, and another collection for spatial values.</p>
</blockquote>
<p>— <a href="https://help.figma.com/hc/en-us/articles/14506821864087-Overview-of-variables-collections-and-modes#h_01H9V3QSVH2T1EYNXP7RNXZ8MV" target="_blank" rel="noopener noreferrer">Figma's docs</a></p>
<p>The collection is specific to how your team organizes your variables. For example, maybe one collection is for colors related to data visualization, while another is for font sizes.</p>
<p>If you are using variable modes for light and dark themes, there will be a variable collection in here that will help you identify if a particular color in <code><span><span style="color:var(--sh-identifier)">variables</span></span></code> belongs to "light" versus "dark" mode. It also lists all of the variable IDs that have those modes under <code><span><span style="color:var(--sh-identifier)">variableIds</span></span></code>.</p>
<p>As you are parsing through this data, you'll want to keep track of what the name of the token is and the raw CSS value by iterating over these things. I like storing this information in a JavaScript object/JSON.</p>
<p>Here's a made up example of a variable collection from Figma's API supporting both light and dark modes.</p>
<pre><code><span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableCollection:some-long-id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">modes</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">modeId</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">Dark</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">modeId</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">2</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">Light</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">variableIds</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:1</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:2</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">]</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>We've got two modes: one for "light" and one for "dark". You'll see there's also an array of <code><span><span style="color:var(--sh-identifier)">variableIds</span></span></code>.</p>
<p>If we pull up <code><span><span style="color:var(--sh-class)">VariableID</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-class)">1</span></span></code> from that array, you'll see something like the following. Here we have a <code><span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">primary</span></span></code> semantic token that we'd like to export.</p>
<pre><code><span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:1</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:1</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">background/primary</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">valuesByMode</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">type</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VARIABLE_ALIAS</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:5</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">2</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">type</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VARIABLE_ALIAS</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:6</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>We can pull the name of the token from <code><span><span style="color:var(--sh-identifier)">name</span></span></code>, but to get the value we need to look into <code><span><span style="color:var(--sh-identifier)">valuesByMode</span></span></code>. Notice how it is "by mode" — yup, we've got two modes, a light and dark mode, so we have two <code><span><span style="color:var(--sh-identifier)">valuesByMode</span></span></code> defined here.</p>
<p>The <code><span><span style="color:var(--sh-identifier)">valuesByMode</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">]</span></span></code> key refers to the <code><span><span style="color:var(--sh-identifier)">modeId</span></span></code> above, which is <code><span><span style="color:var(--sh-class)">DARK</span></span></code>. The <code><span><span style="color:var(--sh-identifier)">valuesByMode</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">2</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">]</span></span></code> key refers <code><span><span style="color:var(--sh-class)">LIGHT</span></span></code>. So <code><span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">primary</span></span></code> has two values, one for when in light mode and another for dark mode.</p>
<p>Oh no! A <code><span><span style="color:var(--sh-class)">VARIABLE_ALIAS</span></span></code>? What's that? It's a reference to yet another variable. Depending on how your designers have setup variables, you may have deeply nested references. In your data parsing, this complicates things slightly, as you don't have access directly to your color value. For this, you'll need to follow the <code><span><span style="color:var(--sh-class)">VARIABLE_ALIAS</span></span></code> trail.</p>
<p>If we follow <code><span><span style="color:var(--sh-class)">VariableID</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-class)">5</span></span></code>, we get to our raw color value (in rgba). In this made up example, the Designer Tony has associated the <code><span><span style="color:var(--sh-identifier)">gray</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">900</span></span></code> primitive token to <code><span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">primary</span></span></code> in the dark mode.</p>
<pre><code><span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:5</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:5</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">gray-900</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">valuesByMode</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">random-id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">r</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">15</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">g</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">23</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">b</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">42</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">a</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">1</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>And the Light Mode definition would be defined as well. Designer Tony has associated the <code><span><span style="color:var(--sh-identifier)">gray</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">100</span></span></code> primitive token to <code><span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">primary</span></span></code> in the light mode.</p>
<pre><code><span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:6</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">VariableID:6</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">gray-100</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">resolvedType</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">COLOR</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">valuesByMode</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">random-id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">r</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">241</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">g</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">245</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">b</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">249</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">a</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">1</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>So that's following the design token trail all the way down from a semantic token to an alias to a primitive token. You'll need to keep track of how these semantic tokens reference the primitive ones. Once again, I typicaly store it in a JavaScript object or write it to JSON for convenience.</p>
<p>Now repeat this process for every token! Ah, recursion and loops. Gotta love it!</p>
<a id="tokens-checkpoint" href="#tokens-checkpoint"><h2>Tokens Checkpoint</h2></a>
<p>After going through above, you'll have an object that looks something like the following.</p>
<pre><code><span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">light</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">background/primary</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">value</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">gray-100</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">type</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">COLOR</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">dark</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">background/primary</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">value</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">gray-900</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">type</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">COLOR</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">primitive</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">gray-100</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">value</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">rgb(241 245 249)</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">type</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">COLOR</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">gray-900</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">value</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">rgb(15 23 42)</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">type</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">COLOR</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Awesome! Now all of your Figma Variables are in a format you can use to generate your CSS variables from. Before doing that, let's discuss why you might want to adjust some of these <code><span><span style="color:var(--sh-identifier)">value</span></span></code>s before pushing them over to CSS values.</p>
<a id="converting-values" href="#converting-values"><h2>Converting Values</h2></a>
<p>You may notice that Figma provides <code><span><span style="color:var(--sh-identifier)">rgba</span></span></code> for color values. You may want to use hex or something else. With the power of JavaScript, you can do whatever you want here! For the rest of the article, for simplicity, I'll keep rolling with <code><span><span style="color:var(--sh-identifier)">rgb</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span></span></code>, but know that hex, oklch, or whatever you want to support is an option, you'll need to write the code to do these conversions.</p>
<p>When exporting non-color token values, the resolved type might be <code><span><span style="color:var(--sh-class)">FLOAT</span></span></code>. A lot of times these are for pixel values. I prefer converting these values from pixels to <code><span><span style="color:var(--sh-identifier)">rem</span></span></code>s <a href="https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/" target="_blank" rel="noopener noreferrer">for most cases and accessibility</a>.</p>
<p>For the names of each token, you likely want to follow the CSS Variable syntax. So rather than <code><span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">primary</span></span></code>, you probably want <code><span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">primary</span></span></code> instead. Maybe you even want to add a prefix in there to differentiate between Design System tokens versus application level tokens (e.g., <code><span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">ds</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">primary</span></span></code>). This type of conversion can happen wherever you see fit. Either during the above process, or in the next step.</p>
<a id="create-the-css-variables" href="#create-the-css-variables"><h2>Create the CSS Variables</h2></a>
<p>Now that you have all of your names and variables in a nice format, you can write them to CSS files directly.</p>
<p>Depending on how you want to structure your files, you could have separate primitive and semantic files.</p>
<pre><code><span><span style="color:var(--sh-comment)">/* primitive.css */</span></span>
<span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">root</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">gray</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">100</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">rgb</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">241</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">245</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">249</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">gray</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">900</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">rgb</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">15</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">23</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">42</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-comment)">/* semantic-dark.css */</span></span>
<span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">root</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">dark</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">primary</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">gray</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">900</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-comment)">/* semantic-light.css */</span></span>
<span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">root</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">light</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">primary</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">gray</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">100</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>If you only want to expose your semantic tokens and not your primitive ones, you can skip over them completely by writing the raw primitive value instead.</p>
<pre><code><span><span style="color:var(--sh-comment)">/* light.css */</span></span>
<span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">root</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">light</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* Value is derived from `--gray-100` */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">primary</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">rgb</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">241</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">245</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">249</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-comment)">/* dark.css */</span></span>
<span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">root</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">dark</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* Value is derived from `--gray-900` */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">primary</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">rgb</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">15</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">23</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">42</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<a id="bridging-design-and-engineering" href="#bridging-design-and-engineering"><h2>Bridging Design and Engineering</h2></a>
<p>With all of above in place, you've got all you need! Obviously I oversimplified a lot of this, but hopefully this gives you a starting point and a decent understanding to start diving in yourself.</p>
<p>Every use case is slightly different, so I mostly wanted to plant the seed that you can sync <em>a lot</em> of things from Figma and into code using their API. The Figma API is <em>very</em> powerful and probably underutilized with most teams.</p>
<p>Above I touched on an opportunity to handle some of this heavy lifting in open source by providing a package that'll fetch your variables and write them to a JSON object. Then you'd need to take that object and figure out how you'd want to structure your CSS files. I have nothing to share quite yet, but watch this space and something may pop up here soon™.</p>
<p>Overall, keeping Figma and code in sync can be a manual process. But it doesn't have to be! By keeping our Figma and CSS variables in sync, it ensures we continue to bridge the gap with our Design Systems between design and engineering. As always, thanks for reading. See you soon!</p>
<blockquote>
<p>Want to sync more things between Figma and code? Check out my <a href="https://www.tonyward.dev/articles/figma-dev-resources" target="_blank" rel="noopener noreferrer">syncing Figma dev resources</a> article.</p>
</blockquote>
<a id="update-nov-21-2024" href="#update-nov-21-2024"><h3>Update Nov 21,2024</h3></a>
<p><a href="https://yonatankra.com/" target="_blank" rel="noopener noreferrer">Yonatan Kra</a> over on <a href="https://bsky.app/profile/yonatankra.com/post/3lbgj3tuwlc2e" target="_blank" rel="noopener noreferrer">Bluesky</a> mentioned:</p>
<blockquote>
<p>Have you thought of converting the figma output to a json and then using style dictionary to convert it to css?
It has the added benefit of then being able to convert it to multiple formats (like native mobile stuff if your DS needs to support that) or multiple theme versions. WDYT?</p>
</blockquote>
<p>Absolutely! I actually had written this in the initial article but ended up removing it to keep this scoped to CSS variables. You could write these Figma variables to <a href="https://amzn.github.io/style-dictionary/#/" target="_blank" rel="noopener noreferrer">Style Dictionary</a> and then get multi-platform design tokens. This could be <strong>huge</strong> for some Design System teams.</p>
<p>More from <a href="https://bsky.app/profile/darn.es/post/3lbh6mkjkts2z" target="_blank" rel="noopener noreferrer">David Darnes</a> as well</p>
<blockquote>
<p>I suppose vanilla js would be more performant, but Style Dictionary means you can leverage their well documented API. Plus as Tony said you can generate all distribution formats. In the past we've done that and wrapped it into a single npm package</p>
</blockquote>
<p>Style Dictionary would also allow you to create a Tailwind config, so that you can use all of your tokens with TailwindCSS. Style Dictionary opens the door to a lot of great ideas if you want more than just CSS variables!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Managing Releases with Changesets]]></title>
            <link>https://tonyward.dev/articles/managing-releases-with-changesets</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/managing-releases-with-changesets</guid>
            <pubDate>Mon, 04 Nov 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Do you find managing releases of your Design System to be a pain? Are you tired of repeating yourself to write a changelog and release notes? Do you release <em>too</em> frequently, leading to upgrade fatigue for your consumers?</p>
<p>I've been using <a href="https://github.com/changesets/changesets" target="_blank" rel="noopener noreferrer">Changesets</a> since 2022 and believe it's an excellent tool to help with all of the above questions. Let's dive in.</p>
<a id="what-is-it?" href="#what-is-it?"><h2>What is it?</h2></a>
<blockquote>
<p>"A tool to manage versioning and changelogs with a focus on multi-package repositories".</p>
</blockquote>
<p>Changesets allows you to follow semantic versioning and puts you in control of when to cut a release. When you're ready, it'll publish your package(s), update a changelog file, and update the releases section in GitHub with this information.</p>
<p>It's very straight forward to use — simply run <code><span><span style="color:var(--sh-identifier)">pnpm</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">changeset</span></span></code> when your change is worth calling out for consumers of your package. Check in the markdown file it added, write some helpful information outlining the changes, and then put your pull request up for review like you would normally.</p>
<p>Let's quickly run through an example of a Changesets workflow. Say you're building a Design System that publishes to <code><span><span style="color:var(--sh-identifier)">npm</span></span></code>.</p>
<ul>
<li>You put up a PR to add a new Alert component.</li>
<li>You run <code><span><span style="color:var(--sh-identifier)">pnpm</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">changeset</span></span></code>.</li>
<li>You select a <code><span><span style="color:var(--sh-identifier)">minor</span></span></code> version change, since we are adding new functionality.</li>
<li>You'll be prompted for a summary of the changes. We'll write something nice about our new Alert component, how to import it, when to use it, and lastly how to use it.</li>
<li>It'll then ask you for confirmation of the changeset.</li>
<li>A changeset is added to your changes as a markdown file.</li>
<li>You commit this markdown file and push it to your branch. Let it merge in with your PR.</li>
<li>Your PR merges, but it's <strong>not yet released</strong> to consumers. It's simply in <code><span><span style="color:var(--sh-identifier)">main</span></span></code>.</li>
<li>Changesets collects all open changes and creates a "Release Preview" PR (you can name this whatever you want).</li>
<li>This PR keeps track of all changes that'll go out in the next release. It looks at the "largest change" to determine which versioning path it should take. So if you have 2 <code><span><span style="color:var(--sh-identifier)">minor</span></span></code> updates and one <code><span><span style="color:var(--sh-identifier)">patch</span></span></code>, the next release will be a <code><span><span style="color:var(--sh-identifier)">minor</span></span></code> update. If you have a <code><span><span style="color:var(--sh-identifier)">major</span></span></code> update and 500 <code><span><span style="color:var(--sh-identifier)">patch</span></span></code> changes, it'll bump it up to major.</li>
<li>Merging the "Release Preview" PR versions your package(s), creates a tag, and publishes to <code><span><span style="color:var(--sh-identifier)">npm</span></span></code>. Using the changeset markdown files you created earlier, the changelog file is automatically updated with this information. If you're on GitHub, it creates a nice release log for you too using the same changeset files.</li>
</ul>
<p>After this process, consumers can use your changes and have all of the information they may need about the release. Since you follow semantic versioning, their expectations are properly set with each version update.</p>
<p>If all above makes sense, you can probably stop here and go give it a try — otherwise, I'll ramble a bit longer about some of my favorite things with Changesets.</p>
<a id="respect-semantic-versioning" href="#respect-semantic-versioning"><h2>Respect Semantic Versioning</h2></a>
<p><a href="https://semver.org" target="_blank" rel="noopener noreferrer">Semantic versioning</a> is a key piece of building and maintaining a JavaScript package. It communicates to your consumer what happened in the release, whether it's a breaking change that may require some of their time to upgrade, a minor version update with new features they can leverage, or a patch change to fix some bugs.</p>
<p>Unfortunately, semantic versioning isn't always respected by library authors. This is frustrating. But for packages you own, you can ensure this is followed quite easily — especially when using Changesets. With each <code><span><span style="color:var(--sh-identifier)">pnpm</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">changeset</span></span></code> command you run, Changesets explicitly asks you what type of change (major, minor, or patch) is in your pull request.</p>
<p>Follow semantic versioning. Your users will appreciate it! No one wants to be surprised with a breaking change in a minor or patch release.</p>
<a id="control-when-to-release" href="#control-when-to-release"><h2>Control When to Release</h2></a>
<p>Being able to decide <em>when</em> to release is huge. Sometimes you want to batch a bunch of changes together. Maybe you're drastically changing all of your design tokens. Or maybe you're going through a really large refactor that'll enable future features. Maybe you are rewriting a large component and you want to break the changes into separate PRs to make it easier to review, but you don't want to cut a release until you're completed. Rather than having to do feature branches, you can let everything merge into <code><span><span style="color:var(--sh-identifier)">main</span></span></code> without any worry of it being released to consumers too early.</p>
<p>Compare this to the <a href="https://github.com/semantic-release/semantic-release" target="_blank" rel="noopener noreferrer">semantic-release</a> approach where it'll cut a release with every PR merge by default based on the commit message. Overall, I think Changesets is a bit simpler by collecting markdown files and tracking along with a Release Preview PR. Simply merge that PR when you're ready to release and be on your way.</p>
<p>Don't worry about when to merge. Instead always be shippin' / mergin'!</p>
<a id="automated-changelogs-and-release-notes" href="#automated-changelogs-and-release-notes"><h2>Automated Changelogs and Release Notes</h2></a>
<p>I really like the automated changelogs and release notes. As mentioned above, Changesets allow you to write descriptions of each change in markdown. I typically add code examples in my release notes, as it makes it easier for folks to "see" the changes.</p>
<p>Part of my job is to work with teams and communicate changes that go into our Design System. Putting detailed information in a changeset makes my life a bit easier, even when your documentation is really great. Now it's in the changelog, the release note, and the docs.</p>
<p>Want an example? <a href="https://github.com/CrowdStrike/ember-toucan-core/releases/tag/%40crowdstrike%2Fember-toucan-form%400.3.0" target="_blank" rel="noopener noreferrer">Here you go</a> - check this out.</p>
<p>Releases link directly to the pull request and commit(s) for each change as well. This makes it helpful for consumers to get more information and context. I dig it.</p>
<a id="other-niceties" href="#other-niceties"><h2>Other Niceties</h2></a>
<p>When you're in a monorepo setting, if a few of your packages are interdependent, it'll orchestrate the releases properly between all of them. This has been really nice. No more worrying about having to manually bump versions or ensure changes are in lock step with one another.</p>
<p>My favorite thing is that they aren't clever with how they work — there aren't any surprises if you modify their generated changeset markdown files. Things flat out work really well and it's pretty difficult to find yourself in a weird state. Accidently set something as a <code><span><span style="color:var(--sh-identifier)">patch</span></span></code>, but it should be a <code><span><span style="color:var(--sh-identifier)">major</span></span></code>? Go in and edit the markdown file and that's it. Once your changes are in <code><span><span style="color:var(--sh-identifier)">main</span></span></code>, it'll update your Release Preview. I really appreciate the simplicity with Changesets.</p>
<p>Using GitLab and private registries? Check out <a href="https://medium.com/@haleywardo/simplify-package-versioning-with-changesets-for-gitlab-private-registries-4306762c9e29" target="_blank" rel="noopener noreferrer">this step-by-step guide</a> from <a href="https://www.linkedin.com/in/haley-ward/" target="_blank" rel="noopener noreferrer">Haley Ward</a>.</p>
<a id="that-s-all-i-ve-got" href="#that-s-all-i-ve-got"><h2>That's All I've Got</h2></a>
<p>Anyway, that's all I got! Big fan of Changesets. There are some other tools like <a href="https://github.com/embroider-build/release-plan" target="_blank" rel="noopener noreferrer">release-plan</a> that look really promising. I'll report back as I play with it. Thanks and see ya next time!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[It's Time to Disrupt Visual Regression Testing]]></title>
            <link>https://tonyward.dev/articles/visual-regression-testing-disruption</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/visual-regression-testing-disruption</guid>
            <pubDate>Fri, 11 Oct 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I've been using visual regression tools off and on since 2017 when building Design Systems. Seven years later in 2024, things haven't changed a whole lot. While the current offerings are better than they were in 2017, there's a lingering cross-operating system problem, which is where the paid offerings come in. Paying for visual regression testing can be difficult in different organizations, so it's tempting to look at open source solutions. It turns out the major open source options have drawbacks as well.</p>
<p>I think it's time the visual regression testing space was disrupted. I understand visual regression testing is an incredibly complex problem to solve, but I believe there's a clever solution somewhere on the horizon.</p>
<blockquote>
<p><strong>Update October 15, 2024:</strong> A few days after posting this, Haley <a href="https://medium.com/@haleywardo/streamlining-playwright-visual-regression-testing-with-github-actions-e077fd33c27c" target="_blank" rel="noopener noreferrer">wrote some GitHub Actions</a> to implement our idea. It's a proof of concept and could use some tuning, but hey, progress!</p>
</blockquote>
<blockquote>
<p><strong>Update April 12, 2025:</strong> I ended up <a href="/articles/visual-regression-testing-disruption-2">writing about another solution</a> we adopted on our open source Web Component Design System with links to GitHub Actions you can reference!</p>
</blockquote>
<a id="what-is-visual-regression-testing?" href="#what-is-visual-regression-testing?"><h2>What is Visual Regression Testing?</h2></a>
<p>You're likely already writing unit and integration tests, maybe even with <a href="https://www.tonyward.dev/articles/design-system-code-coverage" target="_blank" rel="noopener noreferrer">100% code coverage</a>, for your Design System components. How are you verifying your components <em>visually</em> as changes are made? This is where visual regression testing comes in.</p>
<p>It takes screenshots of your components with each pull request so you can be notified when something changes to a component's presentation. Did you update a CSS variable that now changes the Button's border to be red? Was that intentional? You'll get notified on your pull request and can make that decision. It's another automation tool that helps build better Design Systems.</p>
<p>As Design System authors, we care how things visually appear to the user. By testing only the <em>functionality</em> in our unit and integration tests, you're only covering one area of your work — the other area is the presentational piece. Does this component match the designs? Did you inadvertently break another component's visual presentation by making a change to shared styles?</p>
<p>Unit and integration testing tools don't allow for these types of assertions — they can only exercise the component. Without verifying how a component is presented, you're left with manual visual regression testing. And often times, visual bugs slip through the cracks — we are all human. We make mistakes! Why not let the robots help us cut down on those mistakes as much as possible?</p>
<p>What options are currently available in this space? I've listed some of the major players. There are quite a few more out there, but I didn't want this article to become massive. Let's dive in from a high level.</p>
<a id="chromatic" href="#chromatic"><h2>Chromatic</h2></a>
<p>Chromatic is a great product. It is tightly integrated with Storybook. A personal drawback to me was being reliant on Storybook's <code><span><span style="color:var(--sh-identifier)">play</span></span></code> function to interact with a component, which sometimes leads to extra stories being written <em>only</em> for tests. The <code><span><span style="color:var(--sh-identifier)">play</span></span></code> function becomes massive if you rely on interactions to test different visual states.</p>
<p>The good news is that Chromatic now plays nicely with <a href="https://www.chromatic.com/playwright" target="_blank" rel="noopener noreferrer">Playwright</a> and allows you to do <a href="https://www.chromatic.com/docs/playwright/targeted-snapshots/" target="_blank" rel="noopener noreferrer">targeted snapshots</a>. This looks pretty promising! It's especially appealing if you're already using Playwright.</p>
<p>It integrates well with CI. For the Standard plan, you get to test across all major browsers. Their UI looks really nice. The Enterprise option includes single sign on. There's a lot of great stuff here. Overall, Chromatic is a very solid choice, but has the drawback of being a paid service.</p>
<a id="percy" href="#percy"><h2>Percy</h2></a>
<p>Percy has been around for quite some time and was bought by BrowserStack in 2020 (congrats 🎉!). I was using it back in 2017 with Toyota's Loom Design System. It has all of the advantages of Chromatic listed above. The thing I liked about it was how it met you where your tests were at.</p>
<p>You import their <code><span><span style="color:var(--sh-identifier)">percySnapshot</span></span></code> function. You call that function within your existing tests. You run their <code><span><span style="color:var(--sh-identifier)">percy</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">exec</span></span></code> command to run your tests, but then that's it. It's very nice. Percy is also another solid choice, but like Chromatic, costs money.</p>
<a id="playwright" href="#playwright"><h2>Playwright</h2></a>
<p>Playwright's component testing is <a href="https://playwright.dev/docs/test-components" target="_blank" rel="noopener noreferrer">still experimental</a> as of the time of this writing. Due to that, some organizations may not choose it for writing unit and integration tests, but it <em>is</em> great for end-to-end tests. There's no denying that!</p>
<p>Playwright offers <a href="https://playwright.dev/docs/test-snapshots" target="_blank" rel="noopener noreferrer">visual comparisons</a> out of the box. On the surface, this sounds great! But it has a major issue that is very well documented in GitHub issues and discussions ( <a href="https://github.com/microsoft/playwright/issues?q=is%3Aissue+toHaveScreenshot+is%3Aclosed+" target="_blank" rel="noopener noreferrer">1</a>, <a href="https://github.com/microsoft/playwright/issues/20954#issuecomment-1651998612" target="_blank" rel="noopener noreferrer">2</a>, <a href="https://github.com/microsoft/playwright/issues/8161#issuecomment-1369230603" target="_blank" rel="noopener noreferrer">3</a> ).</p>
<p>Playwright and other tools have an issue with headless Chrome and an OS dependency. As an example, if you're developing on a Mac, you check in your visual regression baseline images for Chrome. Then CI runs in Linux, comparing against those baseline images taken in MacOS, and guess what? Your tests fail. Every time. What's the recommended solution?</p>
<blockquote>
<p>"Your CI environment is different from local one, it's probably a linux box while your local machine is a mac. The rendering is very sensitive to the host environment (and even to whether your mac has power adapter plugged in or not), it may be that on the CI machine the page uses different fonts too. To make the visual tests reproducible we recommend generating expectations on the same machine where you run the tests. There is a also a bunch of options that allow make toHaveScreenshot less strict, you can toy with those."</p>
</blockquote>
<p>— <a href="https://github.com/microsoft/playwright/issues/28741#issuecomment-1864976229" target="_blank" rel="noopener noreferrer">Playwright GitHub Issue</a></p>
<p>I'll chat more about this in a moment, but man, this was a bummer. At first, I was <em>so</em> impressed with Playwright's visual testing. Then I pushed things to CI and it all fell apart. I feel like I'm knocking on Playwright a bit too much here, but I really do appreciate everyone who works on the project and think it's really great solution! I wrote Selenium + WebDriverIO tests for many years back in 2012 and I really wish we had Playwright back then. But there's no denying the visual testing area could use some improvement, either via documentation or technical improvements.</p>
<a id="so-what-s-the-problem-tony?" href="#so-what-s-the-problem-tony?"><h2>So What's the Problem, Tony?</h2></a>
<p>To me, the problem with Percy and Chromatic is that it's from a third-party. Tinfoil-hat-thinking maybe, but these services take screenshots of your products, so there could be some hesitancy in working on a top-secret-soon-to-be-announced-feature, screenshotting it, and uploading it to a third-party service. Some organizations may, understandably, feel uneasy about that.</p>
<p>The other issue with these offerings are that even though I believe the pricing is <em>very</em> reasonable, it can sometimes be a no-go for approval within an organization. The manual labor involved to stop visual regressions is <em>very</em> costly. Engineering time, QA time, not catching a bug and letting your customer see it is embarrassing, and the list goes on. But <em>quantifying</em> this cost can be difficult when suggesting the org pays for a service. It's tough.</p>
<p>These are the two bottlenecks I've hit since 2017 when discussing visual regression testing. It's less of a technical problem and more of an organizational approval roadblock. Once again, totally understandable! Every organization operates a bit differently as far as approvals for this type of stuff goes as well.</p>
<p>How about Playwright? It's not uploading things to a third-party and all of the images are stored in the repository you're already working in. Well, as mentioned above, it ain't perfect either. You have to make some tradeoff decisions when it comes to developer experience.</p>
<p>Requiring the use of docker, as recommended in the GitHub comment above, to get around the browser/OS issue is a bummer. Docker adds complexity, especially for less experienced engineers. It can be a pain to use. You also need to ensure your local and CI images are in sync with one another.</p>
<p>For some, using docker may be a completely acceptable solution. Maybe you're already paying for it. That's totally fine! My issue with the docker approach is that we keep adding more technology tooling to solve a single testing problem. We keep throwing different technology onto the pile, adding new tools and complexities. It'd be nice to not have to do that in an ideal world. It'd be great if we could continue to use the existing tooling we are already familiar with.</p>
<p>Back to Playwright's OS issue. You could switch your CI over to <a href="https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories" target="_blank" rel="noopener noreferrer">MacOS boxes</a> (<code><span><span style="color:var(--sh-identifier)">macos</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">latest</span></span></code>), or whatever OS everyone on your team uses, but that's not ideal for folks contributing that may not have access to that OS. For example, if your repository is a public library and your team develops solely on Mac, then a Linux user comes along, you'll hit issues when they go to check in their snapshot screenshot. There are ways around this, of course, but it's a process.</p>
<p>Another solution I've heard is to adjust Playwright's <a href="https://playwright.dev/docs/test-snapshots#maxdiffpixels" target="_blank" rel="noopener noreferrer">maxDiffPixels</a>. Adjusting thresholds, in my opinion, is a no-go for Design Systems. We <em>need</em> to know when a 1-pixel border color changes. We can't adjust our fidelity bar, as it's extremely important for our work. If you're working <em>outside</em> of a Design System and at an app-level, adjusting the threshold may work just fine for you! But once again, in Design System land, I personally feel this is unacceptable.</p>
<p>Am I nitpicking? Maybe. But this has been my experience over the last 7 years.</p>
<a id="disruption" href="#disruption"><h2>Disruption</h2></a>
<p>New JavaScript frameworks pop up frequently. I'd love to see some disruption in the visual regression testing space. Maybe something out there already exists and I've missed it? Definitely let me know!</p>
<p>My ideal solution would be an open source (of course!), free offering that is completely self-managed. One that does not require a subscription or any payment. Although I know I'd throw some cash at an open source project if one came along!</p>
<p>A solution where images are not uploaded to a third-party. This completely removes the middle man situation outlined above where your top secret features are now being uploaded to some service. If you want to push your images or test reports somewhere, go for it, but you should be in control of where things will live.</p>
<p>A solution that doesn't require docker, or any other heavy dependencies. Adding the overhead of docker or any other containerization option isn't ideal.</p>
<p>A solution that meets users where their tests are at - if you've already got a bunch of tests in vitest, great. If you're using jest, awesome. Forcing folks to migrate to Playwright or Storybook testing is a big lift and an instant non-starter for many. Being test-framework agnostic would be great.</p>
<p>A solution that can run across all major browsers. Even across the major OSes if you'd like.</p>
<p>I have an idea I've been cooking up along with <a href="https://www.linkedin.com/in/haley-ward/" target="_blank" rel="noopener noreferrer">Haley Ward</a> that is purely GitHub Actions based. By pushing the work solely to CI, you get around the cross-OS issues mentioned above. The thought is that you can use whatever testing framework and visual testing tool you want — <a href="https://playwright.dev/docs/test-snapshots" target="_blank" rel="noopener noreferrer">Playwright</a>, <a href="https://github.com/americanexpress/jest-image-snapshot" target="_blank" rel="noopener noreferrer">jest-image-snapshot</a>, <a href="https://www.npmjs.com/package/@web/test-runner-visual-regression" target="_blank" rel="noopener noreferrer">web-test-runner</a>, whatever. As long as it generates some sort of report.</p>
<p>That report can then be optionally deployed somewhere via a GitHub Action or you can pull down the GitHub Asset yourself to view the diff. The images can be checked in to source control, or not, it's up to you. It'll rely on GitHub commments and reactions to approve or deny the visual changes. It'll block pull requests until the appropriate amount of visual approvals occur.</p>
<div><div><img alt="A gif from it's always sunny." loading="lazy" width="480" height="304" decoding="async" data-nimg="1" style="color:transparent" src="/_next/static/media/lol.04xgwy6k7c425.gif"></div></div>
<p>We'll see where we land. Maybe the above gif is me. It could be way more complex than we think and we could miss our mark. Oh well, at least we're trying! Stay tuned!</p>
<p>To wrap things up, visual regression testing plays a crucial role, particularly for Design Systems. It ensures visual bugs are caught early, before they reach users. Although visual regression tools can sometimes be avoided due to their cost and complexity, I'm hopeful that this field will evolve and become more accessible over time. As always, thanks for reading. See you soon!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Design System Code Coverage]]></title>
            <link>https://tonyward.dev/articles/design-system-code-coverage</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/design-system-code-coverage</guid>
            <pubDate>Mon, 23 Sep 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>If you're building a Design System, you're working on the foundational building blocks to accelerate the development of features and entirely new products for teams. Other teams count on you to provide high quality and well tested code. Let's discuss testing and why I think it's reasonable to enable a high code coverage metric threshold naturally, by writing meaningful tests, which you're likely already doing!</p>
<p>Starting a new Design System with a focus on tests sets a strong foundation for quality and reliability. By ensuring that every piece of code is covered, teams can catch bugs early, prevent regressions in the future, and confidently refactor as the codebase evolves. This creates a culture of accountability, where developers are encouraged to write robust, meaningful tests, ultimately leading to a more maintainable and stable codebase. Just like Design Systems, it will take more time upfront, but the long-term benefits far outweigh the initial investment, resulting in fewer issues and smoother development.</p>
<blockquote>
<p>For the rest of this article, I'll be honing in on Design Systems focusing on atoms and molecules. It's important to understand atoms and molecules when it comes to <a href="https://atomicdesign.bradfrost.com/chapter-2/" target="_blank" rel="noopener noreferrer">atomic design methodology</a>. I'm talking about buttons, inputs, form elements, modals, dropdowns, etc., — nothing more complex than that! The higher you go up the hierarchy, and especially as you get into application code, squeaking out the last 10-20% of coverage may not be worth the time and investment. So let's focus on a micro-level for the rest of this article.</p>
</blockquote>
<a id="the-case-for-code-coverage" href="#the-case-for-code-coverage"><h2>The Case for Code Coverage</h2></a>
<p>We all understand the importance of testing, but tracking code coverage has mixed opinions. I used to believe that focusing on coverage metrics could lead to writing tests simply to meet those numbers, without ensuring they were meaningful. My view at the time was that it was better to prioritize meaningful tests over coverage percentages.</p>
<p>However, the more I thought about this, the more I saw advantages to code coverage metrics for Design Systems specifically. As developers, writing meaningful tests is an inherent part of our job. It's entirely possible to create tests that are both meaningful <em>and</em> provide the coverage we need — these goals are not mutually exclusive, though they're often perceived that way. Given enough time, any developer would naturally write tests that are both meaningful and comprehensive, covering all necessary paths, because that's the right thing to do.</p>
<p>For Design Systems, the rationale is even more straightforward. When focusing on atoms and molecules, these components are inherently less complex than other areas of Software Development due to their hierarchy. Their code shouldn't involve extremely complex logic — if they do, you likely aren't following atomic design principles. Given this, I believe hitting a high code coverage metric is very attainable.</p>
<a id="requirement-a-strong-testing-culture" href="#requirement-a-strong-testing-culture"><h2>Requirement: A Strong Testing Culture</h2></a>
<p>I thought it'd be important to touch on something before we progress further...</p>
<p>You may see that your team is writing crappy tests that get the coverage you are looking for, but the tests aren't doing anything meaningful like I described above. Unfortunately automation can't catch these types of problems. This is more of a cultural team issue, or maybe even an organizational culture issue. It could mean that not everyone understands the importance of tests or how to write <em>meaningful</em> tests. If you notice this happening, it's probably wise to pull your team together and come to some agreement on principles for testing that everyone can get behind.</p>
<p>For the rest of this article, I'm making the assumption that people on your team aren't writing tests only to make CI green. I'm assuming that folks care about writing tests to build better products and build confidence in the code y'all write. I'm assuming best intentions, even in a corporate environment where it may be difficult to spend the time to write tests. I'm assuming there are people who actually review the pull requests being put up to ensure a certain quality and consistency to help improve the codebase overall.</p>
<p>Lots of assumptions, but for folks who don't just punch the clock day in and day out and really want to build great products, this should all be pretty obvious. It's still worth noting this to frame my personal point of reference.</p>
<p>Alright, let's continue!</p>
<a id="an-example" href="#an-example"><h2>An Example</h2></a>
<p>By committing to writing tests with some sort of coverage metric enabled, even when it takes more time, you're building a stronger Design System, stronger products using your building blocks, and becoming a more experienced developer. I'm not saying you should be spending all night writing tests, of course, but you <em>should</em> be making time throughout your regular business hours to write them. Tests lead to fewer bugs down the road. Additionaly, refactoring is a natural and necessary part of our jobs. With test coverage, developers can refactor confidently, knowing any regressions or unintended changes will likely be caught.</p>
<p>Let's look at an example component and how we might write some tests for it.</p>
<pre><code><span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">interface</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">ButtonProps</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">extends</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">ComponentPropsWithoutRef</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">children</span><span style="color:var(--sh-sign)">?</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">ReactNode</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-sign)">?</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">ComponentType</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">SVGProps</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-class)">SVGSVGElement</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">variant</span><span style="color:var(--sh-sign)">?</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">primary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">secondary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">tertiary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// I'll be using clsx below.</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// If you aren't familiar it's an easy way to combine multiple classes</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// based on conditionals.</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// https://github.com/lukeed/clsx#readme</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">forwardRef</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-class)">HTMLButtonElement</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">ButtonProps</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">(</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">(</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">children</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">className</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Icon</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">variant</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">primary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">rootProps</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">ref</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">button</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">ref</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">ref</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">clsx</span><span style="color:var(--sh-sign)">(</span></span>
<span><span style="color:var(--sh-space)">          </span><span style="color:var(--sh-identifier)">classes</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">root</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">          </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">            </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-identifier)">classes</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">primary</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">variant</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">primary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">            </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-identifier)">classes</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">secondary</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">variant</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">secondary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">            </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-identifier)">classes</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">tertiary</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">variant</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">tertiary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">          </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">          </span><span style="color:var(--sh-identifier)">className</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">rootProps</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">        </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">Icon</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&amp;</span><span style="color:var(--sh-sign)">&amp;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Icon</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">role</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">img</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">aria-</span><span style="color:var(--sh-property)">hidden</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">true</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">classes</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">icon</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-jsxliterals)">}</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">        </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">children</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">      </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">button</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Above you have a simple Button component. What tests would you write given the code above? Take a moment to jot down some ideas and come back before continuing!</p>
<hr>
<p>All done? Sweet! Here's what comes to mind for me:</p>
<ul>
<li>Verify the Button can render the children provided.</li>
<li>Verify the Button can render a provided icon.</li>
<li>Verify the Button applies the provided <code><span><span style="color:var(--sh-identifier)">className</span></span></code>.</li>
<li>Verify the Button renders each variant (<code><span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">primary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">secondary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">tertiary</span><span style="color:var(--sh-string)">'</span></span></code>).</li>
<li>Verify the Button can receive a <code><span><span style="color:var(--sh-identifier)">ref</span></span></code> and forwards it.</li>
<li>Verify the Button spreads root properties.</li>
</ul>
<p>To some, it may seems like a lot of tests for a simple Button. But you'd probably write most or all of these yourself anyway due to the component API provided. And guess what? You'll get 100% test coverage! So the meaningful tests you'd already be writing get you to a high code coverage metric naturally. Since we're dealing with an atom, there's not much logic, and it's mostly all markup and conditionally applying classes.</p>
<p>Button is one of the least complex cases, but even for more complex components, you'd want to write tests for given paths. For example, say you have a Dropdown component that relies on keyboard interaction. You'd want to write tests that verify those interactions work as expected, because if you don't, you won't be confident that your code works properly.</p>
<p>For UI tests, I've always looked at them as providing different inputs, receiving output, and then asserting if the output is what you'd expect. The inputs are attributes or properties, and the output is UI. Since we don't have to deal with data fetching, local storage, or complex state, it means less complexity in our test cases.</p>
<p>This is why I feel as though 100% test coverage for Design Systems focusing on this layer is very attainable. You care about what the output is. In application code, you may not care about the HTML that is ultimately rendered, but for Design System authors, you do. This is what we provide to our users directly, and we care about the markup we are providing, but consumers may not. So I look at Design System tests through a slightly different lens than if I were building a full application and writing tests for that.</p>
<a id="generative-ai-can-help!" href="#generative-ai-can-help!"><h2>Generative AI Can Help!</h2></a>
<p>Writing tests is a bit like brushing your teeth. We all know we <em>should</em> do it. But sometimes we're busy. Or maybe we'd rather be doing something else. Or maybe we're stressed out and have to take care of another thing. That's where using technology can help.</p>
<p>If your organization allows it, Generative AI can do a lot of the lifting here for you. For kicks, let's use our Button component above and see what tests ChatGPT would write for it.</p>
<p>Here was my prompt.</p>
<blockquote>
<p>"Given the following React code, what would you test?"</p>
</blockquote>
<blockquote>
<p>For this Button component, you would want to test the following scenarios:</p>
<ol>
<li>Rendering Tests
Default Rendering: Ensure that the component renders properly without any children or icon.
Children Rendering: Test that when children are passed, they are rendered inside the button.
Icon Rendering: Ensure that if an icon is passed, the icon component is rendered.</li>
<li>Variant Prop Tests
Primary Variant: Ensure the button applies the correct primary CSS class when the variant="primary" prop is passed (default behavior).
Secondary Variant: Ensure the button applies the correct secondary CSS class when variant="secondary" is passed.
Tertiary Variant: Ensure the button applies the correct tertiary CSS class when variant="tertiary" is passed.</li>
<li>Icon Prop Tests
Icon Present: Test that the Icon component is rendered when an icon prop is provided.
Icon Role and Accessibility: Ensure that the Icon has the correct role="img" and aria-hidden attributes for accessibility purposes.</li>
<li>ClassName Prop
Custom ClassName: Test that a custom className passed to the button is correctly applied along with the default classes.</li>
<li>Forwarding Refs
Forward Ref Test: Ensure that the ref is forwarded correctly to the underlying button element.</li>
<li>Root Props Spread
Event Handlers and Attributes: Test that any extra props (like onClick, id, data-* attributes, etc.) passed through rootProps are spread to the underlying button element.</li>
<li>Snapshot Testing
Create snapshots to ensure the component renders the correct structure and classes based on the props passed (e.g., for each variant, with or without icon).
Example Test Cases</li>
</ol>
</blockquote>
<pre><code><span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">fireEvent</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">@testing-library/react</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">@testing-library/jest-dom</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">./Button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Default rendering test</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">test</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">renders the button with default props</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toBeInTheDocument</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveClass</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">primary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Children rendering test</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">test</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">renders children inside the button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Click Me</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveTextContent</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">Click Me</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Icon rendering test</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">test</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">renders the icon when passed</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Icon</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">svg</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">data-</span><span style="color:var(--sh-property)">testid</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">icon</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-jsxliterals)">;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">  render(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">icon</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">Icon</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Click Me</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByTestId</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">icon</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toBeInTheDocument</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveAttribute</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">role</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">img</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Variant tests</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">test</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">applies the correct class for the primary variant</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">variant</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">primary</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Primary</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveClass</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">primary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Forwarding ref test</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">test</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">forwards ref to the button element</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">ref</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">createRef</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-class)">HTMLButtonElement</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">ref</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">ref</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Click Me</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">ref</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">current</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toBeInstanceOf</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">HTMLButtonElement</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<blockquote>
<p>These tests cover functionality, accessibility, and styling for the component.</p>
</blockquote>
<p>Cool! I'd say those are some pretty decent tests. If nothing else, it gives you an excellent place to start.</p>
<hr>
<p>Now I asked it how we'd get 100% test coverage.</p>
<p>Here was my prompt.</p>
<blockquote>
<p>"Given the following React component, can you write me tests that will yield 100% code coverage?"</p>
</blockquote>
<blockquote>
<p>To achieve 100% code coverage for the Button component, the tests should cover:
Rendering with default props: Check that the button renders with default variant (primary).
Rendering with different variants: Ensure that the correct class is applied for primary, secondary, and tertiary.
Rendering with an icon: Verify that the icon renders correctly when passed, and check accessibility attributes.
Forwarding ref: Ensure that the ref is forwarded correctly.
Passing arbitrary props: Test that additional props are spread to the button element.
Rendering with className: Check that the className prop is correctly merged.</p>
<p>Here's how you can write tests using @testing-library/react and jest:</p>
</blockquote>
<pre><code><span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">@testing-library/react</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">userEvent</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">@testing-library/user-event</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Button</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">ButtonProps</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">./Button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">react</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Mock the clsx and classes modules (assuming you have them)</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">jest</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">mock</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">clsx</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">args</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">string</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">args</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">filter</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">join</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">classes</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">root</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">root</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">primary</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">primary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">secondary</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">secondary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">tertiary</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">tertiary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">icon</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Mock SVG icon component</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">MockIcon</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">props</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">SVGProps</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-class)">SVGSVGElement</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">svg</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">props</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-identifier)">describe</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">Button component</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">it</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">renders with default props</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Click Me</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">/click me/i</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveClass</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">root primary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">it</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">renders with </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">secondary</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)"> and </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">tertiary</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)"> variants</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">variant</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">secondary</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Secondary Button</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">buttonSecondary</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">/secondary button/i</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">buttonSecondary</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveClass</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">root secondary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">variant</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">tertiary</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Tertiary Button</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">buttonTertiary</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">/tertiary button/i</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">buttonTertiary</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveClass</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">root tertiary</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">it</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">renders with an icon</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">icon</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">MockIcon</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Icon Button</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">/icon button/i</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">img</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">hidden</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">true</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toBeInTheDocument</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">icon</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveClass</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">icon</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">it</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">does not render icon when icon prop is not provided</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">No Icon</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">queryByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">img</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">not</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">toBeInTheDocument</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">it</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">forwards ref to the button element</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">ref</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">createRef</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-class)">HTMLButtonElement</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">ref</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">ref</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Ref Button</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">ref</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">current</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toBeInstanceOf</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">HTMLButtonElement</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">it</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">spreads additional props onto the button element</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">data-</span><span style="color:var(--sh-property)">testid</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">custom-prop</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Custom Props Button</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByTestId</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">custom-prop</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toBeInTheDocument</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">it</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">accepts and merges className prop</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">custom-class</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Custom Class Button</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">/custom class button/i</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveClass</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">root primary custom-class</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">it</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">can handle click events</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">async</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">onClick</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">jest</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">fn</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">onClick</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">onClick</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Click Me</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">Button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">screen</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">getByRole</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">/click me/i</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">await</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">userEvent</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">click</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">expect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">onClick</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">toHaveBeenCalledTimes</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<blockquote>
<p>Explanation:
Default props test: Verifies that the Button renders with the primary variant by default.
Variant tests: Verifies the correct classes for secondary and tertiary variants.
Icon test: Ensures that the icon is rendered correctly with the appropriate class and accessibility attributes.
No icon test: Ensures the absence of the icon if the prop isn't passed.
Ref forwarding test: Checks if the ref is forwarded correctly to the button element.
Props spreading test: Verifies that arbitrary props (like data-testid) are correctly passed to the button.
className merging test: Ensures that the className prop is merged with internal classes.
Click handler test: Simulates a user click and ensures that the onClick handler is called.</p>
<p>These tests should provide 100% coverage for the Button component.</p>
</blockquote>
<hr>
<p>So what's the missing piece our AI overlords were missing in the first prompt? Looks like testing the different variants, adding a className, and verifying one can spread properties onto it. Not 100% coverage out of the box, but not bad, ChatGPT, not bad.</p>
<p>And if you're <em>really</em> interested in maximizing the coverage, it seems totally capable of doing so by writing another prompt, as seen by directly above. And if you can't use GenAI, filling in these test gaps manually would take maybe 5-10 minutes.</p>
<a id="complex-scenarios" href="#complex-scenarios"><h2>Complex Scenarios</h2></a>
<blockquote>
<p>"But this super edge case is <em>so</em> hard to test, why should I write a test for it?"</p>
</blockquote>
<p>If it's an edge case you're worried about, that's exactly why testing it is so crucial. Without a test, it's far more likely to experience regressions. Additionally, without proper testing, it's easy to lose track of why a specific piece of code behaves the way it does. Writing a test provides that valuable context in a documenting way, while also ensuring we don't regress as changes are made.</p>
<p>With tools like <a href="https://sinonjs.org" target="_blank" rel="noopener noreferrer">sinon</a>, you can simulate any state you need for your component, no matter how complex. If your tests are getting extremely complex, it may be a sign that your component API needs some work. Would composition help break things up into meaningful pieces? Maybe it takes a bit more time to get the last 10% of code coverage, but if you can cover all <em>known</em> flows, it will provide a confidence boost for your team as things change in the future.</p>
<a id="why-tests-don-t-get-written" href="#why-tests-don-t-get-written"><h2>Why Tests Don't Get Written</h2></a>
<p>From my experience, tests don't get written due to inexperience, lack of time, or a pressure to deliver features. It's easier to only write the code for the feature in the moment. It takes more time to write tests. But the tests are <em>equally</em> as important as the application code.</p>
<p>If you're finding you need to skip writing tests due to time or other pressures, it's important to bring it up with your manager. Increase the estimation of every task by adding complexity. Setup a strong testing culture within your team. Start tracking the number of bugs before and after having tests to present a case to management that testing is important. Bring up cases where tests saved your bacon. It's up to you and your team to uphold a strong testing culture, despite external pressures. You can do it! I believe in you!</p>
<a id="don-t-wait" href="#don-t-wait"><h2>Don't Wait</h2></a>
<p>If you're setting up a new codebase, it's important to <em>start</em> with a strong testing culture. Don't wait to get tests added until some future point. Right after you setup the repository, immediately setup a testing environment/framework. As time progresses, old code rarely sees tests written. It's too late at that point. By having a testing framework in place from the beginning and enforcing code coverage metrics, it'll set the standard from day one.</p>
<a id="100-coverage-doesn-t-mean-you-ll-have-0-bugs" href="#100-coverage-doesn-t-mean-you-ll-have-0-bugs"><h2>100% Coverage Doesn't Mean You'll Have 0 Bugs</h2></a>
<p>One argument on the internet against 100% test coverage is that the coverage metric doesn't mean your code is bug free. Of course it doesn't! We're all humans writing code — we make mistakes. Even AI makes mistakes writing code — just ask ChatGPT to write something pretty complex for you. It will likely make a few mistakes too, or not account for certain cases that are specific to your business needs.</p>
<p>I find this argument against the 100% coverage metric a bit silly. You obviously can still have logic bugs with 100% test coverage. You likely have a bug because you didn't account for a particular flow. When this happens, in my experience, it means you need to write <em>more</em> code in your component to handle this case, which guess what? Means you need to write more tests. So you'll fix the issue and write meaningful tests for this new path. And your tests will likely get you right back up to 100% coverage.</p>
<p>100% test coverage doesn't mean "full, complete coverage" — it means that every line of code is accounted for in some way. It's a metric. People value metrics differently. But once again, the real key to all of this is writing meaningful tests.</p>
<a id="give-it-a-shot" href="#give-it-a-shot"><h2>Give it a Shot</h2></a>
<p>Teams rely on you and your Design System to sweat the details and provide high quality, well tested code. Let's not let them down!</p>
<p>While achieving high test coverage requires upfront investment in time and resources, the payoff in reliability, maintainability, and developer confidence makes it a worthwhile endeavor. You're likely already writing meaningful tests, and if your Design System focuses on atoms and molecules, achieving 100% test coverage is a lot more achievable than you might think! The next time you start a new Design System, try setting the coverage threshold to 100% for a few months and see what you think. Do you feel more confident in your codebase? If not, at least you gave it a try to see what it'd be like!</p>
<p>If you've made it this far, I thank you for sticking with me. Have a good one!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Closed Shadow Roots for Design Systems]]></title>
            <link>https://tonyward.dev/articles/web-components-closed-roots-for-design-systems</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/web-components-closed-roots-for-design-systems</guid>
            <pubDate>Thu, 29 Aug 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I've already discussed why I believe <a href="https://www.tonyward.dev/articles/web-components-design-system" target="_blank" rel="noopener noreferrer">Web Components are great for Design Systems</a>. It's becoming clear to me that setting the <code><span><span style="color:var(--sh-identifier)">mode</span></span></code> of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode" target="_blank" rel="noopener noreferrer">ShadowRoot</a> property for these components to <code><span><span style="color:var(--sh-identifier)">closed</span></span></code> rather than the default of <code><span><span style="color:var(--sh-identifier)">open</span></span></code> has a lot of advantages for Design Systems as well. Don't agree? Humor me for a bit, please!</p>
<p>Let's talk a bit about the Shadow DOM and custom elements. Here's a great quote from MDN that describes the general philosophy of custom elements and Web Components.</p>
<blockquote>
<p>"An important aspect of custom elements is encapsulation, because a custom element, by definition, is a piece of reusable functionality: it might be dropped into any web page and be expected to work. So it's important that code running in the page should not be able to accidentally break a custom element by modifying its internal implementation."</p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM" target="_blank" rel="noopener noreferrer">MDN</a></li>
</ul>
<p>This is what we want from Design System components as well. We want them to contain a reusable piece of functionality (HTML, JS, CSS) that can be placed anywhere and work without any external dependencies. We want to use our components in any JavaScript framework, or none at all, and ensure that application code does not negatively impact our components by breaking styling or behavior with JavaScript.</p>
<p>One of the big advantages of using Web Components for this is around encapsulation. Encapsulation is extremely important. It ensures that users don't get to mess with our components unless we provide knobs for doing so. Encapsulation promotes consistency. It also ensures less buggy code. MDN believes so as well.</p>
<blockquote>
<p>"Without the encapsulation provided by shadow DOM, custom elements would be impossibly fragile. It would be too easy for a page to accidentally break a custom element's behavior or layout by running some page JavaScript or CSS. As a custom element developer, you'd never know whether the selectors applicable inside your custom element conflicted with those that applied in a page that chose to use your custom element."</p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#shadow_dom_and_custom_elements" target="_blank" rel="noopener noreferrer">MDN</a></li>
</ul>
<p>All of us who've worked on Design Systems for a reasonable amount of time know how important it is for changes to our components to be conversations/requests that turn into work on our end, rather than hacks on their end that immediately destroy the UI/UX or consistency of a product.</p>
<a id="hold-up-wtf-is-a-shadow-dom?" href="#hold-up-wtf-is-a-shadow-dom?"><h2>Hold up, wtf is a Shadow DOM?</h2></a>
<blockquote>
<p>"A set of JavaScript APIs for attaching an encapsulated "shadow" DOM tree to an element — which is rendered separately from the main document DOM — and controlling associated functionality. In this way, you can keep an element's features private, so they can be scripted and styled without the fear of collision with other parts of the document."</p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components" target="_blank" rel="noopener noreferrer">MDN</a></li>
</ul>
<p>I believe I caught Wes Bos on <a href="https://syntax.fm" target="_blank" rel="noopener noreferrer">Syntax.fm</a> referring to the Shadow DOM in a very simple way: it's a private, separate DOM that's inside of the regular DOM you're used to working with. If you're still a bit unclear of what a Shadow DOM is, read through that MDN link above. It does a fabulous job. It's important to understand what a Shadow DOM is before we proceed.</p>
<p>Now let's talk about encapsulation a bit more for CSS and JavaScript.</p>
<a id="1-styling-css-encapsulation-protections" href="#1-styling-css-encapsulation-protections"><h2>1. Styling / CSS Encapsulation Protections</h2></a>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#encapsulation_from_css" target="_blank" rel="noopener noreferrer">Documentation</a></li>
</ul>
<p>By default, Web Component styling is encapsulated because of the Shadow DOM. Styles from your application cannot override our component's styles unless those components expose either <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties" target="_blank" rel="noopener noreferrer">CSS variables</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part" target="_blank" rel="noopener noreferrer">parts</a> via their public API.</p>
<p>Let's look at a brief example. Say your application code had the following CSS.</p>
<pre><code><span><span style="color:var(--sh-comment)">/* Within your application */</span></span>
<span><span style="color:var(--sh-comment)">/* app.css */</span></span>
<span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* We _really_ want all buttons to be red! */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">red</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">!</span><span style="color:var(--sh-identifier)">important</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>And say you had a Lit Web Component with the following.</p>
<pre><code><span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">customElement</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">class</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">SimpleButton</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">extends</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">LitElement</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">static</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">css</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">    button {</span></span>
<span><span style="color:var(--sh-string)">      background-color: blue;</span></span>
<span><span style="color:var(--sh-string)">    }</span></span>
<span><span style="color:var(--sh-string)">  </span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">html</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)">&lt;button&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/button&gt;</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Whenever <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">simple-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">simple-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span></code> is rendered in the DOM, the page CSS <strong>does not</strong> affect nodes inside the shadow DOM due to the encapsulation protections. The button background color would remain blue.</p>
<p>Sweet. This is exactly what we want with Design System components - to make styling adjustments part of our component's public API, so that a consuming application's styles can't clobber <em>our</em> component styling.</p>
<p>We want styling to be very intentional and to allow only certain knobs to be adjusted so that the component doesn't end up looking like Frankenstein's monster. I discussed exposing these things via a component's public API already <a href="https://www.tonyward.dev/articles/web-components-design-system#:~:text=I%27ll%20elaborate%20more.)-,CSS%20variables,-Using%20CSS%20variables" target="_blank" rel="noopener noreferrer">in a previous post</a> if you're interested.</p>
<p>For a quick example, here's how we might expose a CSS variable for <code><span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span></span></code> to be adjusted.</p>
<pre><code><span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">customElement</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">class</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">SimpleButton</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">extends</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">LitElement</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">static</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">css</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">    button {</span></span>
<span><span style="color:var(--sh-string)">      background-color: var(--simple-button-background-color, blue);</span></span>
<span><span style="color:var(--sh-string)">    }</span></span>
<span><span style="color:var(--sh-string)">  </span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">html</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)">&lt;button&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/button&gt;</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-comment)">/* Within your application */</span></span>
<span><span style="color:var(--sh-comment)">/* app.css */</span></span>
<span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">root</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* Set the CSS variable to the color we want. */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">simple</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">red</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<a id="2-javascript-encapsulation-protections" href="#2-javascript-encapsulation-protections"><h2>2. JavaScript Encapsulation Protections</h2></a>
<p>Well, we've covered CSS. How about JavaScript?</p>
<p>JavaScript is a bit different, in that there <em>is</em> a way to interact with the internals of a component when its <code><span><span style="color:var(--sh-identifier)">mode</span></span></code> is set to <code><span><span style="color:var(--sh-identifier)">open</span></span></code>.</p>
<p>Taking a look at the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#encapsulation_from_javascript" target="_blank" rel="noopener noreferrer">MDN example</a>, there are some protections in place already. You can't query the <em>internals</em> of a Web Component by simply using <code><span><span style="color:var(--sh-identifier)">querySelector</span></span></code> or any other selection method. But you <strong>can</strong> get to them by using <code><span><span style="color:var(--sh-identifier)">host</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">shadowRoot</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span></span></code> as outlined in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#element.shadowroot_and_the_mode_option" target="_blank" rel="noopener noreferrer">the example</a> a bit further down.</p>
<p>If you use our examples from above, doing something like this won't work.</p>
<pre><code><span><span style="color:var(--sh-comment)">// Try to query for the &lt;button&gt; tag within `simple-button`</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">document</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-comment)">// &gt; null</span></span>
<span><span style="color:var(--sh-comment)"></span></span></code></pre>
<p>But if you do the following, you can.</p>
<pre><code><span><span style="color:var(--sh-comment)">// Query through the host's shadow root</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">document</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">shadowRoot</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-comment)">// &gt; &lt;button&gt;&lt;/button&gt;</span></span>
<span><span style="color:var(--sh-comment)"></span></span></code></pre>
<p>This is quite different than what we covered with CSS. With CSS, you have to intentionally expose variables or parts via the public API to allow for the consuming application to modify styling, whereas with JavaScript, you can break the encapsulation by simply targeting the <code><span><span style="color:var(--sh-identifier)">host</span></span></code> and using <code><span><span style="color:var(--sh-identifier)">shadowRoot</span></span></code> to query inside of it. What a bummer!</p>
<p>This is problematic, because it means the encapsulation promise we've discussed thus far is great for CSS, but not for JavaScript. But what if I told you there was a way that could help? It's not perfect either, but it's one more extra protection to keep the internals of your components private from outsiders.</p>
<a id="close-it-up" href="#close-it-up"><h2>Close it up</h2></a>
<blockquote>
<p>"If you don't want to give the page this ability, pass <code><span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">mode</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">closed</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">}</span></span></code> instead, and then shadowRoot returns <code><span><span style="color:var(--sh-class)">null</span></span></code>."</p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#element.shadowroot_and_the_mode_option:~:text=The%20%7Bmode%3A%20%22open%22%7D%20argument%20gives%20the%20page%20a%20way%20to%20break%20the%20encapsulation%20of%20your%20shadow%20DOM.%20If%20you%20don%27t%20want%20to%20give%20the%20page%20this%20ability%2C%20pass%20%7Bmode%3A%20%22closed%22%7D%20instead%2C%20and%20then%20shadowRoot%20returns%20null" target="_blank" rel="noopener noreferrer">MDN</a></li>
</ul>
<p>That's right - we can set the <code><span><span style="color:var(--sh-identifier)">mode</span></span></code> to closed for our component to restrict that access.</p>
<pre><code><span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">customElement</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">class</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">SimpleButton</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">extends</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">LitElement</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">static</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shadowRootOptions</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">LitElement</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">shadowRootOptions</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">mode</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">closed</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">static</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">css</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">    button {</span></span>
<span><span style="color:var(--sh-string)">      background-color: blue;</span></span>
<span><span style="color:var(--sh-string)">    }</span></span>
<span><span style="color:var(--sh-string)">  </span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">html</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)">&lt;button&gt;&lt;slot&gt;&lt;/slot&gt;&lt;/button&gt;</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Doing this provides a similar encapsulation as our CSS flow above. It ensures no one gets to put their hands in our cookie jar and interact via JavaScript with the internals of our components. It helps prevent misuse of our components.</p>
<p>If you've read through to the next paragraph, you'll notice the following.</p>
<blockquote>
<p>"However, you should not consider this (setting <code><span><span style="color:var(--sh-identifier)">mode</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">closed</span><span style="color:var(--sh-string)">'</span></span></code>) a strong security mechanism, because there are ways it can be evaded, for example by browser extensions running in the page. It's more of an indication that the page should not access the internals of your shadow DOM tree."</p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#element.shadowroot_and_the_mode_option:~:text=However%2C%20you%20should%20not%20consider%20this%20a%20strong%20security%20mechanism%2C%20because%20there%20are%20ways%20it%20can%20be%20evaded%2C%20for%20example%20by%20browser%20extensions%20running%20in%20the%20page.%20It%27s%20more%20of%20an%20indication%20that%20the%20page%20should%20not%20access%20the%20internals%20of%20your%20shadow%20DOM%20tree." target="_blank" rel="noopener noreferrer">MDN</a></li>
</ul>
<p>Users <em>can</em> programmatically set the mode back to <code><span><span style="color:var(--sh-identifier)">open</span></span></code> if they'd like, but to me this means they're signing themselves up for potential unknowns. It's a break in the contract.</p>
<p><strong>To follow the philosophies of Web Components above, users shouldn't care what the internals of your component are or are doing under the hood - they use your components because they want the UI/UX they provide.</strong> Just like how you treat <code><span><span style="color:var(--sh-identifier)">react</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">query</span></span></code> or any other library as a black box - you insert inputs, you get outputs - you should treat closed shadow root Web Components the same way. You don't go dumpster diving into <code><span><span style="color:var(--sh-identifier)">react</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">query</span></span></code> - you interact with the public API directly. If you do dive into the internals and change things, you're probably doing something you shouldn't and it's a <em>huge</em> code smell.</p>
<blockquote>
<p>"When the mode of a shadow root is "closed", the shadow root's implementation internals are inaccessible and unchangeable from JavaScript—in the same way the implementation internals of, for example, the <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">video</span><span style="color:var(--sh-sign)">&gt;</span></span></code> element are inaccessible and unchangeable from JavaScript."</p>
</blockquote>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode#:~:text=When%20the%20mode%20of%20a%20shadow%20root%20is%20%22closed%22%2C%20the%20shadow%20root%27s%20implementation%20internals%20are%20inaccessible%20and%20unchangeable%20from%20JavaScript%E2%80%94in%20the%20same%20way%20the%20implementation%20internals%20of%2C%20for%20example%2C%20the%20%3Cvideo%3E%20element%20are%20inaccessible%20and%20unchangeable%20from%20JavaScript." target="_blank" rel="noopener noreferrer">MDN</a></li>
</ul>
<p>No one dives into the <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">video</span><span style="color:var(--sh-sign)">&gt;</span></span></code> element internals to interact with it. They use the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video" target="_blank" rel="noopener noreferrer">public API</a> instead. Why should our components be any different?</p>
<a id="okay-but-how-do-i-interact-with-these-things?" href="#okay-but-how-do-i-interact-with-these-things?"><h2>Okay, but how do I interact with these things?</h2></a>
<p>So how <em>do</em> we expect users to interact with our components if the <code><span><span style="color:var(--sh-identifier)">mode</span></span></code> is set to <code><span><span style="color:var(--sh-identifier)">closed</span></span></code>? The same way you do with native elements. Just like when you need to interact with the native <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">input</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span></span></code> element, you go through the attributes, properties, and methods. You attach event listeners when things happen. Stick to JavaScript fundamentals!</p>
<p>For our <code><span><span style="color:var(--sh-identifier)">simple</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">button</span></span></code> component above, you can interact with the element just fine with JavaScript APIs via the host.</p>
<pre><code><span><span style="color:var(--sh-comment)">// We use the tag name directly and don't require</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// the `.shadowRoot` at all! Everything can</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// go through the host element.</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">document</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">addEventListener</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">click</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">console</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">log</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">clicked</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Click it</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">document</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">click</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// Check if it is disabled or not</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">document</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">disabled</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>Button here is a simplistic case, but my personal philosophy is to stick to what native does. Writing a Web Component that ultimately renders an <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">input</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span></span></code>? Make the API identical to the native input element. Making a Modal/Dialog Web Component? Expose the <code><span><span style="color:var(--sh-identifier)">open</span></span></code> attribute and <code><span><span style="color:var(--sh-identifier)">showModal</span></span></code> methods just like the native elements. This makes your component APIs familiar, standardized, and consistent.</p>
<a id="rely-on-composition" href="#rely-on-composition"><h2>Rely on Composition</h2></a>
<p>You may be wondering how you access content within the closed shadow root. The answer is that Design System authors should be relying on composition. Say you have a Modal component and want to render a form inside of it.</p>
<pre><code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">simple-</span><span style="color:var(--sh-identifier)">modal</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">label</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">Information</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">open</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">form</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">label</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">for</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">firstname</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">First name:</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">label</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">input</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">type</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">text</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">name</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">firstname</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">id</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">firstname</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">br</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">type</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">submit</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Submit</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">button</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">form</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">simple-</span><span style="color:var(--sh-identifier)">modal</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span></code></pre>
<p>And for pseudocode say <code><span><span style="color:var(--sh-identifier)">simple</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">modal</span></span></code> looks something like.</p>
<pre><code><span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">html</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">      &lt;dialog ?open=</span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">open</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)">&gt;</span></span>
<span><span style="color:var(--sh-string)">        &lt;h2&gt;</span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">label</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)">&lt;/h2&gt;</span></span>
<span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">        &lt;slot&gt;&lt;/slot&gt;</span></span>
<span><span style="color:var(--sh-string)">      &lt;/dialog&gt;</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Rely on slots for compositional APIs. Because of our slot, the <code><span><span style="color:var(--sh-identifier)">form</span></span></code> element is still accessible to you via JavaScript directly because it is in the "light DOM". This means, that yes, you can interact with everything here by doing the following.</p>
<pre><code><span><span style="color:var(--sh-identifier)">document</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-modal #firstname</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">value</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">Tony</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-identifier)">document</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">simple-modal button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">click</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>The <code><span><span style="color:var(--sh-identifier)">h2</span></span></code> rendered inside of the Modal on the other hand, is <em>not</em> accessible because it is inside of the closed shadow root of the <code><span><span style="color:var(--sh-identifier)">simple</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">modal</span></span></code> component. It's important to decide what should remain accessible versus not, but that's one of the advantages in my opinion of encapsulation.</p>
<a id="bringing-it-all-together" href="#bringing-it-all-together"><h2>Bringing it all together</h2></a>
<p>All of this is why I believe <code><span><span style="color:var(--sh-identifier)">mode</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">closed</span><span style="color:var(--sh-string)">'</span></span></code> is great for Design System components. It forces Design System authors to write public APIs that a consumer can use to interact with your components rather than them diving into the internals of your components. It'll help promote consistency and reliability. It has the advantages of Web Components, where anyone can place your Web Component on their page, no matter the framework and get a full UI/UX experience for that particular component. It offers privacy and encapsulation to Design System authors.</p>
<p>When building Design Systems, we want to ensure folks are using our components as they were intended. Closing the shadow root provides a ton of protections for authors. It's always better to start as the most restrictive and slowly open things up, rather than going the opposite direction and attempting to shove the genie back into the bottle later. It also promotes a good separation of concerns, between Design Sytem components and application code, which most developers should appreciate.</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Web Components for Framework Developers]]></title>
            <link>https://tonyward.dev/articles/web-components-for-framework-devs</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/web-components-for-framework-devs</guid>
            <pubDate>Fri, 26 Jul 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Web Components are great for building Design Systems that need to work across multiple frameworks. If you've been working in framework-land for quite some time now, you may find some topics confusing at first. In this post, I share some of the learnings I've experienced over the past year working with Web Components.</p>
<a id="learn-the-platform" href="#learn-the-platform"><h2>Learn "The Platform™"</h2></a>
<p>To summarize this entire post, I'd say: learn or relearn how to use JavaScript, CSS, and HTML without a framework.</p>
<p>A lot of us started learning web development this way; however, we quickly switched to a framework because that's what the industry forced us to do. We've forgotten a lot of the fundamentals. Go build a little web app with no framework and get back to the basics of web development. Because Web Components are part of the native Platform and treated as first-class citizens by the browser, remembering how to add event listeners and attributes goes a long way when diving into Web Components.</p>
<p>For those that don't have the time to go do this (I get it!), here's a quick brain dump from me.</p>
<a id="attributes-versus-properties" href="#attributes-versus-properties"><h2>Attributes versus Properties</h2></a>
<p>The first thing you should do is go read Jake Archibald's excellent post on <a href="https://jakearchibald.com/2024/attributes-vs-properties/" target="_blank" rel="noopener noreferrer">HTML attributes vs DOM properties</a> then come back here. Seriously, I'll wait. It'll take you like 5 minutes. Go read it!</p>
<a id="attribute-types" href="#attribute-types"><h2>Attribute Types</h2></a>
<p>Attributes passed to Web Components can only be one of the following:</p>
<ul>
<li>String</li>
<li>Boolean</li>
<li>Number</li>
</ul>
<p>Unlike in frameworks where you can pass arrays or objects, you can't do that with attributes in Web Components due to the limitations above. I know what you are thinking - why not just stringify/serialize the complex data type into a string? You do you! I wouldn't do that though. There are a few options: you can make them properties instead or think about your Web Components a bit differently to fit into "The Platform™" patterns. Would composition in your components help? Hopefully!</p>
<a id="-boolean-attributes" href="#-boolean-attributes"><h2>👀 Boolean Attributes</h2></a>
<p>It's important to call out Boolean attributes in particular. In React, you may have something like the following for a checkbox.</p>
<pre><code><span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-identifier)">isChecked</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">setIsChecked</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">React</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">useState</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-keyword)">false</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">input</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">type</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">checkbox</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">checked</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">isChecked</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>Super convenient! Your state <code><span><span style="color:var(--sh-identifier)">isChecked</span></span></code> is a Boolean that's either <code><span><span style="color:var(--sh-keyword)">true</span></span></code> or <code><span><span style="color:var(--sh-keyword)">false</span></span></code> and it acts just as you'd expect.</p>
<p>Now open up a <a href="https://codepen.io" target="_blank" rel="noopener noreferrer">codepen</a> and paste the following code.</p>
<pre><code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">input</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">type</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">checkbox</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">checked</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">false</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span></span>
<span></span></code></pre>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#checked" target="_blank" rel="noopener noreferrer">checked attribute</a> is a Boolean type. The snippet above provided the checked attribute with a string value of <code><span><span style="color:var(--sh-keyword)">false</span></span></code>. I've asked a lot of people what their expectations are for this and the answer is normally that it shouldn't be checked.</p>
<p>It appears to be checked though. Why is that?</p>
<p><strong>The presence of a boolean attribute applies that attribute</strong>. Simply setting <code><span><span style="color:var(--sh-identifier)">checked</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">false</span><span style="color:var(--sh-string)">"</span></span></code> enables the <code><span><span style="color:var(--sh-identifier)">checked</span></span></code> attribute and the string value is ignored - even though it is set to <code><span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">false</span><span style="color:var(--sh-string)">"</span></span></code>. This is <em>maybe</em> unexpected given the React example above.</p>
<p>In the React example, we are using a Boolean value, whereas in the input example above we are using a string for the value. This is where the difference lies; however, it could be a bit confusing at first. React does some magic for you to automatically handle this case by removing the attribute when the value is falsy - how nice of them! But it could make things confusing when it comes to Boolean attributes when you're building without a framework.</p>
<p>So the <strong>tl;dr</strong> of this section is: if you don't want to activate a boolean attribute on a native element or Web Component, don't add it at all!</p>
<a id="-how-i-learned-to-love-event-listeners" href="#-how-i-learned-to-love-event-listeners"><h2>"How I Learned To Love Event Listeners"</h2></a>
<p>I've been listening to the new Glass Animals album and happen to love <a href="https://www.youtube.com/watch?v=_eFAYoEjYhA" target="_blank" rel="noopener noreferrer">this song</a>.</p>
<p>Based on the point above about the attribute types, this means you can't pass functions into components.</p>
<p>In React, you would maybe have something like this:</p>
<pre><code><span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">handleClick</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">e</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">console</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">log</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">clicked!</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">e</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">ProductCard</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">onClick</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">handleClick</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>How does that look with Web Components? Events! You attach event listeners and when those events are dispatched, your callback is called.</p>
<pre><code><span><span style="color:var(--sh-identifier)">document</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">product-card</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">addEventListener</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">click</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">e</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">console</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">log</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">clicked</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">e</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>Internally, Web Components can dispatch their own events or custom events when something happens that a consumer should be notified of. This is identical thinking to passing a function <em>into</em> a component, but the difference is that the component <em>itself</em> dispatches the event and the consumer must add an event listener for it to be notified when "the thing" happens. In Lit, this is accomplished with <code><span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">dispatchEvent</span></span></code>.</p>
<pre><code><span><span style="color:var(--sh-comment)">// Within my Web Component</span></span>
<span><span style="color:var(--sh-comment)"></span></span>
<span><span style="color:var(--sh-keyword)">function</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">onSomeEventHappening</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">// Plain old event</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">dispatchEvent</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-keyword)">new</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Event</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">cool-event</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">// Or custom events, where you can provide</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">// some extra details to the consumer!</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">dispatchEvent</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-keyword)">new</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">CustomEvent</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">cool-custom-event</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">detail</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">some info</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Attach event listeners to the custom events and you're all set.</p>
<pre><code><span><span style="color:var(--sh-identifier)">document</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">my-component</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">addEventListener</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">cool-event</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">e</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">console</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">log</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">e</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-identifier)">document</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">querySelector</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">my-component</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">addEventListener</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">cool-custom-event</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">e</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">console</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">log</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">e</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">target</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">detail</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>For an example of what I'm talking about, take a look <a href="https://lit.dev/playground/#project=W3sibmFtZSI6InNpbXBsZS1ncmVldGluZy50cyIsImNvbnRlbnQiOiJpbXBvcnQge2h0bWwsIGNzcywgTGl0RWxlbWVudH0gZnJvbSAnbGl0JztcbmltcG9ydCB7Y3VzdG9tRWxlbWVudCwgcHJvcGVydHl9IGZyb20gJ2xpdC9kZWNvcmF0b3JzLmpzJztcblxuQGN1c3RvbUVsZW1lbnQoJ3NpbXBsZS1ncmVldGluZycpXG5leHBvcnQgY2xhc3MgU2ltcGxlR3JlZXRpbmcgZXh0ZW5kcyBMaXRFbGVtZW50IHtcbiAgc3RhdGljIHN0eWxlcyA9IGNzc2BwIHsgY29sb3I6IGJsdWUgfWA7XG5cbiAgQHByb3BlcnR5KClcbiAgbmFtZSA9ICdTb21lYm9keSc7XG4gIFxuICBvbkNsaWNrKCkge1xuICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ2ZpcmUtZXZlbnQnKSk7XG4gIH1cblxuICByZW5kZXIoKSB7XG4gICAgcmV0dXJuIGh0bWxgXG4gICAgICAgIDxkaXY-XG4gICAgICAgICAgPHA-SGVsbG8sICR7dGhpcy5uYW1lfSE8L3A-XG4gICAgICAgICAgPHA-VGhpcyBpcyB0aGUgY2FzZSB3aGVyZSBtYXliZSB5b3UgaGF2ZSBhIGNvbXBsZXggY29tcG9uZW50IGFuZCB3YW50IHRvIGZpcmUgYW4gZXZlbnQgd2hlbiBhIG5lc3RlZCBjb21wb25lbnQgaXMgY2xpY2tlZC4gSGVyZSBpcyBob3cgeW91J2QgZG8gdGhhdC4gPC9wPlxuICAgICAgICAgIDxidXR0b24gQGNsaWNrPSR7dGhpcy5vbkNsaWNrfT5GaXJlIGFuIGV2ZW50PC9idXR0b24-XG4gICAgICAgIDwvZGl2PlxuICAgIGA7XG4gIH1cbn1cbiJ9LHsibmFtZSI6ImluZGV4Lmh0bWwiLCJjb250ZW50IjoiPCFET0NUWVBFIGh0bWw-XG48aGVhZD5cbiAgPHNjcmlwdCB0eXBlPVwibW9kdWxlXCIgc3JjPVwiLi9zaW1wbGUtZ3JlZXRpbmcuanNcIj48L3NjcmlwdD5cbjwvaGVhZD5cbjxib2R5PlxuICA8c2ltcGxlLWdyZWV0aW5nIG5hbWU9XCJXb3JsZFwiPjwvc2ltcGxlLWdyZWV0aW5nPlxuPC9ib2R5PlxuXG48c2NyaXB0PlxuICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdzaW1wbGUtZ3JlZXRpbmcnKS5hZGRFdmVudExpc3RlbmVyKCdmaXJlLWV2ZW50JywgKCkgPT4gY29uc29sZS5sb2coJ2NsaWNrZWQnKSk7XG48L3NjcmlwdD5cbiJ9LHsibmFtZSI6InBhY2thZ2UuanNvbiIsImNvbnRlbnQiOiJ7XG4gIFwiZGVwZW5kZW5jaWVzXCI6IHtcbiAgICBcImxpdFwiOiBcIl4zLjAuMFwiLFxuICAgIFwiQGxpdC9yZWFjdGl2ZS1lbGVtZW50XCI6IFwiXjIuMC4wXCIsXG4gICAgXCJsaXQtZWxlbWVudFwiOiBcIl40LjAuMFwiLFxuICAgIFwibGl0LWh0bWxcIjogXCJeMy4wLjBcIlxuICB9XG59IiwiaGlkZGVuIjp0cnVlfV0" target="_blank" rel="noopener noreferrer">at this playground</a>.</p>
<a id="styling" href="#styling"><h2>Styling</h2></a>
<p>I already <a href="https://www.tonyward.dev/articles/web-components-design-system" target="_blank" rel="noopener noreferrer">wrote about the advantages</a> Design Systems have when it comes to styling with Web Components due to the Shadow DOM. Essentially Web Components need to make styling part of their public API either via CSS Custom Properties (variables) or CSS parts. Unlike in React, your components can't accept a <code><span><span style="color:var(--sh-keyword)">class</span></span></code> or <code><span><span style="color:var(--sh-identifier)">className</span></span></code> property that gets applied as only CSS variables pierce the Shadow DOM. Instead, stick to the blessed Platform way.</p>
<a id="slots" href="#slots"><h2>Slots</h2></a>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot" target="_blank" rel="noopener noreferrer">Slots</a> are great! As a Web Component author, you get to decide areas where consumers can supply their own markup and it goes in those specific locations you identified.</p>
<blockquote>
<p>The <code><span><span style="color:var(--sh-identifier)">slot</span></span></code> HTML element—part of the Web Components technology suite—is a placeholder inside a web component that you can fill with your own markup, which lets you create separate DOM trees and present them together.</p>
</blockquote>
<p>Say you had a header component and you wanted to expose a way to render elements either on the left or right side. You do that with slots in web components. Here's how consuming a component with slots may look.</p>
<pre><code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">my-</span><span style="color:var(--sh-identifier)">header</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">home-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">slot</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">left</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">home-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">sign-</span><span style="color:var(--sh-property)">out-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">slot</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">right</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">sign-</span><span style="color:var(--sh-property)">out-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">my-</span><span style="color:var(--sh-identifier)">header</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span></code></pre>
<p>The home button goes on the left, the sign out button now sits on the right. The <code><span><span style="color:var(--sh-identifier)">my</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">header</span></span></code> component would look something like <a href="https://lit.dev/playground/#project=W3sibmFtZSI6InNpbXBsZS1ncmVldGluZy50cyIsImNvbnRlbnQiOiJpbXBvcnQge2h0bWwsIGNzcywgTGl0RWxlbWVudH0gZnJvbSAnbGl0JztcbmltcG9ydCB7Y3VzdG9tRWxlbWVudCwgcHJvcGVydHl9IGZyb20gJ2xpdC9kZWNvcmF0b3JzLmpzJztcblxuQGN1c3RvbUVsZW1lbnQoJ3NpbXBsZS1ncmVldGluZycpXG5leHBvcnQgY2xhc3MgU2ltcGxlR3JlZXRpbmcgZXh0ZW5kcyBMaXRFbGVtZW50IHtcbiAgc3RhdGljIHN0eWxlcyA9IGNzc2BwIHsgY29sb3I6IGJsdWUgfWA7XG5cbiAgQHByb3BlcnR5KClcbiAgbmFtZSA9ICdTb21lYm9keSc7XG5cbiAgcmVuZGVyKCkge1xuICAgIHJldHVybiBodG1sYFxuICAgICAgPGhlYWRlciBzdHlsZT1cImRpc3BsYXk6IGZsZXg7IGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjtcIj5cbiAgICAgICAgPGRpdj5cbiAgICAgICAgICA8c2xvdCBuYW1lPVwibGVmdFwiPjwvc2xvdD5cbiAgICAgICAgPC9kaXY-XG4gICAgICAgIFxuICAgICAgICA8ZGl2PlxuICAgICAgICAgIDxzbG90IG5hbWU9XCJyaWdodFwiPjwvc2xvdD5cbiAgICAgICAgPC9kaXY-XG4gICAgIDwvaGVhZGVyPmA7XG4gIH1cbn1cbiJ9LHsibmFtZSI6ImluZGV4Lmh0bWwiLCJjb250ZW50IjoiPCFET0NUWVBFIGh0bWw-XG48aGVhZD5cbiAgPHNjcmlwdCB0eXBlPVwibW9kdWxlXCIgc3JjPVwiLi9zaW1wbGUtZ3JlZXRpbmcuanNcIj48L3NjcmlwdD5cbjwvaGVhZD5cbjxib2R5PlxuICA8c2ltcGxlLWdyZWV0aW5nPlxuICAgIDxidXR0b24gc2xvdD1cImxlZnRcIj4xPC9idXR0b24-XG4gICAgPGJ1dHRvbiBzbG90PVwicmlnaHRcIj4yPC9idXR0b24-XG4gIDwvc2ltcGxlLWdyZWV0aW5nPlxuPC9ib2R5PlxuIn0seyJuYW1lIjoicGFja2FnZS5qc29uIiwiY29udGVudCI6IntcbiAgXCJkZXBlbmRlbmNpZXNcIjoge1xuICAgIFwibGl0XCI6IFwiXjMuMC4wXCIsXG4gICAgXCJAbGl0L3JlYWN0aXZlLWVsZW1lbnRcIjogXCJeMi4wLjBcIixcbiAgICBcImxpdC1lbGVtZW50XCI6IFwiXjQuMC4wXCIsXG4gICAgXCJsaXQtaHRtbFwiOiBcIl4zLjAuMFwiXG4gIH1cbn0iLCJoaWRkZW4iOnRydWV9XQ" target="_blank" rel="noopener noreferrer">this</a>.</p>
<pre><code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">header</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">style</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">display: flex; justify-content: space-between;</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">!--</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">Left</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">side</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">container -</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">name</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">left</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-sign)">!</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Right</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">side</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">container</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">name</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">right</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">header</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span></code></pre>
<p>We're relying on CSS a bit here to simplify putting things in their respective places, but you could imagine a more complex component where custom content needs to go in very specific places. By exposing <code><span><span style="color:var(--sh-identifier)">slot</span></span></code>s, you get to define those locations! Pretty cool. I dig them.</p>
<a id="no-self-closing-tags" href="#no-self-closing-tags"><h2>No Self-Closing Tags</h2></a>
<p>Web Components don't use self-closing tags, which is maybe a bit of a surprise if you're coming from a framework.</p>
<p>So in React you may have something like the following.</p>
<pre><code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">MyCoolComponent</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">prop1</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">Hello</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">prop2</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">World</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span></span>
<span></span></code></pre>
<p>With Web Components, this would need to look like this.</p>
<pre><code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">my-</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">component</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">attr1</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">Hello</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">attr2</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">World</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">my-</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">component</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span></code></pre>
<p>Another <a href="https://jakearchibald.com/2023/against-self-closing-tags-in-html/" target="_blank" rel="noopener noreferrer">great read</a> from Jake on some history here. There's also more info <a href="https://developer.mozilla.org/en-US/docs/Glossary/Void_element" target="_blank" rel="noopener noreferrer">at MDN</a> about void elements and self-closing tags. I could write more about this topic, but these two resources combined do such an excellent job that you should read these instead!</p>
<a id="more-?" href="#more-?"><h2>More...?</h2></a>
<p>As time progresses, I'll add more here with amendments below. For now, that's all that comes to mind. Let me know if there's anything I missed or worth discussing! Enjoy! 👋</p>
<a id="reading-material" href="#reading-material"><h3>Reading Material</h3></a>
<p>Here are some links that may be helpful.</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components" target="_blank" rel="noopener noreferrer">Web Components</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements" target="_blank" rel="noopener noreferrer">Custom Elements</a></li>
<li><a href="https://web.dev/articles/custom-elements-best-practices" target="_blank" rel="noopener noreferrer">Custom Element Best Practices</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots" target="_blank" rel="noopener noreferrer">Templates &amp; Slots</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM" target="_blank" rel="noopener noreferrer">Shadow DOM</a></li>
<li><a href="https://lit.dev/learn/" target="_blank" rel="noopener noreferrer">Lit Docs</a>
<ul>
<li>If you'll be using Lit to build Web Components</li>
</ul>
</li>
<li><a href="https://codelabs.developers.google.com/codelabs/lit-2-for-react-devs#0" target="_blank" rel="noopener noreferrer">Lit for React Devs</a>
<ul>
<li>A little dated, but still applies</li>
</ul>
</li>
</ul>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Driving Consistency with Custom ESLint Rules]]></title>
            <link>https://tonyward.dev/articles/driving-consistency-with-eslint-rules</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/driving-consistency-with-eslint-rules</guid>
            <pubDate>Mon, 24 Jun 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I spent some time over the weekend writing an <a href="https://github.com/ynotdraw/example-eslint-plugin" target="_blank" rel="noopener noreferrer">ESLint plugin template</a>. What is an ESLint plugin and how can it help drive consistency in a repository? Well you're on the right post!</p>
<p>If you've worked with me in the last 5 years or so, you've probably heard me harp on consistency being key to the long term success of a codebase. Consistency is extremely important in many areas of software development and UI/UX. In the UI/UX realm, your customers expect visual consistency in a product. They expect similar flows to be consistent across different areas of the product. Without it, the experience feels disjointed and your product may become annoying to use. In software development, without consistency, refactoring code is like untangling a massive, impossible nest of spaghetti while also trying to find a needle in a haystack.</p>
<p>One of the strong opinions I have is that I'd rather have 10 consistent "areas of improvement" in a codebase than 10 one-off solutions for that problem scattered about. It's way easier to collect the consistent patterns and arrive at an improved solution, or maybe a nice abstraction. It also makes refactoring way easier - you know of all current places doing "the thing" because you can search for that pattern fairly easily. With the one-off approach, it can be extremely difficult to track all of those use cases down. This leads to hidden tech debt. Ouch.</p>
<p>For the past 6 months, I've been working with an incredible team. We grew very rapidly to 8 engineers including myself. As we kept growing, it was clear to me that we needed to establish and enforce patterns in our repository to ensure we maintain a high quality bar while also ensuring the codebase was easy to contribute to. Without patterns and guidance, the codebase would quickly become chaotic. It would make it harder to onboard new engineers. Different, inconsistent patterns would pop up over time. It would make PR reviews a lot harder. Not being able to automate some of the decisions we've previously decided on lead to rehashing the same conversations with different people in different PRs.</p>
<p>One way I solved this was driving consistency via Prettier and ESLint rules. Automated tools are always a great place to start. Prettier formats the code, while ESLint enforces coding patterns. Using off the shelf rules are great, because it's less work for you. There are a lot of great ESLint plugins you can use like <a href="https://github.com/sindresorhus/eslint-plugin-unicorn" target="_blank" rel="noopener noreferrer">unicorn</a>; however, you may reach the point where what you want to enforce is <em>very</em> specific to your codebase's needs. This is where writing custom ESLint rules come in.</p>
<p>In my opinion, a great sign of a well established repository is popping into it and not being able to tell which developer wrote which file. Each file follows a similar format and the patterns are consistent across files. When I look at a repository, I prefer everything following the same patterns. This happens quite a lot in large open source projects. They have so much automation and tooling in place to help drive consistency - it makes it a treat to contribute to that repository because there's less to think about.</p>
<p>I strongly believe custom ESLint rules are extremely helpful for any codebase. They help drive this consistency I've been harping on above. It's even better when the code provides autofixes - then the developer doesn't even need to think about how to format or structure their code. It gets fixed for them automatically by running a command!</p>
<p>I've open sourced a repository on GitHub at https://github.com/ynotdraw/example-eslint-plugin. It's a walkthrough of how to write your own ESLint plugin and rules. In the README, I talk through everything as best I can to hopefully share some of the knowledge I've collected. Some of it may be incorrect, or there may be other ways - this is why I open sourced it! Please feel free to put up a Pull Request if you find improvements.</p>
<p>Overall, consistency is very important to me. It helps a codebase from spiraling out of control. It makes contributions so much easier. It serves as documentation in a way for how the repository wants code written. It makes developer's lives easier by autofixing code for them to match the patterns we decided on as a team. It makes refactoring easier. Leaning heavily on Prettier and ESLint goes a really long way. And whatever areas software can't fix automatically, using documentation is a great solution too.</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Collecting Feedback & Measuring the Success of a Design System]]></title>
            <link>https://tonyward.dev/articles/measuring-design-system-success</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/measuring-design-system-success</guid>
            <pubDate>Wed, 01 May 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>How do you know if your Design System is actually working well for people and providing value for the company? This is a really complex topic, and I have a few ideas. I'd love to hear some of yours too! Let's chat on <a href="https://www.linkedin.com/in/ynotdraw/" target="_blank" rel="noopener noreferrer">LinkedIn</a> or <a href="https://bsky.app/profile/ynotdraw.bsky.social" target="_blank" rel="noopener noreferrer">Bluesky</a>.</p>
<p>In a previous post I mentioned how you should <a href="https://www.tonyward.dev/articles/productize-your-design-system" target="_blank" rel="noopener noreferrer">productize your Design System</a>. Every product wants customers - it helps them get funding and succeed. The same applies here. It's important to show the value your Design System is bringing to your customers - your colleagues. Measuring success and collecting feedback go hand-in-hand. Not only should you be measuring success, you also need the feedback from people using your Design System so that it can improve.</p>
<blockquote>
<p>Measuring is hard, collecting feedback is easier.</p>
</blockquote>
<p>To me, collecting feedback is easier than measuring success because it's more direct. I feel as though measuring success can be really difficult, because collecting metrics can be tricky. Reviewing the metrics and coming to conclusions without being biased is also difficult. This is exactly why I wrote this article! Without further ado, here are some ideas I've come up with around measuring success and collecting feedback.</p>
<a id="-customer-satisfaction" href="#-customer-satisfaction"><h2>"Customer Satisfaction"</h2></a>
<p>This is where I like to start - it's similar to being in sales. The sales department's job is to interact with you and convince you to buy their product to fulfill your needs. In this case, you are the sales person meeting with your fellow colleagues and convincing them your Design System will help them at their job. Pitch the Design System to them if they aren't using it already. "Get in the field" with them. See how they are doing their work currently and offer your Design System as a potential improvement. At a minimum, see what feedback they have about your Design System. Hopefully you'll sell them on it!</p>
<p>One of my favorite things to do after selling folks on a Design System is meet with both designers and engineers as they work on a new product or feature. Give them access to what they need in Figma and start with the design side of things. Sit with them as they begin to work through the process of building. Ask questions along the way about their thinking and how they feel things are going. Then go do the same thing with engineers - give them access to your Design System library. Was the process smooth overall? Did it accelerate their development at all? If yes, nice! If not, you'll have some feedback to make things better for next time.</p>
<p>Holding office hours is another great way to measure "customer satisfaction". Having dedicated time for people to ask questions or even just hang out and talk about your Design System is a great way to spread the word about your work, while also getting additional feedback in a casual way. It's similar to above as well, where it could be a pairing session that reveals improvement areas for your team.</p>
<a id="adoption" href="#adoption"><h2>Adoption</h2></a>
<p>Another metric I like to use is measuring adoption. Before measuring adoption though, I have a piece of advice: <strong>Don't make using your Design System a requirement.</strong> It should be optional and left up to the teams building the product/feature. But why?!</p>
<p>Say you're at a company with an existing Design System and a group wants to spin up a new product. If they immediately reach for using the Design System, that's a huge win for your team. It means they see the value in using it versus starting completely from scratch. This is why I prefer making the Design System optional - you want it to be so good that people <em>want</em> to use it for every single project because it saves them time and effort.</p>
<p>Another good adoption metric is the case where there's an existing product not currently using your Design System. They end up adopting it. That's huge! This means that team sees the value your Design System is providing.</p>
<p>These are more "long term" or "long feedback loop" metrics. Not every organization spins up new products at a rapid pace. That's okay. I think it's one of those items to celebrate when it does happen though. What are some other ways we can measure adoption of our Design System that have a shorter feedback loop?</p>
<a id="component-usage-metrics" href="#component-usage-metrics"><h3>Component Usage Metrics</h3></a>
<div><div><img alt="An image of a growth chart." loading="lazy" width="300" height="200" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 160'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r+mnAAAAWklEQVR42lWKOQqAMBAA83/wC1Z2/sRGUAxC7BMEI6xxwT1Yg53DVMM4EaWHiLiKiFDKzWRmrjaAK6UUYwxhG7zvl5lFHbMAQM7nZx792rRdvZ39Qebp2FX1BZgdWcQ9p7obAAAAAElFTkSuQmCC'/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fgrowth-chart.1h-95ao11whma.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fgrowth-chart.1h-95ao11whma.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fgrowth-chart.1h-95ao11whma.png&amp;w=640&amp;q=75"></div></div>
<p>I like collecting data around how many times each component is used in a codebase (and even across codebases!). It ties into adoption, and allows you to see over time which components are leading the pack. It can help provide information later on to UX Researchers so they can have a better understanding of patterns in your products. It helps your team understand which patterns are most used across products. This is a great metric around adoption.</p>
<p>Collecting data for the number of components used in a project doesn't necessarily mean the Design System is <em>successful</em>. "Adoption" and "Success" are related, but just because someone uses your Design System, doesn't necessarily mean your Design System is successful. It <em>does</em> help the overall process of maintaining and developing a Design System though - it's a great way to collect additional feedback and make improvements.</p>
<p>If you release a new component and after a few months you notice that it's not getting used as frequently as you were hoping, that's feedback unlocked by simply keeping track of usage data. You can then figure out why that is and make adjustments.</p>
<p>I had brainstormed this idea with my amazing <a href="https://www.linkedin.com/in/haley-ward/" target="_blank" rel="noopener noreferrer">wife</a> around 2020, but never had enough time to implement it. She ended up building something similar and it's been working out well for her team. Here's the gist:</p>
<ul>
<li>At some cadence, query for usages of your Design System components (using GitHub APIs, for example).</li>
<li>Keep track of how many times each component is used across each repository.</li>
<li>Store this data somewhere (in a dynamo table, S3, Tableau, etc.) you can run queries on it in the future.</li>
<li>At some cadence, weekly/monthly, post the usage stats (it'd be ideal to build a UI surfacing this data with nice charts).</li>
<li>Review the data as a team.</li>
</ul>
<p>Collecting data allows you to ask questions. Are the numbers going up or down? Is adoption of a component fairly low? Why could that be? Does that component need additional functionality? Do folks not know when to use that particular component? These are great examples of questions that come up around your Design System - and you can only ask these questions if you collect the data!</p>
<a id="component-property-usage-metrics" href="#component-property-usage-metrics"><h3>Component Property Usage Metrics</h3></a>
<p>Similar to above, but see how frequenty certain props are used for components.</p>
<p>If something isn't being used, you can begin asking questions about why that may be. Do the designs not call for that functionality as much as you had thought? Does it not have a good UX? Are there accessibility issues? Do people not know about it? Was this added by an engineer, but isn't in Figma, so no designers know about it?</p>
<p>This also helps if you need to deprecate usage of a prop at some point. You'll have the data to help you decide if it's a good idea and what the impact to users will be. Maybe you'll write a codemod to auto-migrate folks to a new property name you want to use instead. Having data around how your users are using your components is extremely helpful when it comes to maintaining and improving a Design System.</p>
<a id="user-interviews-and-anonymous-surveys" href="#user-interviews-and-anonymous-surveys"><h2>User Interviews and Anonymous Surveys</h2></a>
<div><div><img alt="An image of a survey." loading="lazy" width="300" height="200" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 160'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r+mnAAAAYElEQVR42h2KOQ6AIBQFuf91tLHTmNiYeAXFtZAYQwj8DZD4ijfFjMo5e0QgSinlfyJSXhV042Ss9cwoHELQ8xpjVIhYV4117gT/Itzm6dtBWBQR62UrulQiEQIc+8XEH9w7Wruo3m9wAAAAAElFTkSuQmCC'/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsurvey.2qx8evvqrzw2n.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsurvey.2qx8evvqrzw2n.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fsurvey.2qx8evvqrzw2n.png&amp;w=640&amp;q=75"></div></div>
<p>User interviews can be really helpful to get feedback on areas to improve. I suggest scheduling them with folks who have been using your Design System for a few months, at least. It's enough time where they may still remember their "I'm new" feedback, while also working with it long enough to offer suggestions on what could be improved to help their day-to-day. I also suggest interviewing folks from different levels - the more junior folks may have feedback that is completely different than the more senior people. This makes sure you cover a wide spectrum.</p>
<p>User interviews are great, but there's another way to get feedback as well. People love leaving reviews. Take a look at Google and Amazon reviews. But in a professional setting, sometimes people don't feel as though they can be as open or honest as when they review their cool <a href="https://www.amazon.com/Mountain-Three-Adult-T-Shirt-Green/dp/B000NZW3KC/ref=sr_1_5?crid=2Z67EDCRYXDDU&amp;amp;dib=eyJ2IjoiMSJ9.UeSbPIOZXX7GcWwUxlYPRZcZJTLUnmHry3Byj3KZu12hdAQPoU4z5I7J6d6FnxPZA3SCb8z5aOzShjLauIp224S6o6MJqhaPuPs5UVCHxGCiMYP_vyNfSolsj8s7DJM39k-v-19YALikcRPURYAi21xfgMhAbPLhCWeSI0Iz04Fvmn6eAzRjHfl787En2IcfyqXgxq59xocZ0sKC221kWp8BqaDjQ4FpELWbGKKo071Mt0EISCRlRNLSSovdW0VG3YBRcqt-qWOIWR1wes0on1ANtZm0WroL7oHDX7rCyRM.jTJfovXYdvejR6p63YN9xRMfCbk9xGTFca2-v5HJyfE&amp;amp;dib_tag=se&amp;amp;keywords=wolf%2Btshirt&amp;amp;qid=1712792958&amp;amp;sprefix=wolf%2Btshirt%2Caps%2C125&amp;amp;sr=8-5&amp;amp;th=1&amp;amp;psc=1" target="_blank" rel="noopener noreferrer">wolf t-shirt</a>. This is totally understandable!</p>
<p>Not everyone wants to tell you to your face (or via Slack) that the component you made sucks or has issues. It can be difficult for folks to bring up. But if you provide an anonymous way for folks to provide feedback, it can give a chance for those voices to be heard in a safe space.</p>
<p><strong>We want all of the feedback - positive, negative, and somewhere in between.</strong> Offering many avenues to get to this information is key. I typically use a <a href="https://www.google.com/forms/about/" target="_blank" rel="noopener noreferrer">Google Form</a>. They're quick to setup and most folks are familiar with the UI/UX of Google products already.</p>
<a id="accessibility-metrics" href="#accessibility-metrics"><h2>Accessibility Metrics</h2></a>
<p>Does using your Design System improve an accessibility ("a11y") audit? A metric I've used in the past is the case where you introduce a Design System into an existing product. If your audit score <em>improves</em>, that's great. Another win for your Design System. After all, I firmly believe <strong>Design Systems should have as much accessibility baked in as possible</strong>. This topic deserves an article by itself!</p>
<p>There are a ton of tools to help measure accessibility in your products. Here are some I recommend:</p>
<ul>
<li>Follow the <a href="https://www.w3.org/WAI/ARIA/apg/patterns/" target="_blank" rel="noopener noreferrer">ARIA Authoring Patterns Guide</a> and <a href="https://www.w3.org/WAI/ARIA/apg/practices/" target="_blank" rel="noopener noreferrer">Practices Guide</a>.</li>
<li><a href="https://developer.chrome.com/docs/lighthouse/accessibility/scoring" target="_blank" rel="noopener noreferrer">Lighthouse</a> is a fine place to begin your a11y improvement journey. If you're already using Chrome, it's really easy to use! (It also ties into my performance point below.)</li>
<li><a href="https://chromewebstore.google.com/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd?utm_source=deque.com&amp;amp;utm_medium=referral&amp;amp;utm_campaign=axe_hero" target="_blank" rel="noopener noreferrer">axe DevTools for Chrome</a> is a great extension that provides a ton of great a11y information. If you're already using Chrome, it's another no brainer to install.</li>
<li><a href="https://github.com/dequelabs/axe-core" target="_blank" rel="noopener noreferrer">axe-core</a> allows you to write integration or end-to-end tests and verify accessibility programmatically.</li>
<li><a href="https://pa11y.org" target="_blank" rel="noopener noreferrer">pa11y</a> is another great tool to use that's similar to <code><span><span style="color:var(--sh-identifier)">axe</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">core</span></span></code>. I haven't used it yet, but I've been keeping an eye on it for a while now and have heard excellent things.</li>
<li>If you're using Storybook, their <a href="https://storybook.js.org/addons/@storybook/addon-a11y" target="_blank" rel="noopener noreferrer">accessibility addon</a> shows a11y issues right in your story. Convenient!</li>
</ul>
<a id="web-performance-metrics" href="#web-performance-metrics"><h2>Web Performance Metrics</h2></a>
<div><div><img alt="An image of a Lighthouse score for performance." loading="lazy" width="600" height="400" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 200'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAfklEQVR42j2Lyw3DIBBEaYETn4OBXVjsOFKqTRfugaub2FomoEg+jEajec+UnC6RrlxZW2/aRJSZlYg1xniZGMJdSoF8dtRXAzFh2xJSyvDe3yZMYBo43yf6caDWhpwLlrS+B+h7BxFBumDtB7DWfp33I8QwnHPDeffvmfX9AEhFMWBOTCc+AAAAAElFTkSuQmCC'/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fperformance.10t-een9lfigw.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fperformance.10t-een9lfigw.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fperformance.10t-een9lfigw.png&amp;w=1200&amp;q=75"></div></div>
<p>Does using your Design System improve the web performance of your application? You could start by using a <a href="https://developer.chrome.com/docs/lighthouse/overview" target="_blank" rel="noopener noreferrer">Lighthouse</a> score. I'm not sure how much weight this metric should carry, but it could be interesting to see how your Design System would affect performance.</p>
<a id="bug-metrics" href="#bug-metrics"><h2>Bug Metrics</h2></a>
<p>When folks begin adopting your Design System, do less bugs appear in the product? The hope would be that the application would become more stable and have less bugs over a longer period of time as adoption increases. This is another "long term measurement" that may pay off over time, but could be hard to get data from in the short term.</p>
<p>One case I've seen is where there's a photo upload component in a product that's shared across the application. In my case, the existing component was quite buggy, had some UI/UX issues around drag and drop, and wasn't accessible. The Design System team I was on built a new component for this use case. It handled receiving photos from the file system, supported drag and drop, supported keyboard and was friendly to screenreaders, and had a nice horizontal scroll-snap behavior. When we replaced the existing component with our new one, we immediately deleted about 5 bug tickets in the backlog.</p>
<p>Of course our team took the time and effort to build this component, which could have been spent resolving the bugs directly; however, at the end of our task, the Design System team provided a design approved™ component, with better UI/UX, better tests, and made it accessible via keyboard and screen readers. It could also be used across multiple features and products. The product team was also very happy we took on the work to build that particular component and increased our overall relationship with the team. That's a win in my book!</p>
<a id="documentation-site-analytics" href="#documentation-site-analytics"><h2>Documentation Site Analytics</h2></a>
<div><div><img alt="An image of an analytics dashboard." loading="lazy" width="600" height="400" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 120'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAYAAACuyE5IAAAAQUlEQVR42mXLMQ6AIAxAUQqlYG1J3Ize/5zfODO88ZVpxhlJZODum1J1YvnQLBCRTdHjwtZLjxsdi9adP1UdSG1877oZ/YVGIr8AAAAASUVORK5CYII='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fanalytics.1oainnbb8ua2r.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fanalytics.1oainnbb8ua2r.png&amp;w=1200&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fanalytics.1oainnbb8ua2r.png&amp;w=1200&amp;q=75"></div></div>
<p>Keeping an eye on analytics for your documentation site is a great way to get information on your Design System. Are people regularly visiting certain pages? Is that component frequently used or are they having issue with that component and diving in to learn more? This is all great information to have available! Using information from interviews and the methods listed above, coupled with the documentation site information, you may learn about additional areas for improvement.</p>
<a id="that-s-all-folks" href="#that-s-all-folks"><h2>That's All, Folks</h2></a>
<p>Well, if you made it this far, thank you for reading my ramblings! As mentioned at the beginning, measuring success can be difficult, but it's paramount to the success of your Design System. Collecting feedback also creates improvement opportunities and is equally as important. These are all the ideas that come to mind to me around those two areas. What did I miss? Let's chat on <a href="https://www.linkedin.com/in/ynotdraw/" target="_blank" rel="noopener noreferrer">LinkedIn</a> or <a href="https://bsky.app/profile/ynotdraw.bsky.social" target="_blank" rel="noopener noreferrer">Bluesky</a>. Cheers!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Improving]]></title>
            <link>https://tonyward.dev/articles/improving</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/improving</guid>
            <pubDate>Fri, 19 Apr 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I really enjoy software engineering and am constantly trying to improve. I love being in tech because there is <em>so</em> much to learn and so many problems to solve. Every day I try to be a better engineer than the day before. Here are some areas I try to improve on and topics I've learned over the course of my career that I believe are worth sharing.</p>
<a id="opportunities-to-make-mistakes" href="#opportunities-to-make-mistakes"><h2>Opportunities to Make Mistakes</h2></a>
<p>I'm not sure I can say it any better - take it from <a href="https://www.twitch.tv/ThePrimeagen" target="_blank" rel="noopener noreferrer">ThePrimeagen</a>:</p>
<div><div><iframe src="https://www.youtube.com/embed/8XYDQlQUBnw?si=4qVsPWWi72wQ50BC" title="A short from ThePrimeagen talking about becoming a great engineer" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe></div></div>
<p>I agree with his point. Improving as a developer means developing a solution, realizing it's the incorrect one, and getting the opportunity to do it again and again and again. I feel as though the industry is incorrectly against "rewriting x feature every n timeframe". Although I understand how wasteful that can seem on the surface, if you're rewriting it for valid reasons, I don't think it's wasteful at all. In fact, quite the opposite.</p>
<p>Obviously if you are having to rewrite it because the organization keeps firing the team that's supposed to maintain it, that's a different story. But if the original group who keeps rewriting it is coming up with better ways of writing that feature, it likely means there will be other positive impacts as well. Maybe those improvements mean cost savings, better performance, or any other increased/positive metric. <strong>If rewriting something comes with a net gain, it should be embraced.</strong> Okay, back on topic, Tony.</p>
<p>All that to say: it's super important to be given the space and time to make mistakes. The first solution you land on may not be correct or as great as you were hoping. That's okay! Hopefully you'll have time to improve it. Maybe you won't, but maybe you'll experience a similar problem at another company in the future. <strong>As long as you learn from that experience going forward, you'll come out ahead.</strong></p>
<p>To organization leaders out there: Don't fail fast, <em>fall</em> fast and get back up. Encourage risk taking. Encourage trying. <strong>If you don't read the rest of this article, I encourage you to watch this video from Simon Sinek.</strong></p>
<div><div><iframe src="https://www.youtube.com/embed/TTMiILxqBSc?si=fMFWAQCOBTrkJilk" title="Simon Sinek talking about falling rather than failing and what that means" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe></div></div>
<a id="unknowns-and-being-uncomfortable" href="#unknowns-and-being-uncomfortable"><h2>Unknowns and Being Uncomfortable</h2></a>
<p>It can be very scary to take on the unknown, but where there are unknowns, there are opportunities to learn and grow. Rather than being afraid of taking on a task you have no idea how you'd solve, be confident in your abilities and try it. At a minimum, you'll walk away learning something new.</p>
<p>It can feel very uncomfortable taking on something you know nothing about.</p>
<blockquote>
<p>"Why would I attempt to create a new GraphQL mutation when I know nothing about how this service works?"</p>
</blockquote>
<p>Valid question. But if you complete it, you'll know how to add a new mutation in GraphQL and have a better understanding of work in a backend service. You may also come to the realization that you love writing service code rather than frontend code. That could completely change your career trajectory, all from taking a stab at a single task.</p>
<div><div><iframe src="https://www.youtube.com/embed/fpnlSc4pH7I?si=Z267uzhDUnBMHmdk" title="YouTube video of Simon Sinek talking about being uncomfortable" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe></div></div>
<p>It's good to get out of your comfort zone now and then to stretch your wings and challenge yourself. You may learn more about yourself too!</p>
<a id="learn-to-accept-negative-feedback" href="#learn-to-accept-negative-feedback"><h2>Learn to Accept "Negative" Feedback</h2></a>
<p>Earlier in my career as I started working closer with designers on a day-to-day basis, I realized they have a superpower that developers tend to lack: receiving criticism of their work and not taking it personally. I was working with a designer friend on a new project when he pitched his designs to a few others at the company. In my eyes, the others were somewhat brutal with their feedback. I was shocked at how calm and engaged he was chatting with his colleagues about his work, right after he took a bit of a beating. He was asking clarifying questions on how the design could be improved, jotting down notes, and acting as though this was normal. How foreign to me as a developer! He just spent a full day working through the designs to be told they were all wrong and not good. And yet, it didn't bother him? How is this possible!?</p>
<p>I asked him how that made him feel and if that type of feedback loop was normal. I was surprised to learn that, at least according to him, yes it was normal. He then explained how it makes him a better designer and how he looks at it as just that: feedback. He gets to decide what he changes or not by taking in all of the feedback, processing it, and coming up with a different solution that he thinks is better. It goes back to the fall fast mentality above. From that day forward, I looked at feedback of my work in a completely different light by removing the personal factor from the feedback process.</p>
<p>Most developers can probably relate: getting Pull Request feedback can really suck. But it doesn't have to. I know for me personally, prior to the point mentioned above, receiving feedback on something I spent days on and being told "this is all wrong" was a very difficult pill to swallow. It's not a reflection on you as a person though - it just so happens that you didn't consider all of these other things your colleagues have thought of.</p>
<p>Imagine working in a factory building parts that have extremely tight tolerances. There's very little room for error when you're manufacturing these particular parts. If at the end of the day, someone reviews the parts you've created and throws out the ones that weren't in spec, would you be upset and take it personally? Probably not. It should be a mutual understanding that the items created must be within spec. Some items you made weren't within spec, some were. It may drive you to do better the next day. So why do we, as software engineers take things so personally on Pull Request reviews?</p>
<p><strong>Instead of receiving feedback and taking it personally, look at it as a chance to improve.</strong> Ask questions. Learn more as to why someone thinks their suggestion is better. Have you considered that maybe you <em>are</em> wrong? Being open minded and not carrying an ego goes an extremely long way. Have a civil debate back and forth on topics you may not agree with so that you can understand the counter-arguments and then come to a path forward with your team. Additionally, being able to change your opinions on a topic as new information comes in is extremely important.</p>
<p>All of this is about learning and becoming a better engineer. Obviously when you're giving feedback, you shouldn't be an asshole - that's not what I'm saying. My point is to frame the feedback loop as a positive learning experience rather than one you dread. If every time you put up a Pull Request you feel anxious - ask yourself why? It's not a personal reflection of you as a human being - it's simply the work you output for the job you are paid to do. There's always room for improvement and that's okay. It's all about growing.</p>
<a id="people-problems" href="#people-problems"><h2>People Problems</h2></a>
<p>I was interviewing a candidate when I worked at Toyota in 2017 and I heard a response that has stuck with me to this day:</p>
<blockquote>
<p>"People problems" are harder than the technical problems.</p>
</blockquote>
<p>What a thought provoking statement. Sure, figuring out solutions to technical problems can be challenging, but have you tried leading a project with completely different individuals towards a goal, accomplishing it in a reasonable amount of time and at a high quality bar, while also attempting to grow the team, while also not driving each other insane? It's <em>so hard</em>. Everyone has different needs. Some folks are stronger in particular areas than others. Sometimes folks don't get along at all.</p>
<p>This is where I have a ton of respect for places that focus on culture fit rather than technical ability. The teams I enjoyed working with the most were the ones where we all got along and had a good time, rather than the teams that were full of rockstars and none of us liked each other. There's definitely an art to team building.</p>
<p>But even if you are on a team where you don't agree with someone, I go back to what I said above about feedback. Learn from those people and understand their view points. Maybe you think they are overly picky in Pull Request reviews - why are they that way? Are their opinions valid? Maybe it's boils down to a personality difference.</p>
<p>From my experience when I encountered people like this, it turned out they <em>really did</em> know what they were talking about. It offered me a lot to learn - it turned out that their communication style was more direct and different than mine. I was taking their feedback too personally. As I got to learn more about them and what drives them, I realized they actually were really nice human beings that did really high quality work. I also learned a ton from them. <strong>I recommend trying your best to solve "people problems" as well as the technical problems.</strong></p>
<p>Learn to gel with the folks you work with. Work doesn't have to be a slog - we're all human and crave connection to some degree. Working in a team is all about alignment and driving towards a common goal. Find ways to make work more enjoyable. You may gain some lifelong friends!</p>
<p>Lastly, I recently participated in a <a href="https://www.discprofile.com/what-is-disc/disc-styles" target="_blank" rel="noopener noreferrer">DiSC assessment</a> with my team. As most developers likely would, I was thinking: "I'm not sure this is going to be a good use of my time"; however, I walked away thinking the exact opposite. It was extremely helpful. Learning the stressors and motivators of your teammates goes a long way. Maybe large ambiguous tasks make one person extremely anxious, while for another those tasks are where they excel. Doing this assessment helps outline where folks fall in different categories so that you can work better as a team. This type of assessment is a bit of a catalyst to what I described above - you get to learn more about your team much faster than the more organic process and get to cut down on "people problems".</p>
<a id="learn-to-say-no-protect-your-time" href="#learn-to-say-no-protect-your-time"><h2>Learn to Say No &amp; Protect Your Time</h2></a>
<p>It's super important to ensure your mental and physical health is taken care of before anything else. Without being in a good mental and physical state, you won't be you (is this a Snickers commercial?). One recommendation I received is to say "no" more. <strong>It's important to protect your time.</strong> If you're in the position where you <em>can</em> decline something, and you think it'll tip you over the edge, just say no. There are obviously more professional ways to politely decline things as well.</p>
<p>Even simple things like someone scheduling a meeting outside of your working hours. It's appropriate to decline and say something along the lines of "is there a chance we could move this between 7am-3pm?". Or simply decline and suggest a time block that works better for you. This is one of those tips that doesn't get talked about enough in the workplace. Obviously there may be some cases where very important meetings need to fall outside of your typical schedule; however, don't be afraid to protect your time when you feel it is appropriate to.</p>
<a id="struggle-a-little" href="#struggle-a-little"><h2>Struggle a Little</h2></a>
<p>There is absolutely nothing wrong with asking for help, but sometimes struggling with the issue for a few more hours could mean you get to the bottom of it. If you're on a time crunch and there's a fire in production that you're on the hook for - you should ask for help immediately. But if there isn't an extreme urgency, there's an opportunity for you to dig deeper on the problem. It goes back to above on being uncomfortable - not understanding why something isn't working as you'd expect is an uncomfortable feeling, but <strong>taking a step back and evaluating what could be going wrong, then digging in to see if your hypotheses are correct means more opportunities for learning and growth.</strong></p>
<p>One of the patterns I was taught early on in my career was to dig through stack traces and attempt to figure out the root cause of the issue. You learn a ton by doing this and you get exposed to things you may not otherwise see. It also sharpens your troubleshooting skills. Sometimes you may find a legit bug in a library - this means an opportunity to contribute back to an open source project in a meaningful way. Other times you may hit a dead end - that's okay! You made a great attempt.</p>
<p>One tactic I use is going for a walk or stepping away from the problem. Continuing something for multiple hours straight and not making any progress can lead to a dead end, but sometimes an idea will come to you out of the blue if you step away for a bit. For me, my best ideas come about when I'm by myself doing a mundune task like driving or doing yard work. Give yourself a break for a moment and see if any new ideas come to mind.</p>
<p>When you do end up asking for help, it's important to describe the problem and also show everything you've tried. That will give whoever is pairing with you an idea of what you've thought through and tested already. Maybe it'll give them some clues as well. It also shows that you respect your coworker's time by letting them know you really <em>are</em> stuck. Asking for help isn't a bad thing, but it's important to feel a little uncomfortable at times to see if you can figure it out before phoning a friend.</p>
<a id="publish-and-maintain-a-package" href="#publish-and-maintain-a-package"><h2>Publish and Maintain a Package</h2></a>
<p>Now onto a more technical suggestion…</p>
<p>If you're abstracted away from a lot of concepts at your workplace, try spinning up a side project or get on a project at work that is greenfield. <strong>I firmly believe every developer should create a new package from scratch, publish it to <code><span><span style="color:var(--sh-identifier)">npm</span></span></code>, and maintain it for some period of time</strong>. Being able to create packages on GitHub, build them, publish them to <code><span><span style="color:var(--sh-identifier)">npm</span></span></code>, setting up testing, linting, prettier, and general repository maintenance is one way to learn a <em>lot</em> of skills that are extremely applicable to your job in a short amount of time. It really levels you up fairly quickly.</p>
<p>From a high level, doing this means you'll:</p>
<ul>
<li>Learn the different fields in a <code><span><span style="color:var(--sh-identifier)">package</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">json</span></span></code> and what they do</li>
<li>Add tooling to build your packages</li>
<li>Setup a test framework</li>
<li>Setup GitHub Actions</li>
<li>Wire up tests</li>
<li>Update your GitHub Actions workflow to include running your tests</li>
<li>Setup up ESLint and prettier from scratch</li>
<li>Publish to <code><span><span style="color:var(--sh-identifier)">npm</span></span></code>
<ul>
<li>I recommend <a href="https://github.com/changesets/changesets" target="_blank" rel="noopener noreferrer">changesets</a> to help!</li>
</ul>
</li>
<li>Write documentation on how folks use your work</li>
<li>Setup TypeScript configurations (if using TS)</li>
</ul>
<p>All of above takes a lot of effort, but you'll walk away knowing <em>so</em> much more. The experience you get setting up these tools for the first time is well worth it. You'll also appreciate the tooling that is abstracted away from you at larger organizations, because you'll have a better understanding of what all is going on. Not only will you learn a bunch by doing this, it's also fun to do!</p>
<a id="that-s-all-i-got" href="#that-s-all-i-got"><h2>That's All I Got</h2></a>
<p>These are the things I've done to improve as a software engineer over the years. I hope this is helpful for others. Let me know on <a href="https://www.linkedin.com/in/ynotdraw/" target="_blank" rel="noopener noreferrer">LinkedIn</a> or <a href="https://bsky.app/profile/ynotdraw.bsky.social" target="_blank" rel="noopener noreferrer">Bluesky</a>!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Productize Your Design System]]></title>
            <link>https://tonyward.dev/articles/productize-your-design-system</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/productize-your-design-system</guid>
            <pubDate>Wed, 03 Apr 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<blockquote>
<p>I'm trying to write at least once a month, so I figured I'd try a slightly different format for this topic to save myself some time. I decided to have a little more fun with it and it's a bit more loose than usual. Let me know what you think!</p>
</blockquote>
<p><em>Productize it</em>. That's probably the most important piece of advice I'd offer to a team or individual starting a Design System today. This goes for both startups and large organizations. You may be wondering <em>"but why"</em>?</p>
<div><div><img alt="A gif of Ryan Reynolds asking 'but why?'." loading="lazy" width="480" height="259" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbut-why.2znowt7lrbq90.gif&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbut-why.2znowt7lrbq90.gif&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbut-why.2znowt7lrbq90.gif&amp;w=1080&amp;q=75"></div></div>
<p><strong>If the Design System is a side project, it'll never reach its full potential.</strong> Instead, it could quickly become a burden, rather than being a jump start for designers and engineers.</p>
<p>Too many times at places both big and small, I've seen Design Systems start out as a grassroots effort. A couple of individuals <em>(it's me, hi, I'm the problem it's me)</em> get really excited about building one, so we go off and begin chasing our dreams <em>(don't let your dreams be dreams)</em>. I don't think being a grassroots effort is a bad thing necessarily. People don't always understand the value of something until it's tangible. Simply speaking about the benefits without proof isn't enough. So you do what you must: make something tangible.</p>
<p>There's a lot to be said about building something and letting folks get their hands on it. It's even better when you convert people who are skeptical about your project by showing them how much your solution brings them value on a day-to-day basis.</p>
<blockquote>
<p>"Oh, I don't have to build all of these components myself? I can slap these existing elements together to build a feature? Far out!".</p>
</blockquote>
<p>That's been my experience building Design Systems full time since 2017. Sometimes discussing the advantages isn't the same as someone experiencing it for themselves - and that's okay! In fact, I think being able to prove your thing does what you said it would is extremely important. It's how you build credibility and trust with others.</p>
<p>At some point <strong>you need buy-in from leadership to ensure the Design System remains successful</strong>. Otherwise you'll find yourself getting swamped with feature work and never have time to maintain, improve, or further develop the Design System. This means you and your pals who were previously very excited about the Design System end up becoming stressed due to needing to ship things elsewhere and no longer having the time to work on it. This is the danger zone when it comes to side projects. We can fix this though! Pitch your Design System as a real product.</p>
<p>Not sure how to get leadership buy-in? <a href="https://templates.designsystem.university/products/design-systems-buy-in-deck" target="_blank" rel="noopener noreferrer">Design Systems University</a> has you covered with a slide deck. I was actually sent this a day or two ago as I began writing this article. Great timing!</p>
<div><div><img alt="The Drake no/yes meme with the text for Drake No as 'building your Design System as a side project' and the Drake Yes as 'building your Design System as a product'." loading="lazy" width="999" height="799" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdrake.2--l_wza1qdol.jpeg&amp;w=1080&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdrake.2--l_wza1qdol.jpeg&amp;w=2048&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fdrake.2--l_wza1qdol.jpeg&amp;w=2048&amp;q=75"></div></div>
<p>What will productizing get you? Sustainability, stability, and focus. <strong>Pitch your Design System as a real product, because it is!</strong> Your customers are your internal users and team members. Your job as a Design System contributor is to make design and engineering lives easier. You provide the Lego building blocks and your "customers" construct a feature or product. There's a ton of value in that - you are helping the organization ship faster, at a high quality bar. It also means you can hunker down and focus, rather than bouncing around between completely different concepts.</p>
<p>By productizing your Design System, it means you'll also have the opportunity to form a real team around it. To me, an ideal team looks something like the following, which is pretty similar to the <a href="https://templates.designsystem.university/products/design-systems-buy-in-deck" target="_blank" rel="noopener noreferrer">Design System University</a> recommendation with a few minor tweaks:</p>
<ul>
<li>Engineering Manager<!-- -->
<ul>
<li>Frontend Engineers</li>
</ul>
</li>
<li>Design Manager<!-- -->
<ul>
<li>Designers</li>
</ul>
</li>
<li>Accessibility Expert(s)<!-- -->
<ul>
<li>For both Design + Engineering</li>
</ul>
</li>
<li>QA folks</li>
<li>Researcher(s) / Producer(s)</li>
<li>Content Writer(s)</li>
<li>Product Owner</li>
</ul>
<p>Looks familiar, eh? One might say, it looks similar to a product team - because it is!</p>
<p>By having a real, dedicated team it means the Design System can flourish. Bugs can be addressed in a timely manner rather than being thrown into an abyss that never gets looked at. New features can be added to the Design System, which further improves the lives of the people using it. Components can be properly documented and given the time and care they deserve, rather than pumping something out as fast as possible due to a deadline. Researchers can help provide data-driven metrics on your Design System and patterns in your products to help ensure you're making the correct decisions. Your applications will have visual UI/UX consistency. The list goes on and on.</p>
<p>That's my 2 cents on the topic! <em>Productize it</em>. Have a good one and see you next time! I've started my next article which will be about measuring success in Design Systems.</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Roaming Shapes for Empty States]]></title>
            <link>https://tonyward.dev/articles/roaming-shapes</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/roaming-shapes</guid>
            <pubDate>Fri, 15 Mar 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I really enjoy sweating the details and going the extra mile to elevate experiences in apps. One example I've worked on recently had a few empty states that were missing <em>something</em>. The app had this shape theme, where there were shapes all throughout the design. One random idea came to mind: why not let those shapes float around a bit?</p>
<p>Adding micro animations to empty states are a great way to add a little extra detail from a UI/UX perspective. A lot of times empty states use illustrations that look quite nice already; however, adding some movement makes things more fun.</p>
<div><svg width="77" height="56" viewBox="0 0 77 56" fill="none" aria-hidden="true"><path d="M46 36H32C31.4701 35.9984 30.9623 35.7872 30.5875 35.4125C30.2128 35.0377 30.0016 34.5299 30 34V20C30.0021 19.4702 30.2135 18.9627 30.5881 18.5881C30.9627 18.2135 31.4702 18.0021 32 18H46C46.5299 18.0016 47.0377 18.2128 47.4125 18.5875C47.7872 18.9623 47.9984 19.4701 48 20V34C48 34.5304 47.7893 35.0391 47.4142 35.4142C47.0391 35.7893 46.5304 36 46 36ZM33.2 36L37.4 31.15L41.6 26.3L44.8 29.7L48 33.1L33.2 36ZM35.8 21.4C36.3321 21.3998 36.8478 21.5842 37.2593 21.9217C37.6707 22.2591 37.9524 22.7288 38.0564 23.2507C38.1603 23.7725 38.0801 24.3143 37.8294 24.7837C37.5787 25.253 37.173 25.6209 36.6814 25.8247C36.1898 26.0285 35.6428 26.0555 35.1336 25.9011C34.6243 25.7468 34.1843 25.4206 33.8886 24.9783C33.5929 24.5359 33.4597 24.0046 33.5117 23.4751C33.5638 22.9455 33.7978 22.4503 34.174 22.074C34.3871 21.8599 34.6406 21.6901 34.9196 21.5745C35.1987 21.4588 35.4979 21.3995 35.8 21.4V21.4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M58.6785 8.26405C58.9702 7.97233 59.3659 7.80844 59.7784 7.80844C60.191 7.80844 60.5866 7.97233 60.8784 8.26405C61.1701 8.55577 61.334 8.95143 61.334 9.36399C61.334 9.77655 61.1701 10.1722 60.8784 10.4639C60.7326 10.5954 60.5601 10.6937 60.3728 10.7522C60.1854 10.8107 59.9876 10.828 59.793 10.803C59.3758 10.788 58.9142 10.6996 58.4428 10.6996M62.6069 6.53557C63.4248 7.35354 63.9338 8.42982 64.0472 9.58102C64.1606 10.7322 63.8713 11.8871 63.2286 12.8489C62.5859 13.8108 61.6296 14.52 60.5227 14.8558C59.4157 15.1916 58.2266 15.1331 57.1578 14.6904C56.0891 14.2478 55.207 13.4482 54.6617 12.428C54.1164 11.4078 53.9417 10.2301 54.1674 9.09559C54.3931 7.96104 55.0051 6.93985 55.8994 6.206C56.7936 5.47216 57.9145 5.07107 59.0713 5.07107C59.7281 5.07023 60.3785 5.19917 60.9852 5.4505C61.592 5.70183 62.1431 6.07058 62.6069 6.53557V6.53557ZM57.225 11.9174L57.2643 11.8781L57.225 11.9174Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="50%"></path><path d="M18.6886 41.2523C18.4519 40.9143 18.0907 40.6842 17.6845 40.6126C17.2782 40.541 16.8601 40.6336 16.5221 40.8703C16.1842 41.1069 15.954 41.4681 15.8824 41.8744C15.8108 42.2807 15.9035 42.6988 16.1401 43.0367C16.2608 43.1915 16.4136 43.3183 16.588 43.4085C16.7623 43.4986 16.9541 43.55 17.1501 43.5591C17.5636 43.6168 18.0335 43.6099 18.4978 43.6918M15.12 38.8679C14.1725 39.5314 13.4843 40.503 13.1727 41.617C12.8612 42.731 12.9455 43.9186 13.4114 44.9774C13.8773 46.0362 14.6959 46.9007 15.7278 47.4236C16.7596 47.9465 17.9408 48.0955 19.0702 47.8451C20.1995 47.5947 21.2071 46.9605 21.9213 46.0505C22.6355 45.1405 23.012 44.011 22.9867 42.8545C22.9615 41.698 22.536 40.5861 21.7829 39.7081C21.0297 38.8301 19.9954 38.2405 18.8562 38.0396C18.2095 37.9247 17.5466 37.9388 16.9054 38.0809C16.2643 38.2231 15.6575 38.4905 15.12 38.8679V38.8679ZM19.4856 45.1026L19.4537 45.0571L19.4856 45.1026Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="50%"></path><circle cx="18" cy="12" r="3" fill="currentColor" fill-opacity="50%"></circle><circle cx="6.5" cy="1.5" r="1.5" fill="currentColor" fill-opacity="50%"></circle><circle cx="70.5" cy="1.5" r="1.5" fill="currentColor" fill-opacity="50%"></circle><circle cx="75.5" cy="54.5" r="1.5" fill="currentColor" fill-opacity="50%"></circle><circle cx="6.5" cy="54.5" r="1.5" fill="currentColor" fill-opacity="50%" style="animation-delay:2s"></circle><circle cx="58" cy="42" r="3" fill="currentColor" fill-opacity="50%"></circle><path d="M-0.000199058 28.237L9.41478 25.1779L6.23685 31.415L-0.000199058 28.237Z" fill="currentColor" fill-opacity="50%" style="animation-delay:1s"></path><path d="M67.8472 34.242L71.3948 25L74.242 31.3948L67.8472 34.242Z" fill="currentColor" fill-opacity="50%" style="animation-delay:2s"></path></svg><p>There isn't any media for this item.</p></div>
<p>Maybe it's too subtle? Here's a sped up version.</p>
<div><svg width="77" height="56" viewBox="0 0 77 56" fill="none" aria-hidden="true"><path d="M46 36H32C31.4701 35.9984 30.9623 35.7872 30.5875 35.4125C30.2128 35.0377 30.0016 34.5299 30 34V20C30.0021 19.4702 30.2135 18.9627 30.5881 18.5881C30.9627 18.2135 31.4702 18.0021 32 18H46C46.5299 18.0016 47.0377 18.2128 47.4125 18.5875C47.7872 18.9623 47.9984 19.4701 48 20V34C48 34.5304 47.7893 35.0391 47.4142 35.4142C47.0391 35.7893 46.5304 36 46 36ZM33.2 36L37.4 31.15L41.6 26.3L44.8 29.7L48 33.1L33.2 36ZM35.8 21.4C36.3321 21.3998 36.8478 21.5842 37.2593 21.9217C37.6707 22.2591 37.9524 22.7288 38.0564 23.2507C38.1603 23.7725 38.0801 24.3143 37.8294 24.7837C37.5787 25.253 37.173 25.6209 36.6814 25.8247C36.1898 26.0285 35.6428 26.0555 35.1336 25.9011C34.6243 25.7468 34.1843 25.4206 33.8886 24.9783C33.5929 24.5359 33.4597 24.0046 33.5117 23.4751C33.5638 22.9455 33.7978 22.4503 34.174 22.074C34.3871 21.8599 34.6406 21.6901 34.9196 21.5745C35.1987 21.4588 35.4979 21.3995 35.8 21.4V21.4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M58.6785 8.26405C58.9702 7.97233 59.3659 7.80844 59.7784 7.80844C60.191 7.80844 60.5866 7.97233 60.8784 8.26405C61.1701 8.55577 61.334 8.95143 61.334 9.36399C61.334 9.77655 61.1701 10.1722 60.8784 10.4639C60.7326 10.5954 60.5601 10.6937 60.3728 10.7522C60.1854 10.8107 59.9876 10.828 59.793 10.803C59.3758 10.788 58.9142 10.6996 58.4428 10.6996M62.6069 6.53557C63.4248 7.35354 63.9338 8.42982 64.0472 9.58102C64.1606 10.7322 63.8713 11.8871 63.2286 12.8489C62.5859 13.8108 61.6296 14.52 60.5227 14.8558C59.4157 15.1916 58.2266 15.1331 57.1578 14.6904C56.0891 14.2478 55.207 13.4482 54.6617 12.428C54.1164 11.4078 53.9417 10.2301 54.1674 9.09559C54.3931 7.96104 55.0051 6.93985 55.8994 6.206C56.7936 5.47216 57.9145 5.07107 59.0713 5.07107C59.7281 5.07023 60.3785 5.19917 60.9852 5.4505C61.592 5.70183 62.1431 6.07058 62.6069 6.53557V6.53557ZM57.225 11.9174L57.2643 11.8781L57.225 11.9174Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="50%"></path><path d="M18.6886 41.2523C18.4519 40.9143 18.0907 40.6842 17.6845 40.6126C17.2782 40.541 16.8601 40.6336 16.5221 40.8703C16.1842 41.1069 15.954 41.4681 15.8824 41.8744C15.8108 42.2807 15.9035 42.6988 16.1401 43.0367C16.2608 43.1915 16.4136 43.3183 16.588 43.4085C16.7623 43.4986 16.9541 43.55 17.1501 43.5591C17.5636 43.6168 18.0335 43.6099 18.4978 43.6918M15.12 38.8679C14.1725 39.5314 13.4843 40.503 13.1727 41.617C12.8612 42.731 12.9455 43.9186 13.4114 44.9774C13.8773 46.0362 14.6959 46.9007 15.7278 47.4236C16.7596 47.9465 17.9408 48.0955 19.0702 47.8451C20.1995 47.5947 21.2071 46.9605 21.9213 46.0505C22.6355 45.1405 23.012 44.011 22.9867 42.8545C22.9615 41.698 22.536 40.5861 21.7829 39.7081C21.0297 38.8301 19.9954 38.2405 18.8562 38.0396C18.2095 37.9247 17.5466 37.9388 16.9054 38.0809C16.2643 38.2231 15.6575 38.4905 15.12 38.8679V38.8679ZM19.4856 45.1026L19.4537 45.0571L19.4856 45.1026Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="50%"></path><circle cx="18" cy="12" r="3" fill="currentColor" fill-opacity="50%"></circle><circle cx="6.5" cy="1.5" r="1.5" fill="currentColor" fill-opacity="50%"></circle><circle cx="70.5" cy="1.5" r="1.5" fill="currentColor" fill-opacity="50%"></circle><circle cx="75.5" cy="54.5" r="1.5" fill="currentColor" fill-opacity="50%"></circle><circle cx="6.5" cy="54.5" r="1.5" fill="currentColor" fill-opacity="50%" style="animation-delay:2s"></circle><circle cx="58" cy="42" r="3" fill="currentColor" fill-opacity="50%"></circle><path d="M-0.000199058 28.237L9.41478 25.1779L6.23685 31.415L-0.000199058 28.237Z" fill="currentColor" fill-opacity="50%" style="animation-delay:1s"></path><path d="M67.8472 34.242L71.3948 25L74.242 31.3948L67.8472 34.242Z" fill="currentColor" fill-opacity="50%" style="animation-delay:2s"></path></svg><p>There isn't any media for this item.</p></div>
<p>Notice how each individual shape around the one in the center drifts around a bit? Fun! After creating one of these, it was essentially rinse and repeat. I kept rolling with this pattern for other empty states as well in this particular application, swapping out individual icons and tweaking their animations slightly. It was a good time.</p>
<hr>
<p>So how did I go about building this?</p>
<p>I ended up going over to Figma and combining multiple <a href="https://lifeomic.github.io/chromicons.com/" target="_blank" rel="noopener noreferrer">chromicon icons</a> (free, open source icons I helped build!) into a single frame. Once I had the frame, I exported it to a raw SVG.</p>
<p>From there, we can use the raw SVG and animate each individual shape using CSS. There are really awesome tools like <a href="https://gsap.com" target="_blank" rel="noopener noreferrer">GSAP</a> and <a href="https://www.framer.com/motion/" target="_blank" rel="noopener noreferrer">Framer Motion</a>, but I like to try using only CSS before reaching for another tool. For this case in particular, making shapes move is pretty straight forward.</p>
<p>I ended up creating a couple of options for different shapes, as you can see, with staggered start delays and animation durations. All of them leveraged keyframes and used the <code><span><span style="color:var(--sh-identifier)">translate</span></span></code> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate" target="_blank" rel="noopener noreferrer">CSS property</a>.</p>
<p>You may notice the "hero" icon in the middle is a different shade than the surrounding shapes. I ended up using the <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-opacity" target="_blank" rel="noopener noreferrer">stroke-opacity</a> attribute (or <code><span><span style="color:var(--sh-identifier)">strokeOpacity</span></span></code> in React) to ensure the main icon is front and center, and the surrounding icons have a much lower opacity and are thus lighter.</p>
<p>The shapes roaming in this example are pretty subtle, but if you begin adding more motion in your empty states you should consider using the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion" target="_blank" rel="noopener noreferrer">prefers-reduced-motion</a> media query. If your animation duration is pretty short, you'd use this media query to increase the duration so that it reduces the amount of motion on the screen.</p>
<p>That's it! Pretty straight forward! I've pasted the code below for React, but you can also find a pure HTML + CSS version on my <a href="https://codepen.io/ynotdraw/pen/NWmWaqY" target="_blank" rel="noopener noreferrer">CodePen</a>. Happy coding!</p>
<pre><code><span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">./styles.module.css</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">function</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">RoamingShape</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">svg</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">svg</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-property)">width</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">77</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-property)">height</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">56</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-property)">viewBox</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">0 0 77 56</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">none</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-property)">aria-</span><span style="color:var(--sh-property)">hidden</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">true</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">path</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">d</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">M46 36H32C31.4701 35.9984 30.9623 35.7872 30.5875 35.4125C30.2128 35.0377 30.0016 34.5299 30 34V20C30.0021 19.4702 30.2135 18.9627 30.5881 18.5881C30.9627 18.2135 31.4702 18.0021 32 18H46C46.5299 18.0016 47.0377 18.2128 47.4125 18.5875C47.7872 18.9623 47.9984 19.4701 48 20V34C48 34.5304 47.7893 35.0391 47.4142 35.4142C47.0391 35.7893 46.5304 36 46 36ZM33.2 36L37.4 31.15L41.6 26.3L44.8 29.7L48 33.1L33.2 36ZM35.8 21.4C36.3321 21.3998 36.8478 21.5842 37.2593 21.9217C37.6707 22.2591 37.9524 22.7288 38.0564 23.2507C38.1603 23.7725 38.0801 24.3143 37.8294 24.7837C37.5787 25.253 37.173 25.6209 36.6814 25.8247C36.1898 26.0285 35.6428 26.0555 35.1336 25.9011C34.6243 25.7468 34.1843 25.4206 33.8886 24.9783C33.5929 24.5359 33.4597 24.0046 33.5117 23.4751C33.5638 22.9455 33.7978 22.4503 34.174 22.074C34.3871 21.8599 34.6406 21.6901 34.9196 21.5745C35.1987 21.4588 35.4979 21.3995 35.8 21.4V21.4Z</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">stroke</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeWidth</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">2</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeLinecap</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">round</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeLinejoin</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">round</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">path</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">path</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-1</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">d</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">M58.6785 8.26405C58.9702 7.97233 59.3659 7.80844 59.7784 7.80844C60.191 7.80844 60.5866 7.97233 60.8784 8.26405C61.1701 8.55577 61.334 8.95143 61.334 9.36399C61.334 9.77655 61.1701 10.1722 60.8784 10.4639C60.7326 10.5954 60.5601 10.6937 60.3728 10.7522C60.1854 10.8107 59.9876 10.828 59.793 10.803C59.3758 10.788 58.9142 10.6996 58.4428 10.6996M62.6069 6.53557C63.4248 7.35354 63.9338 8.42982 64.0472 9.58102C64.1606 10.7322 63.8713 11.8871 63.2286 12.8489C62.5859 13.8108 61.6296 14.52 60.5227 14.8558C59.4157 15.1916 58.2266 15.1331 57.1578 14.6904C56.0891 14.2478 55.207 13.4482 54.6617 12.428C54.1164 11.4078 53.9417 10.2301 54.1674 9.09559C54.3931 7.96104 55.0051 6.93985 55.8994 6.206C56.7936 5.47216 57.9145 5.07107 59.0713 5.07107C59.7281 5.07023 60.3785 5.19917 60.9852 5.4505C61.592 5.70183 62.1431 6.07058 62.6069 6.53557V6.53557ZM57.225 11.9174L57.2643 11.8781L57.225 11.9174Z</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">stroke</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeWidth</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeLinecap</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">round</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeLinejoin</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">round</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">path</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">path</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-3</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">d</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">M18.6886 41.2523C18.4519 40.9143 18.0907 40.6842 17.6845 40.6126C17.2782 40.541 16.8601 40.6336 16.5221 40.8703C16.1842 41.1069 15.954 41.4681 15.8824 41.8744C15.8108 42.2807 15.9035 42.6988 16.1401 43.0367C16.2608 43.1915 16.4136 43.3183 16.588 43.4085C16.7623 43.4986 16.9541 43.55 17.1501 43.5591C17.5636 43.6168 18.0335 43.6099 18.4978 43.6918M15.12 38.8679C14.1725 39.5314 13.4843 40.503 13.1727 41.617C12.8612 42.731 12.9455 43.9186 13.4114 44.9774C13.8773 46.0362 14.6959 46.9007 15.7278 47.4236C16.7596 47.9465 17.9408 48.0955 19.0702 47.8451C20.1995 47.5947 21.2071 46.9605 21.9213 46.0505C22.6355 45.1405 23.012 44.011 22.9867 42.8545C22.9615 41.698 22.536 40.5861 21.7829 39.7081C21.0297 38.8301 19.9954 38.2405 18.8562 38.0396C18.2095 37.9247 17.5466 37.9388 16.9054 38.0809C16.2643 38.2231 15.6575 38.4905 15.12 38.8679V38.8679ZM19.4856 45.1026L19.4537 45.0571L19.4856 45.1026Z</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">stroke</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeWidth</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeLinecap</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">round</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeLinejoin</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">round</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">strokeOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">path</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">circle</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-1</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cx</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">18</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cy</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">12</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">r</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">3</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fillOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">circle</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">circle</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-2</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cx</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">6.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cy</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">r</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fillOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">circle</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">circle</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-3</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cx</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">70.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cy</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">r</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fillOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">circle</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">circle</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-2</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cx</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">75.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cy</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">54.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">r</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fillOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">circle</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">circle</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-3</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cx</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">6.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cy</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">54.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">r</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">1.5</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fillOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">style</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">animationDelay</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">2s</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">circle</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">circle</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-1</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cx</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">58</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">cy</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">42</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">r</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">3</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fillOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">circle</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">path</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-3</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">d</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">M-0.000199058 28.237L9.41478 25.1779L6.23685 31.415L-0.000199058 28.237Z</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fillOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">style</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">animationDelay</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">1s</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">path</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">path</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">animate-shape-roam-2</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">d</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">M67.8472 34.242L71.3948 25L74.242 31.3948L67.8472 34.242Z</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fill</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">currentColor</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">fillOpacity</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">50%</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-property)">style</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">animationDelay</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">2s</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">path</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">svg</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">svg</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">height</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">11rem</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">width</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">11rem</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">#</span><span style="color:var(--sh-class)">9ca3af</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">keyframes</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">50</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">1px</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">1px</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">100</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">keyframes</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">2</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">50</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">2px</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">4px</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">100</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">keyframes</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">3</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">50</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">2px</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">2px</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">100</span><span style="color:var(--sh-sign)">%</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">transform</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">translate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">/*</span></span>
<span><span style="color:var(--sh-comment)">  If your animation duration is short,</span></span>
<span><span style="color:var(--sh-comment)">  consider using the prefers-reduced-motion</span></span>
<span><span style="color:var(--sh-comment)">  media query to respect user's preferences.</span></span>
<span><span style="color:var(--sh-comment)"></span></span>
<span><span style="color:var(--sh-comment)">  In this case it's probably okay, but if you</span></span>
<span><span style="color:var(--sh-comment)">  get to a 1-2s duration, you may consider</span></span>
<span><span style="color:var(--sh-comment)">  adding the media query and bumping it up to</span></span>
<span><span style="color:var(--sh-comment)">  something more like 6-8s here.</span></span>
<span><span style="color:var(--sh-comment)">*/</span></span>
<span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">animate</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">webkit</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">animation</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">6s</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">infinite</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">animation</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">6s</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">infinite</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">animate</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">2</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">webkit</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">animation</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">2</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">7s</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">infinite</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">animation</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">2</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">7s</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">infinite</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">animate</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">3</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">webkit</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">animation</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">3</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">8s</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">infinite</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">animation</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">shape</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">roam</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-class)">3</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">8s</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">infinite</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Web Components 🤝 Design Systems]]></title>
            <link>https://tonyward.dev/articles/web-components-design-system</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/web-components-design-system</guid>
            <pubDate>Mon, 19 Feb 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I've been using <a href="https://lit.dev" target="_blank" rel="noopener noreferrer">Lit</a> for a few months now to build a Design System with web components and it's 🔥 lit 🔥, as the kids would say. I was admittedly a bit skeptical at first, but I now see the advantages. I'll outline why I think web components make sense for products that are multi-framework. My opinions may change over time. If/When they do, I'll be sure to update this post or follow up in another article.</p>
<a id="tl-dr" href="#tl-dr"><h2>TL;DR</h2></a>
<p>If you'd rather read my high-level opinions rather than my longer ramblings, here they are:</p>
<ul>
<li>If you're building a Design System for multiple frameworks, web components mean you build it once and can use them everywhere</li>
<li>Web Components provide encapsulation, which is really important in Design Systems<!-- -->
<ul>
<li>You have full control over the Component API and styling</li>
<li>This helps enforce rigidity versus flexibility in product Design Systems</li>
</ul>
</li>
<li>Web components use the platform and feel like "getting back to the basics"</li>
<li>Interacting with web components in each framework differs and could be a bit of a mental model shift by your fellow developers<!-- -->
<ul>
<li>For example, using JS to attach event listeners, rather than a simple <code><span><span style="color:var(--sh-identifier)">onClick</span></span></code> property.</li>
</ul>
</li>
</ul>
<a id="web-components" href="#web-components"><h2>Web Components</h2></a>
<div><div><img alt="An image of multiple components" loading="lazy" width="300" height="200" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 200'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAUUlEQVR42nWMOw6AMAxDe/9jcDEGhoqVJiJ/cCXE1gxJ5Ge7PYtpnnncJI6bVSWi7j7BZbb1/aShqgDYH5guvBEQOhMzgyHc/tLIHCqoMjO4X6v0dTw1xlgdAAAAAElFTkSuQmCC'/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcomponents.3huv_981dtlb2.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcomponents.3huv_981dtlb2.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fcomponents.3huv_981dtlb2.png&amp;w=640&amp;q=75"></div></div>
<p>Have you used an <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">iframe</span><span style="color:var(--sh-sign)">&gt;</span></span></code> before? Things don't leak out from the iframe and you don't get full access <em>into</em> it either. The iframe can dispatch events when certain things happen that consumers can listen for and react to. This is essentially a similar concept to web components. Users render your component on their page, they provide some attributes and add event listeners and that's about it. Other than that, the web component just does its thing.</p>
<p>The web component contains the full UI/UX, without exposing ways to modify anything internal to it. You have access to some knobs to adjust things via attributes, can call methods on the component to invoke some internal action, and get notified via event listeners when things happen, but that's about it. All you know is that you need a button element, so you render a <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span></code>. Maybe this separation of concerns is a good thing? It means you are signing up to use the component as is, and only get scoped access.</p>
<p>Web components are essentially an opaque box. You put some things in and get some things out. You don't get to look into the box! Contrast that to typical components, where you can adjust things like styling and modify the inner workings of a component if you <em>really</em> try. The encapsulation of web components offers a lot of advantages, in my opinion, which I'll share in a moment.</p>
<a id="multiple-frameworks" href="#multiple-frameworks"><h2>Multiple Frameworks</h2></a>
<div><div><img alt="An image of a woman contemplating between multiple options." loading="lazy" width="300" height="200" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 240'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAIAAABxZ0isAAAAbUlEQVR42l3MsQ5AQBAEUP9fi9IvqFApSfwCUYgEQcI5CW5vd52c4mKKaV5mPGYmIiH29bokAjItcGsizwAiFnk5bduJmpgFKLQAAFEUz9NipvbA9AtKQeCHddWwk+8qTbK27f5gtkM/Snm48AA0sIlgH+BIXgAAAABJRU5ErkJggg=='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Foptions.2daiywsr71459.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Foptions.2daiywsr71459.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Foptions.2daiywsr71459.png&amp;w=640&amp;q=75"></div></div>
<p>Does your organization build UIs using multiple frameworks? It's a pain to share components between them! From my experience, you normally end up building the same thing in each respective framework. Double the work, but <strong>not</strong> double the fun. Maybe you end up trying to share CSS, since CSS is "easy" to share, but the problem remains: you're building the same thing for two or more distinct frameworks and writing JavaScript in different ways.</p>
<p>Instead, why not build the components once? That's what web components allow you to do. Web components work on all frameworks and are "framework agnostic". Say you have one app in Ember and another in React - they can both use the same exact web component. Take a <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span></code> component for example. The way you interact with this component may be different depending on the framework, but the markup to render the component is identical.</p>
<pre><code><span><span style="color:var(--sh-comment)">// Here's an example of a very simple and not</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// fully-featured button component written in Lit.</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">html</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">css</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">LitElement</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">lit</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">customElement</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">property</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">lit/decorators.js</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">customElement</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">cool-button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">class</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">CoolButton</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">extends</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">LitElement</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">static</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">css</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">    button {</span></span>
<span><span style="color:var(--sh-string)">      background-color: #4095bf;</span></span>
<span><span style="color:var(--sh-string)">      border-radius: 0.375rem;</span></span>
<span><span style="color:var(--sh-string)">      color: #ffffff;</span></span>
<span><span style="color:var(--sh-string)">      border: none;</span></span>
<span><span style="color:var(--sh-string)">      padding-top: 0.5rem;</span></span>
<span><span style="color:var(--sh-string)">      padding-bottom: 0.5rem;</span></span>
<span><span style="color:var(--sh-string)">      padding-left: 0.75rem;</span></span>
<span><span style="color:var(--sh-string)">      padding-right: 0.75rem;</span></span>
<span><span style="color:var(--sh-string)">    }</span></span>
<span><span style="color:var(--sh-string)">  </span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">property</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Boolean</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">reflect</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">true</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">disabled</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">false</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">property</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">submit</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">|</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">reset</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">render</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">html</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">      &lt;button ?disabled=</span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">disabled</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)"> type=</span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">type</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)">&gt;</span></span>
<span><span style="color:var(--sh-string)">        &lt;slot&gt;&lt;/slot&gt;</span></span>
<span><span style="color:var(--sh-string)">      &lt;/button&gt;</span></span>
<span><span style="color:var(--sh-string)">    </span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-comment)">// In Ember</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">some-path-where-cool-button-lives</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Component</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">@glimmer/component</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">on</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">@ember/modifier</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">action</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">@ember/object</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">default</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">class</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">SomeComponent</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">extends</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Component</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">action</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">handleClick</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">event</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">console</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">log</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">cicked</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">template</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">on</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">click</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">handleClick</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Button</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">template</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-comment)">// In React</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">some-path-where-cool-button-lives</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">useEffect</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">useRef</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">react</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">SomeComponent</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">buttonRef</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">useRef</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">useEffect</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">handleClick</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">event</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">console</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">log</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">clicked</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">buttonRef</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">current</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">addEventListener</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">click</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">handleClick</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">buttonRef</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">current</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">removeEventListener</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">click</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">handleClick</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">ref</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">buttonRef</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Button</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>You'll notice that in the React example, you're back to using <code><span><span style="color:var(--sh-identifier)">addEventListener</span></span></code>. You may be thinking "well this isn't very React like!" - that's true, but it <strong>is</strong> your favorite old friend, vanilla JavaScript. If you'd prefer more React-like syntax, <a href="https://lit.dev/docs/frameworks/react/" target="_blank" rel="noopener noreferrer">Lit offers a React package</a> that wraps your web components and offers a more friendly React-like syntax so you can ditch <code><span><span style="color:var(--sh-identifier)">ref</span></span></code>s and event listeners and get back to using props.</p>
<p>The big advantage here is that you write a web component once and can use it anywhere. You render it like any other component and wire up attributes and events. Now you don't have to make <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span></code> twice! Nice 👍.</p>
<a id="the-platform" href="#the-platform"><h2>The Platform</h2></a>
<div><div><img alt="An image of web browser tabs" loading="lazy" width="300" height="200" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 280'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAIAAAC6O5sJAAAAgUlEQVR42j2NwQ7CMAiG9/7P5c2rB+NBl9lpotZa2q4IhdlO3ZdwgA/4u/mPzGr5/SD8tl0tVr0R7hMcE0QmVW0CRQ4T7ODZw2sM/o7ZEtbVTlQzkwlw8m5IMKRgUsDCyyvm3trN1XhoxBhLKU2IyMW57XieFnLOP1HT6hERr9TJB0MEoXvkWSeuAAAAAElFTkSuQmCC'/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbrowser.14jpp-z5-wdu_.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbrowser.14jpp-z5-wdu_.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fbrowser.14jpp-z5-wdu_.png&amp;w=640&amp;q=75"></div></div>
<p>The best part of web components is they're part of <a href="https://github.com/WICG/webcomponents" target="_blank" rel="noopener noreferrer">"the platform™"</a>. Similar to how you can build a web app using only HTML, JavaScript, and CSS, you can create web components using the tooling you already know and love. There are <strong>no external dependencies</strong> if you're writing custom elements yourself. Using only HTML, JavaScript, and CSS feels like getting back to the basics - and I'm loving it.</p>
<p>If you're building a Design System in a repository separate from your main app repository, you will need a way to build your components and distribute them. We've been using <a href="https://esbuild.github.io" target="_blank" rel="noopener noreferrer">esbuild</a> and it seems great for this. Projects like <a href="https://github.com/shoelace-style/shoelace/blob/next/scripts/build.js#L8" target="_blank" rel="noopener noreferrer">Shoelace</a> do something similar. In an organization, I think it's almost guaranteed you'll be using some existing tooling for things like builds, tests, etc., but the idea that the underlying components are "just HTML, JavaScript, and CSS" is pretty cool.</p>
<p>By being baked into the platform, it means there are a lot of protections around web components. Browsers know how to render and what to do with them - they're treated as first-class citizens. We shouldn't expect huge, breaking changes in the short term - contrast that to when you go to bump versions for your favorite JS dependency. Take a look at <a href="https://www.spacejam.com/1996/index2.html" target="_blank" rel="noopener noreferrer">SpaceJam's 1996 website</a>, this baby is still alive and kickin'! Web components will carry the same support far into the future due to being backed by browsers via the platform. It's similar to CSS - there will be new features added as time progresses, but what's there today is set in stone and it's unlikely they'll break or stop working as the years pass.</p>
<blockquote>
<p>Whoa whoa whoa, Tony, you just mentioned above you're using Lit. Why do you keep harping on the platform when you're obviously using an abstraction on top of web components?</p>
</blockquote>
<p>Lit is a light abstraction on top of the native APIs that increases developer experience and make our jobs a bit easier when it comes to writing components. The decorators are quite helpful and it removes a lot of boilerplate you'd write yourself for each component. I'd recommend folks read <a href="https://lit.dev/docs/getting-started/" target="_blank" rel="noopener noreferrer">their docs</a> and see what I'm talking about. Every Lit component is a native web component - under the hood they're using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements" target="_blank" rel="noopener noreferrer">custom elements</a>, so the browser treats them like built-in elements. To me, the benefits of using Lit are worth it.</p>
<a id="encapsulation" href="#encapsulation"><h2>Encapsulation</h2></a>
<div><div><img alt="An image of a large shipping boat with many containers on top of it." loading="lazy" width="300" height="200" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 200'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAeUlEQVR42k3LsQ5EQBSFYa+3/Rb7FFtssQ8hCoVIxCNoRTQiEq1CoUaj486EwXANF41T/l+Otl9D3JC24iJGpRQV7YZBSpjGTgj5hGmWVhT/w+AX+F6SMuAnkDcAX916GebbcT+2k1c1xfMxI5YMMt5Woi96xteF4gHhe2rXAgW9CgAAAABJRU5ErkJggg=='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fencapsulation.2onhtzgv818e1.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fencapsulation.2onhtzgv818e1.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fencapsulation.2onhtzgv818e1.png&amp;w=640&amp;q=75"></div></div>
<p>One of my favorite parts of web components is encapsulation. You get protections automatically when using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM" target="_blank" rel="noopener noreferrer">Shadow DOM</a>. A big selling point of building Design Systems is ensuring consistency across your application(s). This can be really difficult when anyone can come in and target your elements to apply any styling they want. This leads to inconsistencies throughout your product and your customers will notice. Not with web components! With web components, there are only three ways to expose styling to consumers:</p>
<ol>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties" target="_blank" rel="noopener noreferrer">CSS variables</a> (aka custom properties)</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part" target="_blank" rel="noopener noreferrer">parts</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:host" target="_blank" rel="noopener noreferrer">:host</a> (I do not recommend going this route as it's not as clear in my opinion as options 1 and 2 above - I'll elaborate more.)</li>
</ol>
<a id="css-variables" href="#css-variables"><h3>CSS variables</h3></a>
<p>Using CSS variables for sharing styles across web components is really nice. After all, you probably don't want to repeat the same color across <code><span><span style="color:var(--sh-identifier)">n</span></span></code> number of components - it makes sense to leverage a variable instead. They can also be used for customization <em>within</em> web components. CSS variables pierce the Shadow DOM, meaning that a parent rendering a web component <strong>can</strong> override a style inside of a web component if it is exposed as a CSS variable.</p>
<p>You get to decide what consumers get to style by making it a public API, driven by CSS variables. Take a button for example, you may want to allow the consumer to support different color modes - maybe they should be able to adjust the <code><span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span></span></code> CSS property. When you define the <code><span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span></span></code> in your web component, you can expose this via a CSS variable to allow consumers to override the default.</p>
<pre><code><span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* Allows a consumer to adjust this! */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">root</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* Elsewhere you define the variable, maybe at a global level */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">#</span><span style="color:var(--sh-class)">4095bf</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">cool</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">override</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* ...or maybe at the class-level */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* where you'd do something like &lt;cool-button class="cool-button-override"&gt; */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">#</span><span style="color:var(--sh-class)">4095bf</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>If you want to protect a style from being overridden by consumers, keep the style hardcoded/local to the component itself. Maybe you defined the padding of buttons and decided that padding should not be modified at all by consumers. You want button padding to remain 100% consistent and not be adjusted. If that's the case, you can set those values directly in your web component. Since these values are not exposed via a CSS variable, it means consumers have no way of adjusting these values. Nice!</p>
<pre><code><span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">/* Consumers can't adjust these! */</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">padding</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">top</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">5rem</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">padding</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">bottom</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">5rem</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">padding</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">left</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">75rem</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">padding</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">right</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">75rem</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<a id="part" href="#part"><h3>Part</h3></a>
<p>The second option to allow customization to consumers is via <code><span><span style="color:var(--sh-identifier)">part</span></span></code>s in CSS. You expose areas of your UI that consumers can target with CSS, which allows them to customize it to their needs. If a <code><span><span style="color:var(--sh-identifier)">part</span></span></code> is not exposed, the consumer does not get access to style that piece of your UI. Say for example you have a "card" component that you want to expose a header, body, and footer to consumers to style as they please. You'd expose a <code><span><span style="color:var(--sh-identifier)">part</span></span></code> for each.</p>
<pre><code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-sign)">!</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">From</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">a</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)">cool-custom-element</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">web</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">component</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">class</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">card</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">class</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">header</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">part</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">header</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">name</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">header</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">class</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">body</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">part</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">body</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">class</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">footer</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">part</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">footer</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">name</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">footer</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">slot</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-identifier)">cool</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">custom</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">element</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">part</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">header</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">my</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">custom</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">header</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">bg</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-identifier)">cool</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">custom</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">element</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">part</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">body</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">padding</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">4rem</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-identifier)">cool</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">custom</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">element</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">part</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">footer</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">my</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">custom</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">footer</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">bg</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Parts are a great way to allow customization of certain sections of your UI 👍.</p>
<a id="host" href="#host"><h3>Host</h3></a>
<p>I mentioned above how I don't recommend styling <code><span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">host</span></span></code> in your web components. By targeting <code><span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">host</span></span></code>, it means you're exposing all of the styles under it to consumers. This results in the ability for them to override whatever CSS properties are defined there. This may not be clear to folks new to web components and may not be your intention. I personally feel as though sticking with CSS variables and exposing <code><span><span style="color:var(--sh-identifier)">part</span></span></code>s is superior and feels more intentional. I don't target <code><span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">host</span></span></code> when styling my components, but instead use tag selectors or class names. This is a personal preference though! If <code><span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">host</span></span></code> works for you, have at it!</p>
<pre><code><span><span style="color:var(--sh-comment)">/* ❌ I don't recommend this! */</span></span>
<span><span style="color:var(--sh-comment)">/*    Consumers can override via */</span></span>
<span><span style="color:var(--sh-comment)">/*    &lt;cool-button style="background-color: red", which */</span></span>
<span><span style="color:var(--sh-comment)">/*    may not be your intention */</span></span>
<span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-identifier)">host</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">#</span><span style="color:var(--sh-class)">4095bf</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">/* ✅ Instead, keep the background-color as a local CSS property */</span></span>
<span><span style="color:var(--sh-comment)">/*    that is not able to be overriden */</span></span>
<span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">#</span><span style="color:var(--sh-class)">4095bf</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">/* ✅ If you *want* to allow consumers to override background-color, */</span></span>
<span><span style="color:var(--sh-comment)">/*    target the button tag and use a CSS variable */</span></span>
<span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">/* ✅ Or use a class name &lt;button class="button" */</span></span>
<span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">background</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Being able to decide where you draw the line when it comes to exposing style overrides in your components is a huge benefit when building a Design System. With web components, you're in full control on what you expose and consumers have no way of working around the lines you draw. This is extremely powerful! Use CSS variables or expose <code><span><span style="color:var(--sh-identifier)">part</span></span></code>s - otherwise, everything is private!</p>
<a id="wish-list" href="#wish-list"><h2>Wish List</h2></a>
<div><div><img alt="An image of a girl making some wishes" loading="lazy" width="200" height="150" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 280'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAIAAAC6O5sJAAAAj0lEQVR42j2NzQ7CMAyD+/5PBCckXgB2GyAYCKRtrfanNVuTNilhByxbluzDZyTnwEwpigizpE05Z0PCFoMF38Pc2AYI4/9ocVXXi6+qZwN+iJREjKYcu47QBbjs9sXt/l48ZzHEXNi6wwAEU3k4fx6nwSnYaNwCAyGEdbav6+iOffs7lKPFIpjSjDhtAB2/1vKf3uqRFUUAAAAASUVORK5CYII='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fwishes.3n0nluetucr4i.png&amp;w=256&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fwishes.3n0nluetucr4i.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fwishes.3n0nluetucr4i.png&amp;w=640&amp;q=75"></div></div>
<p>Like most things in the world, web components aren't perfect. Here are some things I wish web components supported.</p>
<a id="cross-shadow-root-aria" href="#cross-shadow-root-aria"><h3>Cross Shadow Root ARIA</h3></a>
<p>Essentially allowing for connecting ID references across shadow roots. This is important for accessibility reasons, like associating labels to inputs, tooltips to other elements, etc. - this is huge for accessibility.</p>
<p>This issue is described in the <a href="https://eisenbergeffect.medium.com/web-components-2024-winter-update-445f27e7613a" target="_blank" rel="noopener noreferrer">Web Components 2024 Winter Update</a> which is also worth a read. I'll be keeping my eye on <a href="https://github.com/WICG/aom/pull/207" target="_blank" rel="noopener noreferrer">this GitHub issue</a>.</p>
<a id="spread" href="#spread"><h3>Spread</h3></a>
<p>I was very spoiled using React and being able to spread not only properties, but attributes as well on components. This is extremely powerful and is nice for developer experience reasons. It obviously isn't perfect either though, as anything you pass in gets spread onto the underlying element; however, I think the risks are worth it. This makes life so much easier!</p>
<pre><code><span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">CoolComponent</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">label</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">rest</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">rest</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">label</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">button</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">AnotherComponent</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">CoolComponent</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">id</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">some-id</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">label</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">Button</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">/&gt;</span><span style="color:var(--sh-jsxliterals)">;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">}</span><span style="color:var(--sh-jsxliterals)">;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span></span></code></pre>
<p>Being able to spread attributes onto a web component via similar syntax would be really nice. Instead, you have to manually map these things over, so there's a lot of repetition.</p>
<pre><code><span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">stuff</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">id</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">some-id</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">label</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">Button</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// What I want!</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">$</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">stuff</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span>
<span><span style="color:var(--sh-comment)">// What we have to do today - manually map things over</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">id</span><span style="color:var(--sh-string)">=$</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">stuff</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">id</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-property)">label</span><span style="color:var(--sh-string)">=$</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">stuff</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">label</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-property)">cool-</span><span style="color:var(--sh-identifier)">button</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span></code></pre>
<a id="are-web-components-right-for-you?" href="#are-web-components-right-for-you?"><h2>Are Web Components Right For You?</h2></a>
<div><div><img alt="An image of a man contemplating their choices" loading="lazy" width="200" height="150" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 320'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAgklEQVR42m1OSQ7CMAzM/7/CB0C9cEeCA0hcAbVCrI2CQ+yEmE7V5VB1JMtjz3gx/wHxB+SxNF1S1dLa2jmQXgCrOQQWsU7Ix5QkJTRNVr3A+qH77VFV128IjqgVEPBjoARO5yiRmVuh2yg5L5bFdrObHgeK1fqwP8589Xq+PflRaACqoLjpiAMWNQAAAABJRU5ErkJggg=='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconclusion.2vl5am1gd19m6.png&amp;w=256&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconclusion.2vl5am1gd19m6.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconclusion.2vl5am1gd19m6.png&amp;w=640&amp;q=75"></div></div>
<p>It probably depends! I tried to outline a lot of the advantages I've found when using web components, but for me it still boils down to the question of: "are you building for multiple frameworks?". If the answer is "yes", I think it'd make a lot of sense to give web components and something like Lit a go. If the answer is "no", I'd probably just build in whatever framework you're already using. If the time comes when a cool new framework comes along and you need to support both, then maybe re-evaluate. Web components will only get better from here though! It's an exciting time to be a developer and web components seem to be on the rise - take a look at <a href="https://www.reddit.com" target="_blank" rel="noopener noreferrer">Reddit</a>. They're now using Lit! Happy coding.</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Design Systems: Open versus Closed Source]]></title>
            <link>https://tonyward.dev/articles/open-vs-closed-source-design-system</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/open-vs-closed-source-design-system</guid>
            <pubDate>Fri, 26 May 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Hello! I hope you're enjoying my Design Systems series thus far, we've already talked about <a href="https://www.tonyward.dev/articles/starting-a-design-system" target="_blank" rel="noopener noreferrer">starting one</a> and when it's good to <a href="https://www.tonyward.dev/articles/build-vs-buy-design-system" target="_blank" rel="noopener noreferrer">build versus buy</a>. In this article, I wanted to talk about why you should open source your Design System. <strong><em>In my opinion, open sourcing is a no brainer for many reasons, as long as your organization will allow it.</em></strong></p>
<a id="what-is-open-source?" href="#what-is-open-source?"><h2>What is Open Source?</h2></a>
<p>You may be wondering, "what do you mean by open source?". A huge majority of software out in the wild today uses at least one open source library. There are varying statistics on the internet (search "open source usage stats") about what percentage of modern software is built on open source, but it's safe to say that modern software runs on open source.</p>
<p>To put it plainly, open sourcing something means it is free for anyone to use as long as they follow the provided license. The license is normally written so that users are free to update, change, and distribute the software to their needs, free of charge; however, each license is different. Most open source software follows <a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository#searching-github-by-license-type" target="_blank" rel="noopener noreferrer">a common list</a>, with MIT being the most open and free to use. When looking at open source libraries, be sure to check out their license.</p>
<p>How about "closed source"? What I mean here is the exact opposite of open source. It is entirely private code that is not shared publicly and kept internal to the organization. It makes a lot of sense to keep an organization's business logic / "secret sauce" in this realm; however, when it comes to things like Design Systems and other presentational building blocks, it makes a ton of sense to go open source (in my opinion!).</p>
<p>GitHub has a <a href="https://github.com/open-source" target="_blank" rel="noopener noreferrer">great page</a> to get folks started in open source. It has a lot of great resources for folks new to the concept. I also recommend checking out the <a href="https://www.redhat.com/en/resources/state-of-enterprise-open-source-report-2022" target="_blank" rel="noopener noreferrer">report from RedHat</a> on open source and why enterprises prioritize those who work in open source.</p>
<a id="atomic-design-patterns" href="#atomic-design-patterns"><h2>Atomic Design Patterns</h2></a>
<p>One of the reasons I love open sourcing a Design System is it forces you to follow atomic design patterns. <strong><em>When your Design System is open source, it forces you to write components that are not tied to organization-specific data or data-fetching mechanisms</em></strong>. Instead, you focus on providing the presentational pieces or building blocks for folks to build UI. By following atomic design patterns, <strong><em>your components will have less bugs, be more reusable, have better tests, and have a singular, primary focus</em></strong>.</p>
<p>As a very basic example, let's say we need to build a form that looks like below. How would you break this down into atomic, presentational components? (Go ahead and interact with the form! It also supports both light and dark modes.)</p>
<div><div><div><form><div><label for="name">Name</label><input id="name" type="text" placeholder="" name="name"></div><div><label for="description">Description</label><textarea id="description" name="description"></textarea></div><div><button type="submit">Submit</button></div></form></div></div></div>
<p>Although overly simplifying my point, this is normally how I go about building designs. My mind immediately begins breaking this form into individual building blocks. I see a wrapping form as the parent. Inside of the form is our input, textarea, and button. These are all separate, individual components - they don't need to be tied into a <em>single</em> "Form" component.</p>
<div><div><img alt="A presentational, atomic level breakdown of a form component. It shows a form tag with the following children: an input tag, a textarea tag, and a button tag." loading="lazy" width="350" height="250" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 320'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAICAYAAADaxo44AAAAaElEQVR42mWOyQ1EMQhD00P2PUoipf8KPbIPc/kHg+GBwJRS0HuH9x7OOclaC5NSQoxRCiFI9IYbhMxzTvlaKwwDm2stnHPQWpMEcs5qvve+YO+Ne6+2BWh4jJ+NMf73DKdZMFMcIvgBGkU+s6OO+0EAAAAASUVORK5CYII='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fform.0i0mtiy2f6zuc.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fform.0i0mtiy2f6zuc.png&amp;w=750&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fform.0i0mtiy2f6zuc.png&amp;w=750&amp;q=75"></div></div>
<p>By open sourcing your Design System, it forces you to think at the atomic-level. <strong><em>Creating items as separate components allows you to use them elsewhere in your application, which leads to greater UI/UX consistency!</em></strong></p>
<a id="public-eye" href="#public-eye"><h2>Public Eye</h2></a>
<p>By open sourcing your Design System, anyone who has internet access can now view and interact with what you've built. I've had multiple instances when interviewing candidates where they'd mention "I looked over your Design System on GitHub". This is, at a minimum, a conversation starter when interviewing candidates. From their perspective, they also get a sense of how you write code, the pull request review process at your organization, see how tests are written and coverage, and many other benefits that give a look into how your organization operates on a day-to-day basis. I can't state enough how much open sourcing your code helps with recruiting, both for the interviewer and interviewee.</p>
<div><div><img alt="An image of Tony talking at an Ember Conference in 2018." loading="lazy" width="400" height="300" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 240'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/wAARCAAGAAgDAREAAhEBAxEB/9sAQwAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47/9sAQwEKCwsODQ4cEBAcOygiKDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDhJFgj0aPyvMMs1uUnLsSCS2QR+Aqqc1KbU1otipqyXL1P/9k='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Femberconf.0xnsts0_9ye-p.jpeg&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Femberconf.0xnsts0_9ye-p.jpeg&amp;w=828&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Femberconf.0xnsts0_9ye-p.jpeg&amp;w=828&amp;q=75"></div></div>
<p>In 2018, I <a href="https://www.youtube.com/watch?v=4fI72aZl_N8" target="_blank" rel="noopener noreferrer">presented at EmberConf</a> on Toyota's shared component library, Loom. After giving the talk, I had quite a few people ask me if it was open source and if they could see what we had built. Unfortunately at the time, it was not. This made me realize how powerful open sourcing is when talking at conferences. <strong><em>It's great to talk publicly about your work; however, developers really want to see the code and what problems you're solving</em></strong>. By open sourcing your Design System, you'll have more confidence after presenting as you can show folks exactly what you're doing by providing a link to the repository. When I signed on at LifeOmic, I ensured that we open sourced our Design System, <a href="https://github.com/lifeomic/chroma-react" target="_blank" rel="noopener noreferrer">Chroma</a>, as early as possible for this exact reason.</p>
<a id="github-open-source-benefits" href="#github-open-source-benefits"><h2>GitHub Open Source Benefits</h2></a>
<p>One of my favorite things about open sourcing software is all of the great tooling provided by GitHub. For most things, it's all free as well! I've highlighted some of my favorite benefits below.</p>
<a id="github-actions-ci-cd" href="#github-actions-ci-cd"><h3>GitHub Actions (CI/CD)</h3></a>
<div><div><img alt="An image of a GitHub Actions Workflow." loading="lazy" width="1778" height="734" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 120'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAYAAACuyE5IAAAAZElEQVR42h2J2wpEUAAAT227W/u4tT+zEQ84eHFJSrkTnUjK/zcOTzPNiMf7y/Pzw5YJ1bhRdAuWF/PS7XoiyUqaXlGUA5PaUeuhfaRqZ7K8RvxND8ePMe0AV1OG6e2OjDAsyQkkcCv7HrUe/QAAAABJRU5ErkJggg=='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Factionsworkflow.2iiizi314s0yc.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Factionsworkflow.2iiizi314s0yc.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Factionsworkflow.2iiizi314s0yc.png&amp;w=3840&amp;q=75"></div></div>
<p>GitHub has <a href="https://github.com/features/actions" target="_blank" rel="noopener noreferrer">Actions</a> which cover all of your CI/CD needs. The best part is, for open source software, it's all free! As the docs say, "Build, test, and deploy your code right from GitHub". Depending on your organization, moving CI/CD from whatever you're using internally to GitHub means more cost savings for your organization as it's less time and resources spent running them on your machines.</p>
<a id="publishing-a-documentation-site" href="#publishing-a-documentation-site"><h3>Publishing a Documentation Site</h3></a>
<p>I highly recommend building a documentation site or using something like <a href="https://storybook.js.org" target="_blank" rel="noopener noreferrer">storybook</a> to document your Design System. With the help of a few external providers, you can also publish your docs site <em>for free</em> via GitHub as well. You could use <a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">GitHub Pages</a>, but some of my favorite external providers to use are <a href="http://vercel.com/" target="_blank" rel="noopener noreferrer">Vercel</a>, <a href="https://pages.cloudflare.com" target="_blank" rel="noopener noreferrer">Cloudflare Pages</a>, or <a href="https://www.netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a>. Deploying to any of the mentioned providers above are all done via GitHub Actions.</p>
<p>Once again, your organization is saving money by not having to host and deploy the documentation site.</p>
<a id="test-matrices" href="#test-matrices"><h3>Test Matrices</h3></a>
<div><div><img alt="An image of a GitHub test matrix, testing against many versions of TypeScript and Ember." loading="lazy" width="1514" height="806" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 160'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAYAAACzzX7wAAAAdUlEQVR42hXD2w6CIACAYe5aloHiAUxRM0JWWXNra73/e/3Ni08cZcEmSXN2iaQwLY2bkLlhf8gQSht8XLDdBaUtvZ9Z1i9TeKKrM0Lqmtv9xTBFKutwPvD+/JgfK3nZIJI0w7QjddNzUhWF7RiuETcGUlXyB4bgKiWVuAC2AAAAAElFTkSuQmCC'/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftestmatrix.1rkuzo9mo1r6_.png&amp;w=1920&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftestmatrix.1rkuzo9mo1r6_.png&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Ftestmatrix.1rkuzo9mo1r6_.png&amp;w=3840&amp;q=75"></div></div>
<p>One item I learned recently from a friend, <a href="https://linktr.ee/nullvoxpopuli" target="_blank" rel="noopener noreferrer">NullVoxPopuli</a>, is setting up test matrices. By using test matrices you can now test against many different versions of dependencies in parallel. At CrowdStrike, we test against many <a href="https://github.com/CrowdStrike/ember-toucan-core/blob/main/.github/workflows/ci.yml#L58-L59" target="_blank" rel="noopener noreferrer">different versions of TypeScript</a>, as well as many <a href="https://github.com/CrowdStrike/ember-toucan-core/blob/main/.github/workflows/ci.yml#L115-L120" target="_blank" rel="noopener noreferrer">different versions of Ember</a> in our test steps. This essentially allows us to ensure we are backwards <em>and</em> future compatible.</p>
<a id="visual-regression-testing" href="#visual-regression-testing"><h3>Visual Regression Testing</h3></a>
<div><div><img alt="An image of a man comparing two images" loading="lazy" width="200" height="150" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 280'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAYAAAA1WQxeAAAAtUlEQVR42m2OwQoBYRSFL7IfI0apsZAoRUmThVnbDCNNjNewIDZK8wIspryEV7CQ+Us8As0KGcLWv3FvTFn46tS539lcACSAhJDwF+rkaANV14yKrrXVlm5migUljSk366aCrtrQDPCej9fmeuaH+43b9nxlTWfLxcnla+/IL7gBY2zrOA5lNxpO3EF/vKdOjjaIfBAp+VzJluWs5d8U+CUhpXqCEO/AP+hzKZbsRkWpFkR8/wbKVUEBZEJY6gAAAABJRU5ErkJggg=='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fviztesting.0c2sgh4rbvbd8.png&amp;w=256&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fviztesting.0c2sgh4rbvbd8.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fviztesting.0c2sgh4rbvbd8.png&amp;w=640&amp;q=75"></div></div>
<p>From a high level, the way visual regression testing works is it detects visual changes when someone creates a pull request. As an example, say someone changes the background color of a primary button from blue to purple. This would be flagged as a visual change. It creates a snapshot of what's currently "in production" versus what the incoming changes are and displays an image comparing the two side-by-side. Someone on your team must approve the new, incoming visual changes before the pull request can be merged. If you don't approve the changes, the pull request is blocked. Once the pull request is merged, the new image is set as the baseline going forward.</p>
<p>If you're working in a Design System, you'll be paying close attention to how things are represented visually. Instead of doing this work manually, why not let computers do it for you? If your Design System is open sourced, you can take advantage of visual regression testing tools like <a href="https://percy.io" target="_blank" rel="noopener noreferrer">Percy</a> or <a href="https://www.chromatic.com" target="_blank" rel="noopener noreferrer">Chromatic</a>. They have free plans if your project is open sourced on GitHub. Yet another cost savings item for the organization with a <em>huge</em> benefit for your team!</p>
<a id="open-source-if-possible" href="#open-source-if-possible"><h2>Open Source if Possible</h2></a>
<p>Overall, I highly recommend open sourcing your Design System. The benefits of open sourcing are huge! It helps you write better components by following atomic design patterns, it opens the door for recruiting and conference talks by allowing you to show what you're working on in public, it allows you to use excellent, modern tooling from the open source community, and it'll save your organization a lot of money on CI/CD resources. Thanks for sticking with me on this article. I hoped you enjoyed it! Hopefully catch you in my next Design Systems article!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Design Systems: Build versus Buy]]></title>
            <link>https://tonyward.dev/articles/build-vs-buy-design-system</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/build-vs-buy-design-system</guid>
            <pubDate>Mon, 15 May 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>In my <a href="https://www.tonyward.dev/articles/starting-a-design-system" target="_blank" rel="noopener noreferrer">previous article</a>, I discussed getting started with a Design System from an internal perspective. Now let's get into the fun part - actually building your Design System! One of the questions folks in your organization may ask is:</p>
<blockquote>
<p>Should we build our own Design System from scratch? There are tons of existing, open sourced Design Systems out there - can we use one of those instead?</p>
</blockquote>
<p>So when should you "build" versus "buy" when it comes to Design Systems? I'll share my thoughts.</p>
<a id="buy" href="#buy"><h2>Buy</h2></a>
<p>There are a ton of really great existing component libraries out there that you can use as a base for building your Design System. My favorite at the moment is <a href="https://chakra-ui.com" target="_blank" rel="noopener noreferrer">chakra-ui</a>. Chakra's landing page sells it really well <em>"Chakra UI is a simple, modular and accessible component library that gives you the building blocks you need to build your React applications."</em>. For designers, there's also <a href="https://www.figma.com/community/file/971408767069651759/Chakra-UI-Figma-Kit" target="_blank" rel="noopener noreferrer">a Figma Kit available</a>.</p>
<p><strong><em>By choosing the "buy" path, what we really mean here is using something from the open source community</em></strong>. Something like Chakra comes with a <em>ton</em> of existing components that you won't have to write yourself. Go browse their site for a bit and see what I mean! Ultimately, it could save you a ton of time and really jump start your progress on a Design System.</p>
<p>What are things I personally look for in component libraries?</p>
<ul>
<li>Do they allow theming so you can style the components to match your design language?</li>
<li>Do they have a focus on web accessibility?</li>
<li>How frequently do they do new releases?</li>
<li>How many open issues on GitHub do they have?</li>
<li>Are the contributors still active?</li>
<li>Do they accept outside contributions?</li>
</ul>
<p>From a high level, building your Design System from an existing component library typically looks like:</p>
<ul>
<li>Choose an existing component library</li>
<li>Determine your design language (color palettes, typography, etc.)</li>
<li>Theme the component library to match your design language</li>
<li>Remove any unwanted functionality or styles (e.g., say they provide a loading state for a button, but you don't want that)</li>
<li>Start integrating it into your product(s)</li>
</ul>
<a id="increased-velocity" href="#increased-velocity"><h3>Increased Velocity</h3></a>
<p>Do you have a really small team? If so, choosing an existing component library to base your Design System off of will save you <em>a ton</em> of time. Your velocity will be way faster by not reinventing the wheel. <strong><em>By instead using something off the shelf, you can spend more time theming it to your needs and shipping value to your customers</em></strong>.</p>
<a id="community-driven-updates" href="#community-driven-updates"><h3>Community Driven Updates</h3></a>
<p>By using an existing component library that is open source, you get feature updates and bug fixes from the open source community. This has huge benefits, because it means <strong><em>you can spend less time fixing bugs with the underlying components</em></strong>. Sometimes all it takes is filing an issue on GitHub and a developer will resolve it. If you're a developer yourself, you can also contribute directly to the project by opening a pull request (please do this if you can!).</p>
<p>If you're an organization using an open source library for your Design System, I highly recommend considering how your organization can support the contributors directly. <strong><em>I highly recommend buying their premium version (<a href="https://pro.chakra-ui.com/?utm_source=chakra-ui.com&amp;amp;utm_medium=ad-banner" target="_blank" rel="noopener noreferrer">Chakra Pro, for example</a>), sponsoring them via <a href="https://github.com/sponsors" target="_blank" rel="noopener noreferrer">GitHub</a>, sponsoring via <a href="https://opencollective.com" target="_blank" rel="noopener noreferrer">open collective</a>, or any other means they mention in their README or main site</em></strong>. Throwing a few hundred or thousand dollars their way is a drop in the bucket for most organizations, but could help the individual contributors a ton! After all, if your business is reliant on their work, the organization should want the project to be regularly maintained and successful.</p>
<a id="overrides" href="#overrides"><h3>Overrides</h3></a>
<p>Overriding component styles and props you don't want to support can be tedious if your designs differ quite a bit from what it offers out of the box. An example could be your Design System having highly customized CSS for a component. Also removing unwanted functionality can take a bit of time. For example, as mentioned above, say there is a button component that has a loading state and prop; however, your Design System does not want to expose that. You'll need to remove the prop from the TypeScript interface as well as removing the underlying functionality. If someone is used to using the component library out of the box, they may be confused as to those props or styles aren't available.</p>
<p>Another downside with overrides is potentially shipping unused CSS. This is more of a problem with the very heavy-handed component libraries, but is worth mentioning. Some libraries provide existing, base styles which most folks end up overriding to fit their needs. The problem though, is the base styling is still shipped to the browser. The result is more CSS downloaded that provides no real value. This issue isn't in <em>every</em> component library, but is worth checking out if you'll be affected. This is arguably a nit-pick on my part, but something I consider when reviewing existing libraries.</p>
<a id="breaking-changes-migrations" href="#breaking-changes-migrations"><h3>Breaking Changes / Migrations</h3></a>
<p>Most software projects introduce breaking changes. These are inevitable! It's great getting new features and bug fixes, but there comes a time when things need to change and require a breaking update. Most projects follow <a href="https://semver.org/spec/v2.0.0.html" target="_blank" rel="noopener noreferrer">semantic-versioning</a>, but some do not, which can lead to unexpected breaking changes in minor and patch releases. This ultimately means you may spend a lot of time on upgrades; however, it's all worthwhile in my opinion due to the tradeoff of the initial jump start.</p>
<a id="verdict-of-buy" href="#verdict-of-buy"><h2>Verdict of Buy</h2></a>
<ul>
<li>Pros<!-- -->
<ul>
<li>Higher initial velocity</li>
<li>Community driven updates</li>
<li>Figma kits may be available for designers</li>
</ul>
</li>
<li>Cons<!-- -->
<ul>
<li>Overrides can be tough</li>
<li>Some libraries don't follow semantic versioning</li>
<li>Library UI/UX may influence design direction</li>
</ul>
</li>
</ul>
<a id="build" href="#build"><h2>Build</h2></a>
<p>Building your Design System from scratch can take a lot of time. <strong><em>If you're not under strict time constraints and want full control over the underlying output (HTML/CSS/JS), the "build" path is great</em></strong>. You'll have full control of the components and their APIs. By building these components from scratch, you'll also gain a better understanding of how semantic HTML works.</p>
<p>From a high level, building your Design System from scratch typically looks like:</p>
<ul>
<li>Determine your design language (color palettes, typography, etc.)</li>
<li>Build components in Figma</li>
<li>Build components in code</li>
<li>Start integrating it into your product(s)</li>
</ul>
<a id="semantic-html-headless-solutions-for-a-hybrid-approach" href="#semantic-html-headless-solutions-for-a-hybrid-approach"><h3>Semantic HTML + Headless Solutions for a Hybrid Approach</h3></a>
<p>For many of the components in a Design System, you can use semantic markup. Take a Button component for example - the <code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">button</span><span style="color:var(--sh-sign)">&gt;</span></span></code> HTML <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button" target="_blank" rel="noopener noreferrer">tag</a> is great! <strong><em>You should use semantic HTML as much as possible</em></strong> - it's <a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML" target="_blank" rel="noopener noreferrer">helps with accessibility</a>! Potentially a spicy take (🌶️): but a lot of the building block components are not difficult to build using semantic HTML. Buttons, text, avatars, inputs, textareas, checkboxes, etc. are all backed by HTML tags that have been around for a really long time and are accessible to everyone.</p>
<pre><code><span><span style="color:var(--sh-keyword)">const</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Button</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">children</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">rootProps</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">button</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-property)">type</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">button</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-property)">className</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">rootProps</span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">      </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">children</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-jsxliterals)"></span></span>
<span><span style="color:var(--sh-jsxliterals)"></span><span style="color:var(--sh-jsxliterals)">    </span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">button</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>How about the more complex components though? The <em>really</em> difficult ones? Tooltips, custom selects, popovers, etc.? For these more complicated components, <strong><em>I highly recommend using existing packages that solve a lot of these problems</em></strong>. They're incredibly difficult to get right from an accessibility perspective and by reaching for a community driven solution, you'll save yourself a bunch of time and headaches. Some of my favorite libraries solving these problems are:</p>
<ul>
<li><a href="https://headlessui.com" target="_blank" rel="noopener noreferrer">headlessui</a></li>
<li><a href="https://www.radix-ui.com/docs/primitives/overview/introduction" target="_blank" rel="noopener noreferrer">radix-ui</a></li>
<li><a href="https://www.downshift-js.com" target="_blank" rel="noopener noreferrer">downshift</a> (for selects, multiselects, typeaheads)</li>
<li><a href="https://reach.tech" target="_blank" rel="noopener noreferrer">reach-ui</a></li>
</ul>
<p>The future is near though! Browsers are starting to ship really helpful elements to assist here. Some of these I actually just ran across this weekend! A few examples:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog" target="_blank" rel="noopener noreferrer"><code><span><span style="color:var(--sh-identifier)">dialog</span></span></code></a>
<ul>
<li>Modals/Dialogs built-in to the browser! No longer a need to do an overlay and position everything yourself!</li>
<li>Supported in all major browsers</li>
</ul>
</li>
<li><a href="https://open-ui.org/components/popover.research.explainer/" target="_blank" rel="noopener noreferrer"><code><span><span style="color:var(--sh-identifier)">popover</span></span></code></a>
<ul>
<li>That's right! Popovers straight from the browser rather than using popperjs/floating-ui!</li>
<li><a href="https://youtu.be/buChHSdsF9A?t=762" target="_blank" rel="noopener noreferrer">Check out this great video</a> from Una Kravets sharing some of the details</li>
</ul>
</li>
<li><a href="https://open-ui.org/components/selectmenu/" target="_blank" rel="noopener noreferrer"><code><span><span style="color:var(--sh-identifier)">selectmenu</span></span></code></a>
<ul>
<li>Wow! Even custom select components are coming soon™! So exciting!</li>
<li>Another <a href="https://youtu.be/buChHSdsF9A?t=885" target="_blank" rel="noopener noreferrer">timestamped video</a> from Una Kravets showing the power of <code><span><span style="color:var(--sh-identifier)">selectmenu</span></span></code></li>
</ul>
</li>
</ul>
<a id="verdict-of-build" href="#verdict-of-build"><h2>Verdict of Build</h2></a>
<ul>
<li>Pros<!-- -->
<ul>
<li>Full control over HTML/CSS/JS output</li>
<li>Gain a lot of experience building things from scratch</li>
<li>More design freedom / not feeling limited to an existing system</li>
</ul>
</li>
<li>Cons<!-- -->
<ul>
<li>Lower initial velocity</li>
<li>More maintenance unless you have a dedicated team</li>
<li>No community driven updates (unless you're lucky!)</li>
</ul>
</li>
</ul>
<a id="final-thoughts" href="#final-thoughts"><h2>Final Thoughts</h2></a>
<p>So, which one is right for you? It depends! Both have advantages and disadvantages. If I were on a small team that had a lot of experience building Design Systems, I'd probably do a hybrid approach as mentioned above. If I were on a less experienced team or the sole developer, I'd probably reach for building on top of an existing component library. From experience, it doesn't <em>really</em> matter which one you pick - it's just code! You can always pivot in one direction or the other. What's more important is the team you create to build the Design System (and the friends you make along the way ❤️)!</p>
<hr>
<p><strong>Bonus Section: Design System Dream for Development</strong></p>
<p>From a development perspective, here is an idea I've had for a while now:</p>
<ul>
<li>Use TailwindCSS</li>
<li>Use something along the lines of <a href="https://github.com/marcomontalbano/figma-export" target="_blank" rel="noopener noreferrer">@figma-export/cli</a> to export design tokens such as colors and typography from Figma and into a TailwindCSS config. This means you'll have Figma and code in sync. You could also use this for exporting icons if you wanted.</li>
<li>Setup a job that will run this command for you automatically and do a diff to determine if there are new changes. If so, create a PR and automatically add reviewers for approval. (NOTE: Automating this sounds super dope)</li>
<li>Build all of the semantic elements from scratch (button, input, textarea, checkbox, toggle, radio groups, etc.)</li>
<li>Use something like headlessui for the more complex scenarios (popovers, selects, modals, etc.)</li>
<li>Build and bundle the package with vite/esbuild</li>
<li>Write lots of integration tests!</li>
<li>Add visual regression testing that's built into the PR review process</li>
<li>Open source on GitHub with <a href="https://github.com/changesets/changesets" target="_blank" rel="noopener noreferrer">changesets</a> for helping with releases</li>
<li>Write a documentation site using Next.js + mdx + your Design System to dog food it internally. Deploy on <a href="https://vercel.com" target="_blank" rel="noopener noreferrer">Vercel</a>.</li>
</ul>
<p>I'm hoping I can do this one day! I should write more as to <em>why</em> I think this is a good idea. Maybe an upcoming post! If you end up doing this, let me know how it goes!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Starting a Design System]]></title>
            <link>https://tonyward.dev/articles/starting-a-design-system</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/starting-a-design-system</guid>
            <pubDate>Wed, 10 May 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>I've been building Design Systems now for a little over 6 years! I've built them at varying scales, from small startups to large corporations. I won't go into what a Design System <em>is</em>, or too deep on how it can benefit your organization - there are a ton of really great articles and <a href="https://www.youtube.com/watch?v=YLo6g58vUm0" target="_blank" rel="noopener noreferrer">videos</a> online about that already. Instead, I'd like to dive into how do you get buy-in to build one within your organization and what are some items you should be thinking about early on in your Design System process. After this article, I plan on writing more about specifics, but I wanted this to be a high-level overview of how to kick things off internally. <strong>NOTE:</strong> This is from my personal experience as a frontend developer working on Design Systems, your mileage may vary -- please don't take this as gospel!</p>
<a id="get-management-on-board" href="#get-management-on-board"><h2>Get Management on Board</h2></a>
<p>Pitching a Design System <em>can</em> be very difficult. The main pitch I've given in favor of Design Systems is UI/UX consistency across product(s), providing web accessible components so developers don't <em>need</em> to know the ins-and-outs of web accessibility, and speeding up designers and developers when it comes to building new features and products by providing building blocks. To me, these pieces are the primary drivers of Design Systems within organizations. Upper management normally cares about two things when it comes to UI/UX departments:</p>
<ol>
<li>Are customers/shareholders happy about the current UI/UX experience?</li>
<li>How can we ship more value to our customers faster?</li>
</ol>
<p>Luckily, creating a Design System will help both of these cases.</p>
<p><strong><em>Building a Design System is a long-term investment, where it starts out slow, but over time it begins paying dividends.</em></strong> The benefits will not appear right away - it takes some time to get going, especially if you are creating a Design System from an existing product. This is all completely normal! Don't sweat it!</p>
<p>If you find you're having issues pitching a Design System and the benefits, I recommend sharing the <a href="https://www.figma.com/blog/measuring-the-value-of-design-systems/" target="_blank" rel="noopener noreferrer">"Measuring the value of Design Systems"</a> study. Here's the <strong>TL;DR</strong>:</p>
<blockquote>
<p>We found that when participants had access to a Design System they completed their objective 34% faster than without a Design System.</p>
</blockquote>
<blockquote>
<p>…if every task they’re working on has a relevant Design System, they are able to do 34% more design work in that 140 hours, giving them 212 design hours. That’s equivalent to adding another 3.5 designers to the team each week!</p>
</blockquote>
<blockquote>
<p>Several participants commented that they felt more confident in their final design when they had access to the Design System because they knew it was consistent with the product.</p>
</blockquote>
<p>Pretty crazy, right? This study is only from the design side as well! Unfortunately I'm not finding any similar studies from an engineering perspective, but I can tell you from experience that building a feature with existing building blocks versus having to build them all myself has saved me a <em>ton</em> of time! Finding statistics like above will hopefully help your pitch on <em>why</em> a Design System is important and what it can do for your organization. Once you have management on board, you may not want to do this all alone! If you're an engineer, you'll want a design counterpart and vice versa.</p>
<a id="dedicated-focus-versus-side-project" href="#dedicated-focus-versus-side-project"><h2>Dedicated Focus versus Side Project</h2></a>
<p>Depending on your situation, you may be hiring to build a new Design System team, pulling folks from the existing organization, or working on the Design System as a side project because you think it's the right thing to do. Any of these options are completely valid! Every organization is different. From experience, typically <strong><em>having dedicated folks work on the Design System is the best for long term support and growth</em></strong>. Sometimes, the project may start as something on the side, but as you begin shipping value to customers (in this case, customers can be end users <em>and</em> internal folks consuming your Design System), people will start to notice and it may be easier to get additional headcount and/or transition to working on it full-time.</p>
<p>Even if your request to build a Design System formally gets denied, you can still build one if your engineering and design teams are aligned. <strong><em>Following <a href="https://atomicdesign.bradfrost.com/chapter-2/" target="_blank" rel="noopener noreferrer">atomic design principles</a> applies to both design and development. One of the biggest mistakes engineering teams make is not following these patterns</em></strong>. Breaking components down into atomic pieces makes them easier to test, easier to reuse throughout applications and packages, and helps you write better components by ensuring one component is not handling too much business logic that leads to brittle code. There is an argument about premature abstractions and abstractions in general, but I'm mostly talking about building foundational, presentational components as building blocks. <strong><em>By following atomic design patterns with components, your frontend code will be much easier to maintain and reason about</em></strong>.</p>
<a id="next-up" href="#next-up"><h2>Next Up</h2></a>
<p>In summary, I hope this was a semi-helpful read. I'd like to continue writing about building Design Systems and have a few ideas in mind for next time:</p>
<ul>
<li><a href="https://www.tonyward.dev/articles/build-vs-buy-design-system" target="_blank" rel="noopener noreferrer">"Build versus Buy"</a></li>
<li><a href="https://www.tonyward.dev/articles/open-vs-closed-source-design-system" target="_blank" rel="noopener noreferrer">Open versus Closed Source</a></li>
<li>Choosing a color palette</li>
<li>Choosing a typography scale</li>
<li>Auditing your existing applications and extracting a Design System from them</li>
</ul>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Welcome Back, O' Sleeping Dreamer]]></title>
            <link>https://tonyward.dev/articles/2023-01-05</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/2023-01-05</guid>
            <pubDate>Thu, 05 Jan 2023 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Well hello! It's been a long time. Hope you are doing well! It's been a long time since I've written anything, but I wanted to get back to writing. So what have I been up to these past few years?</p>
<a id="departed-from-ember-and-dove-into-react" href="#departed-from-ember-and-dove-into-react"><h2>Departed from Ember and Dove Into React</h2></a>
<p>In 2018 I took a role at a company called <a href="https://lifeomic.com" target="_blank" rel="noopener noreferrer">LifeOmic</a> which was my first dive into using React. I was there for almost 4 years, which is the longest I've stayed at any one company. It was overall a really great learning experience for me. I was convinced I'd only use Ember for a long time in my career, but I had some great friends working at LifeOmic and they thought I could bring a lot to the table by joining LifeOmic. Working in React was quite nice and a good change of pace. Like all programming languages, libraries, frameworks, etc. I definitely have some complaints, but overall I enjoyed my time using it.</p>
<a id="built-a-design-system" href="#built-a-design-system"><h2>Built a Design System</h2></a>
<p>While I was at LifeOmic, I built our Design System called Chroma with a good friend, <a href="https://www.doryan.co" target="_blank" rel="noopener noreferrer">Doryan Algarra</a>. I initially built it in private, on our private Bitbucket (and eventually GitHub) to iron out some kinks, but eventually took it public on GitHub. You can find it <a href="https://github.com/lifeomic/chroma-react" target="_blank" rel="noopener noreferrer">today still on GitHub!</a>. Chroma was used across all of the LifeOmic products to help designers and developers build web products quickly, with consistent UX. We focused on all the things you'd expect from a Design System - consistent UI/UX, a11y standards, etc. I was really proud with it by the time I left, and was happy to see how great the adoption was. I learned a lot by taking a Design System public - moreso less about code, but more how to convince people Design Systems are a good idea, how to get buy-in from upper management, etc. - I may write about this!</p>
<p><a href="https://medium.com/lifeomic/open-sourcing-our-shared-component-library-d15bc6abefcc" target="_blank" rel="noopener noreferrer">You can find out more here!</a></p>
<a id="built-an-icon-set" href="#built-an-icon-set"><h2>Built an Icon Set</h2></a>
<p>If you have a Design System, why not build your own icon set as well? That's exactly what we did. I built <a href="https://github.com/lifeomic/chromicons" target="_blank" rel="noopener noreferrer">Chromicons</a> with Doryan which is our take on a custom icon set. Doryan is an excellent artist and someone I really admire. One day we were reviewing something with Chroma and we started talking - "what if instead of using feather icons, we just build our own?". That question took off like a rocket and we ran with it to build Chromicons.</p>
<p>One of the things I was proud of building was the process for adding icons. The icons were made in Figma. You'd export them to an SVG file and put them in the <a href="https://github.com/lifeomic/chromicons/tree/master/src/raw/lined" target="_blank" rel="noopener noreferrer">lined directory</a>. After that, React components would get generated automatically via SVGR:</p>
<pre><code><span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">generate:react-components</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">svgr --template svg-template.js src/raw/lined --out-dir src/build/react/lined --typescript</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span></span></code></pre>
<p>Essentially you point SVGR to a JS template to use to convert the SVG file to a component, tell it the directory of your icons, and then tell it where to output the React components. For the template, we are setting the viewbox, adding a <code><span><span style="color:var(--sh-identifier)">data</span><span style="color:var(--sh-sign)">-</span></span></code> attribute for testing, and setting the <code><span><span style="color:var(--sh-identifier)">aria</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">hidden</span></span></code> attribute to true as these are things we wanted <em>all</em> icons to have.</p>
<pre><code><span><span style="color:var(--sh-comment)">// from https://github.com/lifeomic/chromicons/blob/master/svg-template.js</span></span>
<span><span style="color:var(--sh-comment)"></span></span>
<span><span style="color:var(--sh-comment)">/**</span></span>
<span><span style="color:var(--sh-comment)"> * This template allows us to modify the generated React component template.</span></span>
<span><span style="color:var(--sh-comment)"> */</span></span>
<span></span>
<span><span style="color:var(--sh-keyword)">function</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">template</span><span style="color:var(--sh-sign)">(</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">template</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">_</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">imports</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">componentName</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">props</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">jsx</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">exports</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">jsx</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">openingElement</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">attributes</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">jsx</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">openingElement</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">attributes</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">JSXAttribute</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">JSXIdentifier</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">viewBox</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">value</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">StringLiteral</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">value</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">0 0 24 24</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-comment)">// Currently documentation folks at LifeOmic use `data-icon` to identify</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-comment)">// which icon is being used so they can import it in their app</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">JSXAttribute</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">JSXIdentifier</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">data-icon</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">value</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">StringLiteral</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">value</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">componentName</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">name</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-comment)">// For our generated React components, we think this makes sense</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-comment)">// to do by default: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-hidden_attribute</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">JSXAttribute</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">JSXIdentifier</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">aria-hidden</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-identifier)">value</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">type</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">StringLiteral</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">value</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">true</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">return</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">template</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">ast</span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">    </span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-identifier)">imports</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">    const </span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-identifier)">componentName</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)"> = (</span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-identifier)">props</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)">) =&gt; </span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-identifier)">jsx</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">    </span><span style="color:var(--sh-sign)">${</span><span style="color:var(--sh-identifier)">exports</span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-string)"></span></span>
<span><span style="color:var(--sh-string)">  </span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span>
<span><span style="color:var(--sh-identifier)">module</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">exports</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">template</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<a id="became-a-dad" href="#became-a-dad"><h2>Became a Dad</h2></a>
<p>Probably the highlight of this time being away - I became a dad! I've always wanted to be a father, so this was a dream come true. Seeing my son grow every day is such a treat and I am very thankful for the time I got to spend with him while he was a baby during the COVID lockdowns. I will cherish it forever!</p>
<a id="back-to-ember" href="#back-to-ember"><h2>Back to Ember</h2></a>
<p>After almost four years of React, I'm back working in Ember! I took a job at CrowdStrike to work on their Design System and UX Foundations team. I'm really excited about this opportunity and have loved my time thus far. I'm looking forward to what we accomplish!</p>
<a id="talk-soon?" href="#talk-soon?"><h2>Talk Soon?</h2></a>
<p>That's really about it in a nutshell! I hope to write more frequently now that I've redone my personal site here. Hopefully hear more from me soon! Cheers!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Being a Part of EmberConf2018]]></title>
            <link>https://tonyward.dev/articles/ember-conf-2018</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/ember-conf-2018</guid>
            <pubDate>Sun, 25 Mar 2018 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Howdy! This was my first time attending EmberConf in person, and wow, what an experience! If you've never been, I highly recommend it! I thought I'd jot down some really quick thoughts on everything. This year, I submitted a CFP for a MiniTalk and was accepted. This was the first time I've ever talked at a conference and it was a blast. You can catch my talk and slides below in the Links section.</p>
<img alt="Image displaying folks I sat with at Ember Conf 2018" loading="lazy" width="2048" height="1362" decoding="async" data-nimg="1" style="color:transparent;background-size:cover;background-position:50% 50%;background-repeat:no-repeat;background-image:url(&quot;data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 200'%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='20'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='none' style='filter: url(%23b);' href='data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/wAARCAAFAAgDAREAAhEBAxEB/9sAQwAKBwcIBwYKCAgICwoKCw4YEA4NDQ4dFRYRGCMfJSQiHyIhJis3LyYpNCkhIjBBMTQ5Oz4+PiUuRElDPEg3PT47/9sAQwEKCwsODQ4cEBAcOygiKDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDO1LU9ROukvfTOrSAhS3Cj0+lY2TV7G12j/9k='/%3E%3C/svg%3E&quot;)" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf.1axd9ana0b3tt.jpeg&amp;w=2048&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf.1axd9ana0b3tt.jpeg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf.1axd9ana0b3tt.jpeg&amp;w=3840&amp;q=75">
<a id="the-community-conf" href="#the-community-conf"><h2>The Community &amp; Conf</h2></a>
<p>Holy cow, everyone was so nice! This was the first conference I've been to where I've felt 100% comfortable. You could strike up a conversation with anyone! Everyone was extremely friendly and approachable. It was the most inclusive event I've ever been to. Hats off to the organizers!</p>
<p>All of this is further supported by the Slack channel. Outside of the conference, folks were posting where they were going to hang out and inviting anyone else who may be interested. There were arcade trips, bar visits, breakfast trips, and even karaoke! These events were great opportunities to meet other folks in the Ember community.
There were so many great talks this year! NuclearTide and other folks have posted all-things-EmberConf here: https://github.com/nucleartide/emberconf-2018/blob/master/Readme.md</p>
<a id="giving-a-minitalk" href="#giving-a-minitalk"><h2>Giving a MiniTalk</h2></a>
<p>Giving a MiniTalk was a great experience. I felt very comfortable after talking with folks at the conference beforehand— there really wasn't anything to be nervous about! I highly recommend giving a MiniTalk at EmberConf if you're interested in sharing something you're working on, but don't want to give a full-blown talk. You can do it!</p>
<a id="conclusion" href="#conclusion"><h2>Conclusion</h2></a>
<p>Overall, EmberConf 2018 was a blast! The community is so great and the conference was the best I've been to. EmberConf 2019 is coming up next year back in Portland from March 18-20th! Maybe see you there!?</p>
<p><a href="https://www.youtube.com/watch?v=4fI72aZl_N8" target="_blank" rel="noopener noreferrer">https://www.youtube.com/watch?v=4fI72aZl_N8</a></p>
<a id="additional-pictures" href="#additional-pictures"><h2>Additional Pictures</h2></a>
<img alt="Image displaying my talk at Ember Conf 2018" loading="lazy" width="2048" height="1536" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf2.09hcw4uhvz6zb.jpeg&amp;w=2048&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf2.09hcw4uhvz6zb.jpeg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf2.09hcw4uhvz6zb.jpeg&amp;w=3840&amp;q=75">
<img alt="Image displaying the crowd at Ember Conf 2018" loading="lazy" width="2048" height="1536" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf3.39x5hezyrx2d1.jpeg&amp;w=2048&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf3.39x5hezyrx2d1.jpeg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fconf3.39x5hezyrx2d1.jpeg&amp;w=3840&amp;q=75">]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Ember Engines - Tips]]></title>
            <link>https://tonyward.dev/articles/ember-engines-tips</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/ember-engines-tips</guid>
            <pubDate>Tue, 20 Jun 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Welcome to another post about Ember Engines! It's been at least nine months since we've moved our Ember app to an engine (there's a pregnancy joke in here somewhere…) and I feel as though I have a few things that'd be helpful to share with fellow Embereños. My coworker, Todd Jordan, from the Ember learning team gave a fantastic talk at Ember Conf 2017 on how we manage our engines. Go check his video out-it is jammed pack with great info! The point of this post is to touch on a few key points for quick consumption if you don't have time to watch the entire video and also roll in some of my opinions.</p>
<a id="set-up-dedicated-chat-channels" href="#set-up-dedicated-chat-channels"><h2>Set Up Dedicated Chat Channels</h2></a>
<p>Go setup a Slack channel if you don't have one already. You won't regret it. Communication is 🔑, especially as you add more engines and internal add-ons/dependencies. Even if your team is really small to start with, it'll be helpful to have very focused rooms. Can't or don't want to use Slack? There are plenty of other options.</p>
<p>The Ember Community Slack has a great format. It's easy to get help when you need it. Some channels are very clean and reserved for important announcements, while others are used for getting help. I recommend taking a page from their book and having a few different channels:</p>
<ul>
<li><strong>Announcements</strong>: This should be used for announcing major changes. Examples are publishing newer versions of shared add-ons/dependencies, upgrading the host app's Ember version in a branch, and similar “I-may-break-stuff” chats. Try to keep this channel clean so that important messages aren't drowned out/ignored.</li>
<li><strong>Help</strong>: This channel can be used to ask and answer questions. There are going to be growing pains while you and your team figure things out. Especially as the number of engines/add-ons you support grows over time. Allow folks to ask questions, send links to PRs for additional eyes, ask for troubleshooting help when tests pass with Phantom locally, but not on the build server, etc. and keep the off-topic convos to a minimum.</li>
<li><strong>Specific Engine or Addon Channels</strong>: Depending on how many engines/add-ons you have, you may find it helpful to have a separate channel for specific questions. If you're a smaller group and just starting out, this may be overkill.</li>
<li><strong>Off Topic</strong>: This channel is the fun one. Bonding with your fellow Emberños by chatting about Game of Thrones, discussing how a new version of node.js just released, and GIFs are all acceptable here (and recommended).</li>
</ul>
<a id="dependency-sharing" href="#dependency-sharing"><h2>Dependency Sharing</h2></a>
<p>Use semantic versioning where you can, but keep Ember and Ember Data pinned (or use the ~). The reason for this is so you know exactly which version of Ember you are on. A case where this broke us (if we had used the caret) is with Ember 2.12-issue posted here, where our engines lost their styling due to treeForAddon not being called. More on Ember upgrades in a bit…</p>
<p>Semantic versioning of our engines in our host app package.json has worked very well, as long as you are mindful of breaking changes. Using the ^ allows us to automatically pull in minor changes (feature updates, for example) and bug fixes. This is great, especially for internal dependencies such as your engines and add-ons. External dependencies should be treated the same way until they give you a reason to pin versions (or use the ~).</p>
<pre><code><span><span style="color:var(--sh-comment)">/** host app package.json */</span></span>
<span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-comment)">// ...other package.json info here...</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">dependencies</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">engine-a</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">^2.0.0</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">engine-b</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">^6.3.0</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>Making a potentially breaking change? Post it in one of the channels mentioned above. It's better to just bump the major version than to make a potentially breaking change on other people's builds. Once again, communication is key here!</p>
<a id="ember-upgrades" href="#ember-upgrades"><h2>Ember Upgrades</h2></a>
<p>When it's time to upgrade Ember versions, use the trusty announcements channel mentioned above to declare the intent of the upgrade. In my opinion, the host application should create a branch with the upgrade first. Engines can then run their upgrade branch linked (npm or yarn) to the upgrade host branch. This has been really smooth for us.</p>
<p>A super helpful add-on is ember-try. It allows you to run your tests against different dependencies. You can run against the latest version of Ember and Ember Data. It's as simple as setting up a scenario and declaring which versions to use. You can even test against canary and beta builds!</p>
<a id="leverage-yarn-for-the-host-app" href="#leverage-yarn-for-the-host-app"><h2>Leverage Yarn for the Host App</h2></a>
<p>Use a yarn lock file in your host app when gearing up for a release to production. Straight from yarn's page:</p>
<blockquote>
<p>In order to get consistent installs across machines, Yarn needs more information than the dependencies you configure in your package.json. Yarn needs to store exactly which versions of each dependency were installed.</p>
</blockquote>
<p>This is helpful because it'll ensure that the dependencies on your local machine match the dependencies when you go to package up your app on the build server, for example. This isn't required, but definitely nice to have in my opinion.</p>
<a id="have-a-good-deployment-plan" href="#have-a-good-deployment-plan"><h2>Have a Good Deployment Plan</h2></a>
<p>Having a good deployment plan for your engines is important so that you have version control. We have a pipeline that pulls down the engine, runs our unit, integration, and acceptance tests, and then deploys the “release candidate” (engine +host app) out to one of our development environments. We have automation tests that are written by our testing department that then run against the packaged app. These automation tests are the final stamp of approval. If all passes, we bump the version (or respect a version change in the package json) and then push the engine to artifactory to be consumed by the host app for realz.</p>
<p>Now, you may not need something this involved. Just ensure you have thought out how to manage all of your engines from a deployment standpoint! The goal being to respect semantic versioning and not introduce regressions.</p>
<a id="until-next-time" href="#until-next-time"><h2>Until next time…</h2></a>
<p>I hope these tips were helpful! What tips would you share after working with engines for some time? I'll be sure to update this page as things change. As always, thanks for the read and hit the recommend button if you enjoyed this post. Cheers 🍻!</p>
<a id="resources" href="#resources"><h2>Resources</h2></a>
<ul>
<li><a href="https://www.youtube.com/watch?v=LqQi6e_Rlnw" target="_blank" rel="noopener noreferrer">Todd Jordan's EmberConf2017: Ember Engines as an Application Platform talk</a></li>
<li><a href="https://slack.com/" target="_blank" rel="noopener noreferrer">Slack</a></li>
<li><a href="http://semver.org/" target="_blank" rel="noopener noreferrer">Semver</a></li>
<li><a href="https://github.com/ember-cli/ember-try" target="_blank" rel="noopener noreferrer">ember-try</a></li>
<li><a href="https://yarnpkg.com/lang/en/docs/yarn-lock/" target="_blank" rel="noopener noreferrer">Yarn</a></li>
</ul>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Parameter Validator Using ES6]]></title>
            <link>https://tonyward.dev/articles/js-parameter-validator</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/js-parameter-validator</guid>
            <pubDate>Tue, 23 May 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Are you looking for a module that will verify an object contains certain properties? What if you want to validate a function received valid parameters? Look no further- <a href="https://www.npmjs.com/package/parameter-validator" target="_blank" rel="noopener noreferrer"> parameter-validator</a> has you covered. I've started adopting this parameter validator in some of my personal projects recently. We use it at the company I work for all of the time.</p>
<p>I don't want to reiterate what the readme already includes, but it is really simple to use. To verify an object contains the correct properties, here's a simple example:</p>
<pre><code><span><span style="color:var(--sh-keyword)">let</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">params</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">Paula PureCloud</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">id</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">user1</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-comment)">// ES6 Destructuring assignment here</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-keyword)">let</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">id</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">validate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">params</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>If our <code><span><span style="color:var(--sh-identifier)">params</span></span></code> object does <em>not</em> contain a <code><span><span style="color:var(--sh-identifier)">name</span></span></code> or <code><span><span style="color:var(--sh-identifier)">id</span></span></code> property (say we did a <code><span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">delete person.name;</span><span style="color:var(--sh-string)">'</span></span></code> before calling validate), a <code><span><span style="color:var(--sh-class)">ParameterValidationError</span></span></code> will be thrown with a message <code><span><span style="color:var(--sh-class)">”Invalid</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">value</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">of</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">undefined</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">was</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">provided</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">for</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">parameter</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">”</span></span></code>. Pretty easy, right? This comes in handy to ensure the objects we are instantiating have everything they need. It can also set properties for the class via adding a 'this' as an argument. Check out the example:</p>
<pre><code><span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">validate</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">from</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">parameter-validator</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">export</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">default</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">class</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">MarvelClient</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">constructor</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">params</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">validate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">params</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">logger</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">apiKey</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span><span style="color:var(--sh-comment)">// The validator would assign logger from params to 'this.logger'</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// The validator would assign apiKey from params to 'this.apiKey'</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">/**</span></span>
<span><span style="color:var(--sh-comment)">    Use:</span></span>
<span><span style="color:var(--sh-comment)">    let marvelClient = new MarvelClient({</span></span>
<span><span style="color:var(--sh-comment)">        logger: new WinstonLoggerWrapper(),</span></span>
<span><span style="color:var(--sh-comment)">        apiKey: process.env.MarvelApiKey</span></span>
<span><span style="color:var(--sh-comment)">    });</span></span>
<span><span style="color:var(--sh-comment)">*/</span></span>
<span></span></code></pre>
<p>Another great use case is validating parameters sent to a particular function:</p>
<pre><code><span><span style="color:var(--sh-comment)">/**</span></span>
<span><span style="color:var(--sh-comment)">* @param  {Object}  person</span></span>
<span><span style="color:var(--sh-comment)">* @param  {String}  person.name</span></span>
<span><span style="color:var(--sh-comment)">* @param  {String}  person.id</span></span>
<span><span style="color:var(--sh-comment)">* @throws {ParameterValidationError}</span></span>
<span><span style="color:var(--sh-comment)">*/</span></span>
<span><span style="color:var(--sh-identifier)">doSomeMagic</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">person</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-keyword)">let</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">id</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">validate</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">person</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">[</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">name</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">        </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">id</span><span style="color:var(--sh-string)">'</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">]</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-comment)">// Your magic business logic here</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>If the <code><span><span style="color:var(--sh-identifier)">person</span></span></code> object does not have the <code><span><span style="color:var(--sh-identifier)">name</span></span></code> or <code><span><span style="color:var(--sh-identifier)">id</span></span></code> properties defined, it will throw an error. I've found this to be helpful when writing unit tests. There's an async version if you need to ensure that any errors thrown are wrapped in a Promise.</p>
<p>Just thought I'd share a neat module I use on a daily basis. Thanks!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[reCAPTCHA with Ember]]></title>
            <link>https://tonyward.dev/articles/ember-setting-up-recaptcha</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/ember-setting-up-recaptcha</guid>
            <pubDate>Fri, 21 Apr 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Ever wanted to add a reCAPTCHA to your Ember app to prevent automated software (robots 🤖) from generating requests? Good news! There's already an add-on that has you covered. It's really easy to use too, which is great. I recently added this to one of our signup forms.</p>
<img alt="Image displaying the reCAPTCHA prompt" loading="lazy" width="314" height="94" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fre.0jnm45u1jyf58.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fre.0jnm45u1jyf58.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fre.0jnm45u1jyf58.png&amp;w=640&amp;q=75">
<p>Head on over to the Google reCAPTCHA site and click the “Get reCAPTCHA” button. If you're already logged in with a Google account, hooray! If not, you'll need a Google account to use this service. Once you get logged in, it's really easy. I won't go through all of the steps to get all setup, but once you let them know your domains and other settings, you should have two keys. A “site key” and a “secret key”. The site key is used in your HTML for client-side verification. These are public. The secret key is used for communication between your site and Google. As they suggest, keep it a secret.</p>
<a id="install-the-add-on" href="#install-the-add-on"><h2>Install the add on</h2></a>
<p>From your Ember app, run the below cli command:</p>
<p>ember install ember-g-recaptcha
Setup your site key and add the component
You have two ways of specifying your site key in your Ember app. One option is to set it on your ENV variable as the read me suggests:</p>
<pre><code><span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">ENV</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">// ...</span></span>
<span><span style="color:var(--sh-comment)"></span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">gReCaptcha</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">siteKey</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">your-recaptcha-site-key</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">// ...</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>Another option is to set it on the component directly.</p>
<pre><code><span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">g</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">recaptcha</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">size</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">normal</span><span style="color:var(--sh-string)">'</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">sitekey</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">your-recaptcha-site-key</span><span style="color:var(--sh-string)">'</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">onSuccess</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">action</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">onCaptchaResolved</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p><strong>Note:</strong> If you have automation tests running in test environments or don't want to create a site key just yet, you can use the one provided by Google's FAQ page. This test site key doesn't require you/your tests to prove you're not a robot. Just click the checkbox and be on your way. Note the nice little message about it being a test-only key.</p>
<img alt="Image displaying the reCAPTCHA disclaimer text" loading="lazy" width="316" height="94" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fretest.3f2tx0t6l36nw.png&amp;w=384&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fretest.3f2tx0t6l36nw.png&amp;w=640&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fretest.3f2tx0t6l36nw.png&amp;w=640&amp;q=75">
<a id="content-security-policy" href="#content-security-policy"><h2>Content Security Policy</h2></a>
<p>By default, Ember specifies a content security policy. Update your settings as suggested by the Google FAQS. It should look something like:</p>
<pre><code><span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">ENV</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">//</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">contentSecurityPolicy</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-comment)">//</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">style-src</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">self</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">unsafe-inline</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">script-src</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">:</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">self</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)"> https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">frame-src</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">self</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)"> https://www.google.com/recaptcha/</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<a id="onsuccess-action" href="#onsuccess-action"><h2>onSuccess Action</h2></a>
<p>You'll want to use a closure action for when the user passes the reCAPTCHA. This can be done by setting it up in your template and then adding the action in your actions hash:</p>
<pre><code><span><span style="color:var(--sh-comment)">// component.hbs</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-sign)">{</span><span style="color:var(--sh-identifier)">g</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">recaptcha</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">size</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">normal</span><span style="color:var(--sh-string)">'</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">sitekey</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">your-recaptcha-site-key</span><span style="color:var(--sh-string)">'</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">onSuccess</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">action</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">onCaptchaResolved</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<pre><code><span><span style="color:var(--sh-comment)">// component.js</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-identifier)">actions</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">onCaptchaResolved</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">reCaptchaResponse</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">      </span><span style="color:var(--sh-comment)">// Validate the reCAPTCHA response, maybe do something in your component to allow the user to proceed, etc.</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<a id="validate-the-recaptcha-response" href="#validate-the-recaptcha-response"><h2>Validate the reCAPTCHA response</h2></a>
<p>The server request you need to make to validate the reCAPTCHA response is:</p>
<pre><code><span><span style="color:var(--sh-sign)">#</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">POST</span></span>
<span><span style="color:var(--sh-identifier)">https</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-comment)">//www.google.com/recaptcha/api/siteverify?secret=&lt;SECRET_KEY_FROM_ADMIN_CONSOLE&gt;&amp;response=&lt;VALUE_FROM_onCaptchaResolved&gt;</span></span>
<span><span style="color:var(--sh-comment)"></span></span></code></pre>
<p>The response from the POST currently looks something this:</p>
<pre><code><span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">success</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">true</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">challenge_ts</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">2017-04-21T14:24:37Z</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">hostname</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">localhost</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>And that's the waaaaaaaaay the news goes
Hope this was somewhat helpful in showing how easy it is to add a reCAPTCHA to your Ember app. Have fun!</p>
<a id="resources" href="#resources"><h2>Resources</h2></a>
<ul>
<li><a href="https://github.com/algonauti/ember-g-recaptcha" target="_blank" rel="noopener noreferrer">https://github.com/algonauti/ember-g-recaptcha</a></li>
<li><a href="https://developers.google.com/recaptcha/docs/faq" target="_blank" rel="noopener noreferrer">https://developers.google.com/recaptcha/docs/faq</a></li>
</ul>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Setting up ESLint in Ember]]></title>
            <link>https://tonyward.dev/articles/ember-setting-up-eslint</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/ember-setting-up-eslint</guid>
            <pubDate>Wed, 12 Apr 2017 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Looking to setup a linter for your Ember app? I've setup ESLint with a handful of repos lately, so I thought it was time for another post on how to get up and running quickly. I'm going to assume you already know about ESLint and linting in general. I'll try and keep it short and sweet.</p>
<p>(<strong>Note:</strong> Looks like a new ESLint plugin for Ember best practices just surfaced. Take a look at the README, but continue to read along for quick setup!)</p>
<a id="step-1-install-ember-cli-eslint" href="#step-1-install-ember-cli-eslint"><h2>Step #1: Install ember-cli-eslint</h2></a>
<p><strong>Note:</strong> For Ember 2.12 and higher, ember-cli will do this step for you!</p>
<p>Go into your Ember app directory and run:</p>
<p>ember install ember-cli-eslint
After running the ember install above, you'll see a message like below. By default, Ember apps create those JSHint config files for you. You may want to double check what's in those files, but if you haven't touched them at all, it's more than likely safe to whack 'em.</p>
<p>No matter what option you choose, you'll find a .eslintrc.js file in the root of your directory. This file is where you will specify linting rules. Yay!</p>
<a id="step-2a-create-a-test-rule" href="#step-2a-create-a-test-rule"><h2>Step #2a: Create a test rule</h2></a>
<p>If you want to do a quick test to see if things are working, modify your .eslintrc.js file to look something like this:</p>
<pre><code><span><span style="color:var(--sh-identifier)">module</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">exports</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-comment)">// other stuff</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">rules</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">no-console</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">error</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-comment)">// error if console statements</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<p>This is one of the many rules provided by ESLint, where it will error if there are any console statements. Easy enough.</p>
<a id="step-2b-make-sure-we-get-a-linting-error" href="#step-2b-make-sure-we-get-a-linting-error"><h2>Step #2b: Make sure we get a linting error</h2></a>
<p>What's really nice is that the linting rules are evaluated when running ember t. Just go into one of your JS files, add a console.log('hi'); statement and run ember t. You should get a linting error that looks like:</p>
<pre><code><span><span style="color:var(--sh-identifier)">ember</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">quickstart</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">app</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">components</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">pasta</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">selector</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">js</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-class)">6</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-class)">13</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">error</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Unexpected</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">console</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">statement</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">no</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">console</span></span>
<span><span style="color:var(--sh-class)">✖</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">problem</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">error</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">0</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">warnings</span><span style="color:var(--sh-sign)">)</span></span>
<span></span></code></pre>
<p>Sweet-things appear to be working!</p>
<a id="step-3-add-real-rules" href="#step-3-add-real-rules"><h2>Step #3: Add real rules</h2></a>
<p>Depending on if you're working solo or on a team, you may want to follow certain rules. The available rules can be found on the ESLint website. Just add them like we added our test rule above. Your JS files are now linted-good job!</p>
<a id="step-4-install-ember-cli-template-lint" href="#step-4-install-ember-cli-template-lint"><h2>Step #4: Install ember-cli-template-lint</h2></a>
<p>So we are linting JS files now, what about our hbs templates? Can we lint those? Of course!</p>
<pre><code><span><span style="color:var(--sh-identifier)">ember</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">install</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">ember</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">cli</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">template</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">lint</span></span>
<span></span></code></pre>
<p>After running the above command, a .template-lintrc.js file is created. Guess what goes in there? Template rules!</p>
<a id="step-5a-create-a-test-template-rule" href="#step-5a-create-a-test-template-rule"><h2>Step #5a: Create a test template rule</h2></a>
<p>We'll do a quick test, just to ensure everything is setup for linting our templates. Add the following to your lintrc file:</p>
<pre><code><span><span style="color:var(--sh-identifier)">module</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">exports</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">extends</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">recommended</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">rules</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">bare-strings</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">true</span><span style="color:var(--sh-sign)">,</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-comment)">// don't allow bare strings</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<a id="step-5b-make-sure-we-get-a-linting-error" href="#step-5b-make-sure-we-get-a-linting-error"><h2>Step #5b: Make sure we get a linting error</h2></a>
<p>Go over to one of your hbs files and add a bare string like:</p>
<pre><code><span><span style="color:var(--sh-sign)">&lt;</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span><span style="color:var(--sh-jsxliterals)">Should have template error</span><span style="color:var(--sh-sign)">&lt;/</span><span style="color:var(--sh-entity)">div</span><span style="color:var(--sh-sign)">&gt;</span></span>
<span></span></code></pre>
<p>Then just run the trusty ember t command, and you should see:</p>
<pre><code><span><span style="color:var(--sh-identifier)">bare</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">strings</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Non</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">translated</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">string</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">used</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-identifier)">ember</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">quickstart</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">templates</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">components</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">pasta</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">selector</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">L1</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-class)">C5</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">:</span></span>
<span><span style="color:var(--sh-string)">`</span><span style="color:var(--sh-string)">Should have template error</span><span style="color:var(--sh-string)">`</span></span>
<span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Template</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Linting</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">Error</span></span>
<span></span></code></pre>
<p>These errors show up at the top of the console output, so if you're working with a big code base with a lot of errors, you may miss them. Luckily though, you can either a) scroll up or b) notice they failed as tests via the message:</p>
<pre><code><span><span style="color:var(--sh-identifier)">not</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">ok</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">4</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">PhantomJS</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">2</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-class)">1</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">TemplateLint</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">ember</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">quickstart</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">templates</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">components</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">pasta</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">selector</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">hbs</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">should</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">pass</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">TemplateLint</span></span>
<span></span></code></pre>
<a id="step-6-add-real-template-lint-rules" href="#step-6-add-real-template-lint-rules"><h2>Step #6: Add real template lint rules</h2></a>
<p>The available templates rules can be found on the ember-template-lint README. Here's a brief example:</p>
<pre><code><span><span style="color:var(--sh-identifier)">module</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">exports</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-keyword)">extends</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">recommended</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">rules</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">bare-strings</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">true</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">triple-curlies</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-keyword)">false</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">block-indentation</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">4</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">;</span></span>
<span></span></code></pre>
<a id="step-7-setup-your-editor" href="#step-7-setup-your-editor"><h2>Step #7: Setup your editor</h2></a>
<p>If you use VSCode, this ESLint extension is awesome. My favorite part is that it just works out of the box after installing. No other setup was required (at least for me…). The extension also has an auto-fix command, where it will automatically resolve any lint errors. This is done via the lightbulb in the gutter or via the ESLint: Fix all auto-fixable problems command.</p>
<img alt="Image displaying the red squiggle for an ESLint error" loading="lazy" width="498" height="184" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fember-eslint-error-ex.3hhi519uyc_kx.png&amp;w=640&amp;q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fember-eslint-error-ex.3hhi519uyc_kx.png&amp;w=1080&amp;q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fember-eslint-error-ex.3hhi519uyc_kx.png&amp;w=1080&amp;q=75">
<p>Other editors have similar plugins/extensions, so just google away for your answers. It's nice to be working in a file and see any lint errors right away, before even running ember t.</p>
<a id="that-s-all" href="#that-s-all"><h2>That's all</h2></a>
<p>Adding ESLint to your Ember project isn't that difficult, which is really nice. If you have any issues, the bulk of them will likely be editor extension/plugin related. If you try this out and hit some snags, let me know and I'd be glad to help! Happy linting!</p>
<a id="resources" href="#resources"><h2>Resources</h2></a>
<ul>
<li><a href="http://eslint.org/" target="_blank" rel="noopener noreferrer">ESLint</a></li>
<li><a href="https://github.com/ember-cli/ember-cli-eslint" target="_blank" rel="noopener noreferrer">ember-cli-eslint</a></li>
<li><a href="https://github.com/rwjblue/ember-cli-template-lint" target="_blank" rel="noopener noreferrer">ember-cli-template-lint</a></li>
<li><a href="https://justinaray.github.io/LintingMFAO/#/" target="_blank" rel="noopener noreferrer">Great linting presentation</a></li>
<li><a href="https://github.com/rwjblue/qunit-eslint" target="_blank" rel="noopener noreferrer">Lint single qunit test files</a></li>
<li><a href="https://github.com/BadgeLabs/mocha-eslint" target="_blank" rel="noopener noreferrer">Run ESLint in Mocha test files without a task runner</a></li>
<li>(New!) <a href="https://github.com/ember-best-practices/eslint-plugin-ember-best-practices" target="_blank" rel="noopener noreferrer">Ember best practices-eslint plugin</a></li>
</ul>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[CSS with Ember Engines]]></title>
            <link>https://tonyward.dev/articles/ember-engines-css</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/ember-engines-css</guid>
            <pubDate>Sun, 30 Oct 2016 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>First off, welcome to my second post about Ember Engines! I'm working on a series of posts to talk about what I learned when migrating a traditional Ember app to an Ember Engine.</p>
<p>Were you previously using <a href="https://github.com/jayphelps/ember-cli-less" target="_blank" rel="noopener noreferrer">ember-cli-less</a> in your Ember App? By default, the addon compiles <code><span><span style="color:var(--sh-identifier)">app</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">app</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">less</span></span></code> into <code><span><span style="color:var(--sh-identifier)">dist</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">assets</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">app</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">css</span></span></code>. That means you have a single LESS file, importing all of your other LESS files (if applicable), that gets shoved out at build time to the right spot for Ember to pickup and use. One thing I noticed when moving to an engine, was that this didn't work like it did in a traditional app.</p>
<a id="the-engine-problem" href="#the-engine-problem"><h2>The Engine Problem</h2></a>
<p>When we moved over to an engine, I was wondering why none of our CSS was applied. Nothing was styled as it was in our traditional app and I was thoroughly confused. I couldn't really find any documentation on it online, either. The other item not in my favor, was that I'm still fairly new to Ember and the build process.</p>
<p>I'm not a Broccoli master, so this did not come to me very quickly. Luckily, I have a lot of great Ember gurus around me. If you have a good understanding of how the ember-cli build process goes, experience working with addons already, or know a lot about Broccoli in general, you probably already have an idea of what needs to happen. The problem was that our LESS files were not being merged into the Broccoli funnel for the engine. This is required for add-ons and engines!</p>
<a id="the-solution" href="#the-solution"><h2>The Solution</h2></a>
<p>One solution is to use <a href="https://github.com/gabrielgrant/broccoli-less-single" target="_blank" rel="noopener noreferrer">broccoli-less-single</a>. The key part being that this supports <code><span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-keyword)">import</span></span></code>s. Using this allows you to merge your LESS files into a single CSS file, then merge that into the Broccoli funnel.</p>
<p><strong>Warning:</strong> This solution is currently out of date as of 06/15 with Ember 2.12. If you are using that version, please <a href="https://github.com/ember-engines/ember-engines/issues/376" target="_blank" rel="noopener noreferrer">follow the issue on GitHub here</a>.</p>
<p>For the sake of this post, let's say I have the following LESS file:</p>
<pre><code><span><span style="color:var(--sh-comment)">// This file resides at: addon/styles/addon.less</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">components/vegetables.less</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">components/fruits.less</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-keyword)">import</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">variables.less</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">error</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">@</span><span style="color:var(--sh-identifier)">error</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">color</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-sign)">}</span></span>
<span></span></code></pre>
<p>The first thing you'll want to do is add <code><span><span style="color:var(--sh-identifier)">broccoli</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">less</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">single</span></span></code> and <code><span><span style="color:var(--sh-identifier)">broccoli</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">merge</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">trees</span></span></code> to your package.json.</p>
<pre><code><span><span style="color:var(--sh-comment)">// npm install broccoli-less-single --save</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// npm install broccoli-merge-tress --save</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-comment)">// package.json</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">dependencies</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">broccoli-less-single</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">~0.6.0</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">broccoli-merge-trees</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">~1.1.1</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">,</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">ember-cli-htmlbars</span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-string)">"</span><span style="color:var(--sh-string)">~1.0.1</span><span style="color:var(--sh-string)">"</span></span>
<span><span style="color:var(--sh-space)">  </span><span style="color:var(--sh-sign)">}</span><span style="color:var(--sh-sign)">,</span></span>
<span></span></code></pre>
<p>After doing above, then in your <code><span><span style="color:var(--sh-identifier)">index</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">js</span></span></code>:</p>
<pre><code><span><span style="color:var(--sh-comment)">// index.js</span></span>
<span><span style="color:var(--sh-comment)"></span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">EngineAddon</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">require</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">ember-engines/lib/engine-addon</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">MergeTrees</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">require</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">broccoli-merge-trees</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-keyword)">var</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">CompileLess</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-identifier)">require</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">broccoli-less-single</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">)</span><span style="color:var(--sh-sign)">;</span></span>
<span><span style="color:var(--sh-identifier)">module</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">exports</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">=</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">EngineAddon</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">extend</span><span style="color:var(--sh-sign)">(</span><span style="color:var(--sh-sign)">{</span></span>
<span><span style="color:var(--sh-space)">    </span><span style="color:var(--sh-identifier)">name</span><span style="color:var(--sh-sign)">:</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">‘my</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">engine</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">,</span></span>
<span><span style="color:var(--sh-string)">    treeForAddon(tree) {</span></span>
<span><span style="color:var(--sh-string)">        var defaultTree = this._super.treeForAddon.call(this, tree);</span></span>
<span><span style="color:var(--sh-string)">        var compiledLessTree = new CompileLess(tree, </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">addon</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">less</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">, this.name + </span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-identifier)">css</span><span style="color:var(--sh-string)">'</span><span style="color:var(--sh-string)">);</span></span>
<span><span style="color:var(--sh-string)">        return new MergeTrees([defaultTree, compiledLessTree]);</span></span>
<span><span style="color:var(--sh-string)">    }</span></span>
<span><span style="color:var(--sh-string)">});</span></span>
<span><span style="color:var(--sh-string)"></span></span></code></pre>
<p>So what does the above snippet actually do? We take the <code><span><span style="color:var(--sh-identifier)">defaultTree</span></span></code>, which is provided from the <code><span><span style="color:var(--sh-keyword)">super</span></span></code> call, compile our LESS file under <code><span><span style="color:var(--sh-identifier)">styles</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">addon</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">less</span></span></code>, and output it to our <code><span><span style="color:var(--sh-keyword)">this</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">name</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-sign)">+</span><span style="color:var(--sh-space)"> </span><span style="color:var(--sh-class)">‘</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">css</span><span style="color:var(--sh-string)">'</span></span></code> file. After that, we merge the default tree with our new compiled tree and all of that gets shoved out to the <code><span><span style="color:var(--sh-string)">/dist/assets</span><span style="color:var(--sh-sign)">/</span><span style="color:var(--sh-identifier)">vendor</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">css</span></span></code>.</p>
<p>That should be it to get your styles back in action!</p>
<a id="a-note-about-ember-cli-build-js" href="#a-note-about-ember-cli-build-js"><h2>A note about ember-cli-build.js</h2></a>
<p>One side note I wanted to mention is that we aren't doing anything in <code><span><span style="color:var(--sh-identifier)">ember</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">cli</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">build</span><span style="color:var(--sh-sign)">.</span><span style="color:var(--sh-property)">js</span></span></code> like we would normally in our traditional Ember app. Why is that? Just read the comment and you'll get your answer!</p>
<pre><code><span><span style="color:var(--sh-comment)">/*</span></span>
<span><span style="color:var(--sh-comment)"> This build file specifies the options for the dummy test app of this</span></span>
<span><span style="color:var(--sh-comment)"> addon, located in `/tests/dummy`</span></span>
<span><span style="color:var(--sh-comment)"> This build file does *not* influence how the addon or the app using it</span></span>
<span><span style="color:var(--sh-comment)"> behave. You most likely want to be modifying `./index.js` or app's build file</span></span>
<span><span style="color:var(--sh-comment)"> */</span></span>
<span></span></code></pre>
<a id="an-untested-note-about-sass-files" href="#an-untested-note-about-sass-files"><h2>An untested note about SASS files</h2></a>
<p>Even though the examples above are using LESS, you should be able to swap out <code><span><span style="color:var(--sh-identifier)">broccoli</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">less</span><span style="color:var(--sh-sign)">-</span><span style="color:var(--sh-identifier)">single</span></span></code> to use <a href="https://github.com/joliss/broccoli-sass" target="_blank" rel="noopener noreferrer">broccoli-sass</a> instead and be good to go. If not, I'll buy you a beer.</p>
<a id="ta-da!" href="#ta-da!"><h2>Ta-da!</h2></a>
<p>Hopefully that answers you questions about previously using ember-cli-less and the quick changes when you move to an engine. I'm sure there are other ways to handle it; however, the method I showed you worked the best for me. Happy engining!</p>
<p><strong>New (4/24/2017):</strong> There's now an EmberMap course on broccoli!</p>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
        <item>
            <title><![CDATA[Converting an Ember App to an Ember Engine]]></title>
            <link>https://tonyward.dev/articles/ember-migrating-to-engines</link>
            <guid isPermaLink="false">https://tonyward.dev/articles/ember-migrating-to-engines</guid>
            <pubDate>Sun, 23 Oct 2016 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>Well, well… good ol' Ember engines. We had a task on our JIRA board for migrating our traditional Ember app to an engine. I thought it would be a great opportunity to learn something new. I was correct-it was a lot of fun and I learned a ton. So what are Ember Engines?</p>
<p>My favorite way to explain an Ember Engine is this text from the core team:</p>
<blockquote>
<p>a way to break apart a large Ember app into several, smaller apps that can be merged together into a cohesive whole.</p>
</blockquote>
<a id="why-did-we-want-to-move-to-an-engine-in-the-first-place?" href="#why-did-we-want-to-move-to-an-engine-in-the-first-place?"><h2>Why did we want to move to an engine in the first place?</h2></a>
<p>The company I work for is pretty large. We offer these sweet web applications to allow users to chat, video conference, take phone calls, do content management stuff, and all other types of cool things. This main web app we have is written in Ember. Different teams own different pieces of the product, like most places do now-a-days.</p>
<p>We have this parent app that's written in Ember, right? And it just iframes in all of these other Ember applications from teams like mine. Then one day, our director met with us and showed us the answer to our iframe party— Ember Engines. Man, they are sweet!</p>
<p>The benefits were pretty awesome. Instead of i-framing in, we are now just consumed by the parent app. We don't have to do goofy stuff with the router to get the correct URL we want. Instead, it's all done through the parent app's router. Need to use a service that the parent app is using? No problem!</p>
<a id="resources-to-get-started-with-engines" href="#resources-to-get-started-with-engines"><h2>Resources to get started with engines</h2></a>
<p>When I started learning about engines, the links below were great resources. I found the Engine Training link the most helpful when I was deep in the trenches of migrating our app. It really gave me a good understanding of what was required when moving a traditional app to an engine.</p>
<ul>
<li><a href="https://github.com/dgeb/ember-engines" target="_blank" rel="noopener noreferrer">Ember Engines GitHub</a></li>
<li><a href="https://vimeo.com/157688181" target="_blank" rel="noopener noreferrer">Introduction to Ember Engines Video from GEM</a></li>
<li><a href="https://www.youtube.com/watch?v=m5J8AqSDHwc&amp;amp;t=499s" target="_blank" rel="noopener noreferrer">On the Bleeding Edge with Ember Engines by Kacey Coughlin @ EmberSF</a></li>
<li><a href="https://github.com/trentmwillis/engine-training" target="_blank" rel="noopener noreferrer">Engine Training via trentmwillis</a></li>
<li><strong>Update 01/27/2017:</strong> <a href="http://www.ember-engines.com/" target="_blank" rel="noopener noreferrer">www.ember-engines.com</a> is a fantastic resource with great guides</li>
</ul>
<a id="the-future-of-my-writings" href="#the-future-of-my-writings"><h2>The future of my writings</h2></a>
<p>Over the next few posts, I plan on sharing things I learned along the way. My hope is that it'll help someone out one day. If you're fairly new to Ember, maybe this will help! My agenda of topics include:</p>
<ul>
<li>How dependencies are handled in engines</li>
<li>Merging your LESS files if you used to use ember-cli-less</li>
<li>How things like ember-cli-replace and other addons change when you move to an engine</li>
<li>CDN URLs when in an engine</li>
<li>Build pipeline architecture idea</li>
</ul>]]></content:encoded>
            <author>@tonyward.dev (Tony Ward)</author>
        </item>
    </channel>
</rss>