Kevin Yank

Kevin Yank Kevin Yank’s website https://kevinyank.com Subspace Radio #59: Hand-to-hand combat <p>In <a href="https://www.subspace.fm/episodes/episode-59-hand-to-hand-combat-dis-5x10-life-itself">Subspace Radio #59</a>, Rob and I take stock of Star Trek: Discovery following its finale, “Life, Itself.” Burnham’s epic fistfight with Moll prompts us to revisit other Trek punch-ups of the past, including “The Way of the Warrior, Part II” (DS9), “The Gamesters of Triskelion” (TOS), “Tsunkatse” (VOY), “Star Trek” (2009), and “Trials and Tribble-ations”.</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/0b3e684c"></iframe> Wed, 19 Jun 2024 13:18:05 +0000 https://kevinyank.com/posts/subspace-radio-59-hand-to-hand-combat/ https://kevinyank.com/posts/subspace-radio-59-hand-to-hand-combat/ Subspace Radio #58: Into the belly of the beast <p>In <a href="https://www.subspace.fm/episodes/episode-58-into-the-belly-of-the-beast-dis-5x09-lagrange-point">Subspace Radio #58</a>, Rob and I are inspired by “Lagrange Point” to don our Breen helmets and travel into dangerous waters with Starfleet crews who have done the same over the years. We discuss “The Immunity Syndrome” (TOS), “Apocalypse Rising” (DS9) and “Unimatrix Zero” (VOY).</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/f22d3d5f"></iframe> Thu, 13 Jun 2024 04:38:57 +0000 https://kevinyank.com/posts/subspace-radio-58-into-the-belly-of-the-beast/ https://kevinyank.com/posts/subspace-radio-58-into-the-belly-of-the-beast/ Subspace Radio #57: Captains on their own <p>In <a href="https://www.subspace.fm/episodes/episode-57-captains-on-their-own-dis-5x08-labyrinths">Subspace Radio #57</a>, Rob and I are trapped in our separate mindscapes, with only a cryptic librarian version of the other for company. To pass the time, we consider how “Labyrinths” compares to other times starship captain have had to work solo, without their ship or crew. We discuss “Arena” (TOS), “Starship Mine” (TNG), “The Inner Light” (TNG), and “Resistance” (VOY).</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/183ad206"></iframe> Sun, 02 Jun 2024 01:51:20 +0000 https://kevinyank.com/posts/subspace-radio-57-captains-on-their-own/ https://kevinyank.com/posts/subspace-radio-57-captains-on-their-own/ Subspace Radio #56: Big ships <p>In <a href="https://www.subspace.fm/episodes/episode-56-big-ships-dis-5x07-erigah">Subspace Radio #56</a>, Rob and I were actually standing on the bridge of Federation HQ in “Erigah”; you just couldn’t see us for all the Breen! While we were there, we debated whether or not the Breen Dreadnought was the biggest starship we’ve seen to date. Other big ships we discussed: the Fesarius (TOS), the Narada (Star Trek 2009), the Romulan Warbird (TNG), the Jem’Hadar Battleship (DS9), the Enterprise-J (ENT), the Borg cube (TNG) and the USS Vengeance (Star Trek Into Darkness).</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/ec0b810a"></iframe> Wed, 22 May 2024 10:37:17 +0000 https://kevinyank.com/posts/subspace-radio-56-big-ships/ https://kevinyank.com/posts/subspace-radio-56-big-ships/ Subspace Radio #55: Technology in primitive cultures <p>In <a href="https://www.subspace.fm/episodes/episode-55-technology-in-primitive-cultures-dis-5x06-whistlespeak">Subspace Radio #55</a>, we whistle our way through the “fun run” on Halem’no as we discuss “Whistlespeak”, but are wise enough not to step into the rain-making machine at the finish line. Instead, we revisit past instances of primitive cultures unknowingly blended with advanced technology, including “The Apple” (TOS), “For the World is Hollow and I Have Touched the Sky” (TOS), and “Caretaker” (VOY).</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/bc905343"></iframe> Thu, 16 May 2024 13:44:03 +0000 https://kevinyank.com/posts/subspace-radio-55-technology-in-primitive-cultures/ https://kevinyank.com/posts/subspace-radio-55-technology-in-primitive-cultures/ Subspace Radio #54: The Mirror Universe <p>In <a href="https://www.subspace.fm/episodes/episode-54-the-mirror-universe-dis-5x05-mirrors">Subspace Radio #54</a>, we switch on the holographic blind on our Breen loading dock to discuss “Mirrors”, which turned out not to be quite the contribution to Mirror Universe lore we were expecting. To make up for it, we revisit all the other appearances of the Mirror Universe, including “Mirror, Mirror” (TOS), “Crossover” (DS9), “Through the Looking Glass” (DS9), “Shattered Mirror” (DS9), “Resurrection” (DS9), “The Emperor’s New Cloak” (DS9), and “In a Mirror, Darkly, Parts I &amp; II” (ENT). We also briefly reflect (!) on Discovery’s sojourn in the Mirror Universe in season one of that show.</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/bc4f166d"></iframe> Tue, 07 May 2024 10:40:48 +0000 https://kevinyank.com/posts/subspace-radio-54-the-mirror-universe/ https://kevinyank.com/posts/subspace-radio-54-the-mirror-universe/ Subspace Radio #53: Trapped in Time <p>In <a href="https://www.subspace.fm/episodes/episode-53-trapped-in-time-dis-5x04-face-the-strange">Subspace Radio #53</a>, Rob and I are unfazed by the time-travel shenanigans this week in “Face the Strange”, accustomed as we are to weaving our way through the history of the Star Trek Universe. Inspired to revisit other times our characters became stuck revisiting other times, we discuss “Time Squared” (TNG), “The Visitor” (DS9), “Timescape” (TNG), and “Shattered” (VOY).</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/de763c67"></iframe> Wed, 01 May 2024 09:57:11 +0000 https://kevinyank.com/posts/subspace-radio-53-trapped-in-time/ https://kevinyank.com/posts/subspace-radio-53-trapped-in-time/ Subspace Radio #52: Joined Trill <p>In <a href="https://www.subspace.fm/episodes/episode-52-joined-trill-dis-5x03-jinaal">Subspace Radio #52</a>, after defeating the cloak-bees of the dimly-lit rock quarries of Trill, Rob and I take a stroll down the lane (or a swim up the milky river?) of Trill history, discussing all of the joined Trill that we have encountered in our travels. We discuss “Facets” (DS9), “Rejoined” (DS9) and “The Host” (TNG).</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/191eac51"></iframe> Wed, 24 Apr 2024 12:51:52 +0000 https://kevinyank.com/posts/subspace-radio-52-joined-trill/ https://kevinyank.com/posts/subspace-radio-52-joined-trill/ Subspace Radio #51: Starships bumping into planets <p>In <a href="https://www.subspace.fm/episodes/episode-51-starships-bumping-into-planets-dis-5x01-red-directive-dis-5x02-under-the-twin-moons">Subspace Radio #51</a>, are back to finally, unbelievably, talk about Star Trek: Discovery for the very first time! The events of “Red Directive” (DIS) see us reflecting on other times starships have “nosed down” into the unforgiving soil of a world, alien or otherwise. We discuss “Star Trek Into Darkness”, “Timeless” (VOY), and “Star Trek Generations”.</p> <iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/37773313"></iframe> Thu, 18 Apr 2024 09:28:15 +0000 https://kevinyank.com/posts/subspace-radio-51-starships-bumping-into-planets/ https://kevinyank.com/posts/subspace-radio-51-starships-bumping-into-planets/ Use Tailwind CSS prefixes for shared design system components <figure> <img src="https://kevinyank.com/assets/images/blog/use-tailwindcss-prefixes-for-shared-design-system-components/khamkeo-vilaysing-WtwSsqwYlA0-unsplash.jpg" alt="A leafy tree is buffeted by a strong wind. In the foggy background of the windswept landscape, a second, nearly identical tree can barely be seen." /> <figcaption class="text-center text-sm font-light mbs-4">Photo by <a href="https://unsplash.com/@mahkeo?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Khamkéo Vilaysing</a> on <a href="https://unsplash.com/photos/trees-with-wind-photo-WtwSsqwYlA0?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></figcaption> </figure> <p>Surprisingly little has been written about how to use Tailwind with design systems or shared components, when both those components and the app consuming them are styled with Tailwind. Tailwind’s <a href="https://tailwindcss.com/docs/configuration#prefix"><code>prefix</code> option</a> is specifically designed to allow for this, but it’s a somewhat ugly solution that we did our best to avoid at Culture Amp until recently. Here’s everything we learned, and why we’re ultimately embracing <code>prefix</code>.</p> <div class="heading-wrapper"> <h2 id="a-very-brief-intro-to-tailwind" tabindex="-1">A very brief intro to Tailwind</h2> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#a-very-brief-intro-to-tailwind"><span class="sr-only">Permalink to A very brief intro to Tailwind</span> <span aria-hidden="true">#</span></a></div> <p><a href="https://tailwindcss.com/">Tailwind CSS</a> (Tailwind for short) is a not-uncontroversial choice of tool for projects where hand-crafting CSS selectors that apply to semantically meaningful patterns of HTML elements is not beneficial. We have decided to use it at Culture Amp, the reasons for which deserve an article of their own. At the risk of oversimplifying: if the structure of your UI matches the structure of your codebase (i.e. visual blocks tend to correspond to software components rather than sections of a document), you might save time and effort by applying styles to your elements directly in those components, rather than by writing CSS selectors that are tightly coupled to those components’ implementation. Again, there’s a lot more to say here, and I’ll try to write it up soon. Let me know if you’re keen to read it.</p> <p>Fundamentally, Tailwind scans your application’s source code for class names like this:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <p>…and generates a stylesheet that contains only the necessary styles to match the classes you’ve used:</p> <pre><code class="language-css"><span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p>While at first glance this looks like it would have all the downsides of inline styles (the <code>style</code> attribute), Tailwind has designed a remarkably robust language of class names that can cover just about any selector, property, or value you need. Inline styles can’t do pseudo-class selectors or media queries, but Tailwind can:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;dark:hover:bg-sky-500/25&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-color-scheme</span>: dark) { <span class="hljs-selector-class">.dark</span>\<span class="hljs-selector-pseudo">:hover</span>\:bg-sky-<span class="hljs-number">500</span>\/<span class="hljs-number">25</span>:hover { <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">14</span> <span class="hljs-number">165</span> <span class="hljs-number">233</span> / <span class="hljs-number">0.25</span>); } } </code></pre> <p>In a project that’s a good fit for Tailwind (see above), this virtually eliminates the need to write CSS code that would mostly contain selectors that match a single element – an unnecessary abstraction.</p> <div class="heading-wrapper"> <h2 id="styling-shared-components" tabindex="-1">Styling shared components</h2> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#styling-shared-components"><span class="sr-only">Permalink to Styling shared components</span> <span aria-hidden="true">#</span></a></div> <p>For the purposes of this article, shared components are user interface elements that are used in more than one web application. In Culture Amp’s case this includes the React components in our <a href="https://cultureamp.design/">Kaizen design system</a>.</p> <p>When you want to use Tailwind to style both an application’s non-shared components, and the shared components that application uses, you have a decision to make: <strong>Will you publish your shared components with compiled CSS, or do you expect the application’s build to run the Tailwind compiler over the shared components’ source code to generate those styles as well?</strong></p> <p>For many years, Culture Amp took the second option, and distributed shared components without compiled CSS. This meant that every app that consumed shared components needed to include the necessary CSS build tooling – at that time <a href="https://github.com/css-modules/css-modules">CSS Modules</a> and <a href="https://github.com/sass/node-sass">node-sass</a> – with a compatible version and configuration. This was relatively easy to set up, but over time proved difficult to maintain. When node-sass was deprecated in favour of (the much faster but slightly incompatible) <a href="https://sass-lang.com/dart-sass/">Dart Sass</a>, this demanded a difficult lock-step migration across all those codebases, which we have yet to achieve. And as new applications have switched to Tailwind for their own styles, they’ve had to continue to maintain those old build tools in parallel for the shared components’ styles.</p> <p>To avoid this coupling between the source code of shared components and the build tooling of the applications that consume them, we now want to go the other way: have our shared components build their styles in their own build pipelines and publish components with plain CSS. That way, the CSS build tooling of our applications can stay decoupled from the CSS build tooling of our component libraries: a Sass-styled application can consume a Tailwind-styled component without having to run Tailwind, and a Tailwind-styled application can consume a Sass-styled component without having to run Sass.</p> <p>But what happens when a Tailwind-styled application consumes Tailwind-styled components? <strong>How do the Tailwind-generated styles of shared components co-exist with the Tailwind-generated styles of the application?</strong></p> <div class="heading-wrapper"> <h2 id="the-naive-approach" tabindex="-1">The naive approach</h2> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#the-naive-approach"><span class="sr-only">Permalink to The naive approach</span> <span aria-hidden="true">#</span></a></div> <p>At first, it would seem that it’s safe to combine two Tailwind-built stylesheets into one, if slightly inefficient. Let’s say your component uses <code>m-0</code>:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p>And then your application also uses <code>m-0</code>:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p>When you use the shared component in your application, you get something like this:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p><code>.m-0</code> is defined twice in your stylesheet, which is the slightly wasteful part, but the two rules apply the exact same styles, so everything still works.</p> <p>While the duplicate CSS output is unfortunate, Tailwind-generated stylesheets tend to be incredibly small compared to hand-coded CSS, so this bloat is likely negligible. Almost every element in the document that needs its margin set to zero has that done with this one (duplicated) CSS rule, rather than traditional CSS where many, <em>many</em> different rules might specify <code>margin: 0px</code> separately. If you <em>really</em> wanted to avoid this duplicate output, a PostCSS plug-in like <a href="https://www.npmjs.com/package/postcss-discard-duplicates">postcss-discard-duplicates</a> could do this for you.</p> <div class="heading-wrapper"> <h2 id="the-small-problem%3A-configuration-coupling" tabindex="-1">The small problem: Configuration coupling</h2> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#the-small-problem%3A-configuration-coupling"><span class="sr-only">Permalink to The small problem: Configuration coupling</span> <span aria-hidden="true">#</span></a></div> <p>A much bigger downside than the output duplication, however, is the coupling this creates between your Tailwind configurations. If someday you upgrade or alter the configuration of Tailwind in either project (your shared components or your application), and the two outputs do not match, one set of styles will overwrite the other.</p> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.bg-sky-500</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#0ea5e9</span>; <span class="hljs-comment">/* new value */</span> } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.bg-sky-500</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#87cefa</span>; <span class="hljs-comment">/* old value */</span> } </code></pre> <p>In this example, because our combined CSS output includes the shared components’ CSS first and the application’s CSS second, the application styles “win” (because the CSS cascade applies rules of equal specificity in source code order).</p> <p>Adding to the confusion is the fact that Tailwind, remember, only generates styles for class names that you use, so if your application uses some of the classes that your shared components use but not others, you can end up with an unholy mix of the two configurations applied to your application:</p> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.bg-red-500</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ef4444</span>; <span class="hljs-comment">/* new value */</span> } <span class="hljs-selector-class">.bg-sky-500</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#0ea5e9</span>; <span class="hljs-comment">/* new value */</span> } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.bg-sky-500</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#87cefa</span>; <span class="hljs-comment">/* old value */</span> } </code></pre> <p>In the above example, your shared components will display with the old value for <code>bg-sky-500</code>, but the new value for <code>bg-red-500</code>, because that second background colour isn’t used in the application’s source code!</p> <p>You can of course flip the order in which your two stylesheets are combined, putting the application’s styles first:</p> <pre><code class="language-css"><span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.bg-sky-500</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#87cefa</span>; <span class="hljs-comment">/* old value */</span> } <span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.bg-red-500</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ef4444</span>; <span class="hljs-comment">/* new value */</span> } <span class="hljs-selector-class">.bg-sky-500</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#0ea5e9</span>; <span class="hljs-comment">/* new value */</span> } </code></pre> <p>…but this merely reverses the problem: now your application is being unexpectedly re-styled by the Tailwind configuration used to build your shared components’ styles, except for any classes that are used only in your application, which will remain styled by the old configuration.</p> <p>In short, if you want your styles to be applied consistently, you need to avoid breaking changes to your Tailwind configuration, or update the two packages in lock-step. This by itself may not be a dealbreaker if you expect your Tailwind configuration to be relatively stable. Tailwind itself is pretty careful about releasing breaking changes, so it’s unlikely that having slightly different Tailwind versions in the shared components and the application will cause problems <em>most</em> of the time.</p> <p>But it’s hard to ignore the fact that we’ve ended up back in a situation where our applications are forced to match their build configuration to that assumed by our shared components.</p> <div class="heading-wrapper"> <h2 id="the-big-problem%3A-tailwind-depends-on-source-order" tabindex="-1">The big problem: Tailwind depends on source order</h2> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#the-big-problem%3A-tailwind-depends-on-source-order"><span class="sr-only">Permalink to The big problem: Tailwind depends on source order</span> <span aria-hidden="true">#</span></a></div> <p>There’s actually a more subtle (and fatal) issue lurking here: Tailwind is designed with the assumption that it controls the source order of the rules it generates.</p> <p>Let’s consider again our zero-margin <code>div</code>:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p><code>margin</code> is a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties">shorthand property</a>, which means it has the effect of setting <code>margin-block-start</code> (top), <code>margin-inline-end</code> (right), <code>margin-block-end</code> (bottom), and <code>margin-inline-start</code> (left).</p> <p>In traditional CSS, you can follow a shorthand property like this with any specific properties you want to override, like adding a left margin:</p> <pre><code class="language-css"><span class="hljs-selector-class">.bottom-margin-only</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } </code></pre> <p>The order of the property declarations in the above is significant: if you swapped them so that <code>margin-block-end</code> was set first, the <code>margin</code> declaration would override it.</p> <p>In Tailwind, you can likewise override a shorthand class like <code>m-0</code> with a specific class like <code>ms-4</code>:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } </code></pre> <p>But the critical thing to observe here is that <strong>the order of the class names in the HTML is not significant: only the order of the generated CSS rules is.</strong></p> <p>If we swapped the order of the class names in the HTML:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <p>…the left margin would still override the <code>m-0</code>, because Tailwind still generates the two CSS rules in the order that ensures the more fine-grained style (the left margin) is applied after the more coarse-grained style (the margin on all four sides):</p> <pre><code class="language-css"><span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } </code></pre> <p>Now, take this observation that the source order of rules generated by Tailwind is a critical feature of its design, and combine that with the issue we identified above of shared component styles clobbering application styles (or vice versa):</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; <span class="hljs-comment">/* ❌ overides .ms-4 */</span> } </code></pre> <p>In this example, our shared components use <code>m-0</code> and <code>ms-4</code>, but our application only uses <code>m-0</code>. The application’s <code>m-0</code> rule will override the value of <code>margin-inline-start</code> set by the <code>ms-4</code> rule, and break the shared component’s left margin!</p> <p>Once again, you can solve this specific instance by swapping the order of the two generated stylesheets, but you’ll just end up with the opposite problem: shared component styles interfering with application styles, in this example when the margin is on an application element.</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } <span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; <span class="hljs-comment">/* ❌ overides .ms-4 */</span> } </code></pre> <p>If you’re at all tempted to try doing without shorthand styles (e.g. avoid <code>m-0</code> in favour of <code>mt-0 me-0 mb-0 ms-0</code>), Tailwind also depends on source order for modifiers like media queries:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 md:m-4&quot;</span>&gt;</span>This is my application<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-keyword">@media</span> (<span class="hljs-attribute">min-width</span>: <span class="hljs-number">768px</span>) { <span class="hljs-selector-class">.md</span>\:m-<span class="hljs-number">4</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">1rem</span>; } } <span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; <span class="hljs-comment">/* ❌ overides .md:m-4 */</span> } </code></pre> <p>Here we’re saying the first <code>div</code> should have a margin of zero by default, but on medium-sized screens or larger it should have a <code>1rem</code> margin. These two rules have equal specificity (one class selector), so the fact that the media query style overrides the default style depends on source order. A second <code>.m-0</code> rule at the end of the CSS from the shared component clobbers this margin.</p> <p>Modifiers (<code>dark:</code>, <code>hover:</code>, etc.) are a core feature of Tailwind’s styling language; there’s no avoiding them, and as we’ve seen, they are broken by combining multiple Tailwind builds in a single CSS stylesheet.</p> <p>How do we fix this?</p> <div class="heading-wrapper"> <h2 id="seductive-non-solutions" tabindex="-1">Seductive non-solutions</h2> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#seductive-non-solutions"><span class="sr-only">Permalink to Seductive non-solutions</span> <span aria-hidden="true">#</span></a></div> <p>There are a few approaches to solving this that seem like they could work nicely until you think them through. We’ll look at each briefly to explain why they don’t work.</p> <div class="heading-wrapper"> <h3 id="!important" tabindex="-1"><code>!important</code></h3> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#!important"><span class="sr-only">Permalink to !important</span> <span aria-hidden="true">#</span></a></div> <p>Out of the box, Tailwind lets you generate styles with <code>!important</code> that override competing styles in the cascade. <code>class=&quot;!ms-4&quot;</code>, for example, will output this:</p> <pre><code class="language-css">.\!ms-<span class="hljs-number">4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span> <span class="hljs-meta">!important</span>; } </code></pre> <p>First of all, <code>!important</code> is a very sharp knife, and is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/important#best_practices">best avoided</a>. For example, it interferes with inline styles applied to elements by JavaScript. But even if we ignored everything that’s bad about <code>!important</code>, it doesn’t actually solve the problem!</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 !ms-4&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } .\!ms-<span class="hljs-number">4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span> <span class="hljs-meta">!important</span>; } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p>Yes, this prevents the application’s <code>m-0</code> rule from overriding the component’s <code>ms-4</code> rule. But to do this we had to modify our shared component with knowledge of the application that would be consuming it, which is an unhealthy coupling to internal implementation details that would be far from practical to maintain in a real-world ecosystem of shared components and applications.</p> <p>What if we made <em>all</em> our component styles important? Tailwind even offers <a href="https://tailwindcss.com/docs/configuration#important">a configuration option for this</a>:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span> <span class="hljs-meta">!important</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span> <span class="hljs-meta">!important</span>; } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p>Well, this is just equivalent to putting our shared component styles at the end of the stylesheet: as already described above, the shared component styles will end up clobbering application styles in different circumstances:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span> <span class="hljs-meta">!important</span>; <span class="hljs-comment">/* ❌ overrides .ms-4 */</span> } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } </code></pre> <div class="heading-wrapper"> <h3 id="boost-specificity-with-important" tabindex="-1">Boost specificity with <code>important</code></h3> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#boost-specificity-with-important"><span class="sr-only">Permalink to Boost specificity with important</span> <span aria-hidden="true">#</span></a></div> <p>Tailwind’s <code>important</code> configuration option also lets you <a href="https://tailwindcss.com/docs/configuration#selector-strategy">specify a selector</a> for a top-level container element (like <code>body</code> or <code>#app</code>) to boost the specificity of the selectors it generates beyond the usual one-class specificity.</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-tag">body</span> <span class="hljs-selector-pseudo">:is</span>(<span class="hljs-selector-class">.m-0</span>) { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-tag">body</span> <span class="hljs-selector-pseudo">:is</span>(<span class="hljs-selector-class">.ms-4</span>) { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p>Here, setting <code>important</code> to <code>'body'</code> again appears to fix the problem because the shared component styles now consistently override the application styles. But just like with <code>!important</code> above, this only has the same effect as putting the component styles at the end of the stylesheet: shared component styles will clobber application styles instead.</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-tag">body</span> <span class="hljs-selector-pseudo">:is</span>(<span class="hljs-selector-class">.m-0</span>) { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; <span class="hljs-comment">/* ❌ overrides .ms-4 */</span> } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } </code></pre> <div class="heading-wrapper"> <h3 id="css-cascade-layers" tabindex="-1">CSS cascade layers</h3> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#css-cascade-layers"><span class="sr-only">Permalink to CSS cascade layers</span> <span aria-hidden="true">#</span></a></div> <p>A relatively new but now widely-supported addition to browsers, the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@layer"><code>@layer</code> CSS at-rule</a> lets you create groups of CSS rules and control the order in which they are applied to the page. So you could specify that your shared component styles be applied after your application styles:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-keyword">@layer</span> application-styles, component-styles; <span class="hljs-keyword">@layer</span> component-styles { <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } } <span class="hljs-keyword">@layer</span> application-styles { <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } } </code></pre> <p>But once again – you guessed it – this is effectively just like moving the component styles to the bottom of the stylesheet: it creates the opposite problem of component styles interfering with application styles.</p> <div class="heading-wrapper"> <h3 id="discard-duplicates" tabindex="-1">Discard duplicates</h3> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#discard-duplicates"><span class="sr-only">Permalink to Discard duplicates</span> <span aria-hidden="true">#</span></a></div> <p>Remember <a href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#the-naive-approach">at the start of this article</a> when we noted that our shared components and our application could generate the same CSS rule, and that this would bloat our CSS output unnecessarily? I mentioned <a href="https://www.npmjs.com/package/postcss-discard-duplicates">postcss-discard-duplicates</a> could get rid of those duplicate rules.</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } <span class="hljs-comment">/* application styles */</span> </code></pre> <p>This looks promising, because the problematic style has entirely disappeared. But there is still a source order issue here, because the style that survives is the style that appears first in the source order, and that isn’t always right.</p> <p>With a slightly tweaked example where the duplicate style is the fine-grained style (<code>ms-4</code>), we run into the same problem:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-2 ms-4&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0 ms-4&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-2</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0.5rem</span>; <span class="hljs-comment">/* ❌ overrides .ms-4 */</span> } </code></pre> <div class="heading-wrapper"> <h2 id="an-opportunity%3A-intelligent-tailwind-output-merging" tabindex="-1">An opportunity: intelligent Tailwind output merging</h2> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#an-opportunity%3A-intelligent-tailwind-output-merging"><span class="sr-only">Permalink to An opportunity: intelligent Tailwind output merging</span> <span aria-hidden="true">#</span></a></div> <p>The fundamental problem we face is that two Tailwind builds that use the same global namespace for their class names will inevitably interfere with each other because Tailwind is unable to place the generated CSS rules in the source order required for them the override each other in the correct sequence.</p> <p>But what if we could?</p> <p>It’s not as if the correct order for the rules is mysterious. It’s implemented in the Tailwind compiler. Tools like <a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss">prettier-plugin-tailwindcss</a>, which automatically sorts the class names in your HTML code to match the order in which Tailwind generates them in your CSS output, use <a href="https://github.com/tailwindlabs/tailwindcss/blob/0848e4ca26c0869a90818adb7337b5a463be38d0/src/lib/setupContextUtils.js#L951-L990">a public API</a> in Tailwind to get this order.</p> <p>So what if we wrote a PostCSS plug-in that would take the output of two Tailwind builds and merge them together, removing duplicate styles and sorting the remaining styles into the correct order?</p> <p>This seems like it could work, might even be a relatively straightforward project, and could even be worth Tailwind considering as a core feature. I’ve started <a href="https://github.com/tailwindlabs/tailwindcss/discussions/12801">a discussion about this</a> on the Tailwind CSS GitHub project.</p> <p>Even if we implemented this, we would still need to accept that our two Tailwind builds would need to have compatible versions and configurations for their merged outputs to work reliably.</p> <div class="heading-wrapper"> <h2 id="the-actual-solution%3A-prefix" tabindex="-1">The actual solution: <code>prefix</code></h2> <a class="heading-anchor" href="https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/#the-actual-solution%3A-prefix"><span class="sr-only">Permalink to The actual solution: prefix</span> <span aria-hidden="true">#</span></a></div> <p>As I mentioned in the previous section, the fundamental problem we face here is that Tailwind generates styles in a global namespace, and that two Tailwind builds therefore conflict in that namespace.</p> <p>What if we could give our Tailwind builds separate namespaces? Our shared components would receive styles only from their generated CSS output, and our application elements would receive styles only from the application’s Tailwind build output.</p> <p>This is what Tailwind’s <a href="https://tailwindcss.com/docs/configuration#prefix"><code>prefix</code> option</a> is for. It lets you specify a short string of characters that you add to the start of all your Tailwind class names, to distinguish them from styles they need to coexist with (which in this case is another Tailwind-generated stylesheet).</p> <p>For example, we could configure our Kaizen component library with a Tailwind prefix of <code>kz-</code>, and get this:</p> <pre><code class="language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;m-0&quot;</span>&gt;</span>This is my application.<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;kz-m-0 kz-ms-4&quot;</span>&gt;</span>This is a shared component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> </code></pre> <pre><code class="language-css"><span class="hljs-comment">/* shared component styles */</span> <span class="hljs-selector-class">.kz-m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } <span class="hljs-selector-class">.kz-ms-4</span> { <span class="hljs-attribute">margin-inline-start</span>: <span class="hljs-number">1rem</span>; } <span class="hljs-comment">/* application styles */</span> <span class="hljs-selector-class">.m-0</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0px</span>; } </code></pre> <p>The two elements’ styles are completely separate in the CSS, because they use two completely different namespaces! You could swap the order of the two stylesheets, and it would make no difference. (We put our application styles last, because our design system components allow you to pass in class names to override their built-in styles, so those application class names need to come last in the stylesheet.)</p> <p>This is such a clean and complete solution to this problem that you’re probably wondering why this isn’t a much shorter article.</p> <p>The thing is, for those who have become used to Tailwind, the extreme terseness of its class names is one of its best features. It vastly reduces the number of keystrokes required for most styling tasks. Strings like <code>m-0</code> become burned into your muscle memory, something you can type almost without thinking.</p> <p>So if you tell an engineer who loves Tailwind that they need to remember to add three extra characters (<code>kz-</code>) to the start of all their class names when they’re working in a library, and that different libraries will have different prefixes, you’re probably going to ruin their day. And someone will have to make the very unpopular decision of who gets to have the default (unprefixed) namespace: your application codebases or your design system’s component library. Avoiding this pain is why we tried everything else we could first.</p> <p>But this really is the cleanest solution to the problem of enabling different Tailwind builds/versions/configurations to coexist in a single web page, and at Culture Amp we’ve only just learned to accept that.</p> Mon, 22 Jan 2024 02:22:54 +0000 https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/ https://kevinyank.com/posts/use-tailwindcss-prefixes-for-shared-design-system-components/