Kevin YankKevin Yank’s website
https://kevinyank.com
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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"dark:hover:bg-sky-500/25"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 md:m-4"</span>></span>This is my application<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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="!ms-4"</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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 !ms-4"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-2 ms-4"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0 ms-4"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"m-0"</span>></span>This is my application.<span class="hljs-tag"></<span class="hljs-name">div</span>></span>
<span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"kz-m-0 kz-ms-4"</span>></span>This is a shared component<span class="hljs-tag"></<span class="hljs-name">div</span>></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/Subspace Radio #50: Star Trek V commentary<p>In <a href="https://www.subspace.fm/episodes/episode-50-star-trek-v-commentary">Subspace Radio #50</a>, to celebrate our fiftieth episode, we break from our usual format to record an audio commentary for <a href="https://memory-alpha.fandom.com/wiki/Star_Trek_V:_The_Final_Frontier">Star Trek V: The Final Frontier</a>, arguably the least successful Star Trek film, but not without its charms (and an especially charming Vulcan!).</p>
<p>We’ll return with more Subspace Radio when new Trek returns to our screens in April for Star Trek: Discovery’s final season. Until then, see you 'round the galaxy!</p>
<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/f5a5b93e"></iframe>
Mon, 08 Jan 2024 03:22:51 +0000
https://kevinyank.com/posts/star-trek-v-commentary/
https://kevinyank.com/posts/star-trek-v-commentary/Subspace Radio #49: Barriers in space<p>In <a href="https://www.subspace.fm/episodes/episode-49-barriers-in-space-ld-4x10-old-friends-new-planets">Subspace Radio #49</a>, Rob and I approach the infinitely wide, unfathomably tall, impenetrable barrier that is the end of Star Trek: Lower Decks, Season Four, “Old Friends, New Planets”. Among the many tangents we explore this episode, we focus in on other barriers in space, including “Where No Man Has Gone Before” (TOS), “The Tholian Web” (TOS), “Encounter at Farpoint” (TNG), and “The Galactic Barrier” (DIS). Stay after the credits for a brief debrief on “Star Trek V: The Final Frontier”.</p>
<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/f299d149"></iframe>
Thu, 14 Dec 2023 12:53:16 +0000
https://kevinyank.com/posts/subspace-radio-49-barriers-in-space/
https://kevinyank.com/posts/subspace-radio-49-barriers-in-space/Help! Storybook is eating all our tests!<p>Storybook is a visual workshop for developing, composing and documenting UI components in isolation. For many years it’s been a favoured tool by Design System teams. But in recent years, Storybook has sneakily grown into a very powerful and versatile automated testing tool as well!</p>
<p>In this talk, I show how Storybook has disrupted the traditional Test Pyramid at Culture Amp and enabled us to test much more with much less code and effort than ever before.</p>
<p>I had a lot of fun giving this talk at MelbJS + React Melbourne’s end-of-year mega meetup at Kogan’s office in South Melbourne yesterday. Unfortunately the stream recording is not available, so I’ve recorded this slightly less interactive version for posterity.</p>
<figure class="relative w-full mbe-12 pbe-[56.25%]">
<iframe class="absolute inset-0 h-full w-full" src="https://www.youtube-nocookie.com/embed/sKhv_0Z5ltg?rel=0&showinfo=0" frameborder="0" allowfullscreen=""></iframe>
<figcaption class="absolute text-center text-sm font-light is-full pbs-4 block-start-full">
Video of the talk
</figcaption>
</figure>
<p>I’m quite proud of this talk, and would love the opportunity to give it in front of another audience, so please reach out if there’s a speaking opportunity that this talk might be suited for!</p>
Thu, 14 Dec 2023 12:35:42 +0000
https://kevinyank.com/posts/help-storybook-is-eating-all-our-tests/
https://kevinyank.com/posts/help-storybook-is-eating-all-our-tests/Subspace Radio #48: Aliens working together<p>In <a href="https://www.subspace.fm/episodes/episode-48-aliens-working-together-ld-4x09-the-inner-fight">Subspace Radio #48</a>, Rob and I fend off our inner demons, take shelter from a patch of knife rain, and watch “The Inner Fight”, the penultimate episode of season four of Star Trek: Lower Decks. Inspired by Mariner’s ability to unite the clashing aliens trapped on Sherbal V, we discuss other conspicuous occasions of multi-species alliances in Star Trek, including “The Time Trap” (TAS), the Dominion War (DS9), “The Enemy” (TNG), “Allegiance” (TNG), and the production design of sets and locations like the Promenade (DS9).</p>
<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/f299d149"></iframe>
Wed, 08 Nov 2023 12:53:23 +0000
https://kevinyank.com/posts/subspace-radio-48-aliens-working-together/
https://kevinyank.com/posts/subspace-radio-48-aliens-working-together/Subspace Radio #47: Cave episodes<p>In <a href="https://www.subspace.fm/episodes/episode-47-cave-episodes-ld-4x08-caves">Subspace Radio #47</a>, Rob and I get stuck in a cave and are forced to pass the time awaiting rescue discussing “Caves”. As our supplies dwindle, we plumb the hidden depths of other cave-based episodes from Star Trek history, including “The Devil in the Dark” (TOS), “Heart of Stone” (DS9), “Final Mission” (TNG), and “Waltz” (DS9).</p>
<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/6a8beba6"></iframe>
Thu, 26 Oct 2023 11:40:08 +0000
https://kevinyank.com/posts/subspace-radio-47-cave-episodes/
https://kevinyank.com/posts/subspace-radio-47-cave-episodes/Subspace Radio #46: Evil Computers & AIs<p>In <a href="https://www.subspace.fm/episodes/episode-46-evil-computers-ais-ld-4x07-a-few-badgeys-more">Subspace Radio #46</a>, Rob and I consult our robotic overlord for its opinion on “A Few Badgeys More”, before discussing other episodes where the forces of evil aligned against our heroes were artificial in nature: “The Return of the Archons” (TOS), “Star Trek: The Motion Picture”, “The Ultimate Computer” (TOS) and “Prototype” (VOY). Stay after the music for our thoughts on Prodigy’s new lease on life on Netflix!</p>
<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/3860573a"></iframe>
Sun, 22 Oct 2023 03:08:21 +0000
https://kevinyank.com/posts/subspace-radio-46-evil-computers-and-ais/
https://kevinyank.com/posts/subspace-radio-46-evil-computers-and-ais/Subspace Radio #45: Best Ferengi episodes<p>In <a href="https://www.subspace.fm/episodes/episode-45-best-ferengi-episodes-ld-4x06-parth-ferengis-heart-place">Subspace Radio #45</a>, Rob and I back up our truck of gold pressed latinum to Uncle Quark’s Youth Casino in “Parth Ferengi’s Heart Place”, then stay for the haggling at the Museum of Gambling (and the gambling at the Museum of Haggling), and compare our picks for the best Ferengi-centric episode, “Ménage à Troi” (TNG) and “The Magnificent Ferengi” (DS9). Also stick around at the end for our thoughts on the recently-completed run of Very Short Treks!</p>
<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/723c5561"></iframe>
Sun, 15 Oct 2023 07:01:44 +0000
https://kevinyank.com/posts/subspace-radio-45-best-ferengi-episodes/
https://kevinyank.com/posts/subspace-radio-45-best-ferengi-episodes/Subspace Radio #44: Crews going crazy!<p>In <a href="https://www.subspace.fm/episodes/episode-44-crews-going-crazy-ld4x05-empathological-fallacies">Subspace Radio #44</a>, Rob and I lose control of our emotions and, after breaking down the highlights of “Empathological Fallacies”, cannot resist our powerful urges to revisit two classic episodes of The Original Series, “The Naked Time” (TOS) and “Day of the Dove” (TOS).</p>
<iframe width="100%" height="180" frameborder="no" scrolling="no" seamless="" src="https://share.transistor.fm/e/d03c9f81"></iframe>
Tue, 10 Oct 2023 11:23:54 +0000
https://kevinyank.com/posts/subspace-radio-44-crews-going-crazy/
https://kevinyank.com/posts/subspace-radio-44-crews-going-crazy/Weird Mac networking glitch<p>For months, I’ve been unable to get to the bottom of a networking issue that only seems to affect my newest Mac, an M2 MacBook Pro. Immediately after connecting to my home network, it is unable to connect to my Synology NAS server … until suddenly it is.</p>
<p>I often notice this because macOS can’t mount any SMB shares from the server, nor will Safari let me connect to its web UI. I’ll then head to the terminal to try to troubleshoot the issue. Here’s what that looks like:</p>
<pre><code class="language-console"><span class="hljs-meta prompt_">> </span><span class="language-bash">ping 192.168.0.40</span>
PING 192.168.0.40 (192.168.0.40): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
^C
--- 192.168.0.40 ping statistics ---
3 packets transmitted, 0 packets received, 100.0% packet loss
<span class="hljs-meta prompt_">
> </span><span class="language-bash">ssh 192.168.0.40</span>
Using terminal commands to modify system configs, execute external binary
files, add files, or install unauthorized third-party apps may lead to system
damages or unexpected behavior, or cause data loss. Make sure you are aware of
the consequences of each command and proceed at your own risk.
Warning: Data should only be stored in shared folders. Data stored elsewhere
may be deleted when the system is updated/restarted.
<span class="hljs-meta prompt_">
kyank@diskstation> </span><span class="language-bash"><span class="hljs-built_in">exit</span></span>
Connection to 192.168.0.40 closed.
<span class="hljs-meta prompt_">
> </span><span class="language-bash">ping 192.168.0.40</span>
PING 192.168.0.40 (192.168.0.40): 56 data bytes
64 bytes from 192.168.0.40: icmp_seq=0 ttl=64 time=2.974 ms
64 bytes from 192.168.0.40: icmp_seq=1 ttl=64 time=1.976 ms
64 bytes from 192.168.0.40: icmp_seq=2 ttl=64 time=2.005 ms
^C
--- 192.168.0.40 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 1.976/2.505/3.063/0.515 ms
</code></pre>
<p>I can’t tell if it’s something that I’m doing that’s “unblocking” the connection (in this case, connecting to it via SSH), or if it’s just a problem that self-corrects after a time.</p>
<p>My older, M1 MacBook Air and other devices (iPhone, iPad, etc.) seem unaffected.</p>
<p>It’s a strange quirk of modern computing that we don’t really have any place to go with home networking issues like this. If I contacted Apple support, they’d say they don’t support non-Apple network devices. If I contacted Synology support, they’d say they don’t support Apple computers.</p>
<p>I’d love to hear from anyone with an idea of where the problem might be!</p>
Sun, 08 Oct 2023 03:09:15 +0000
https://kevinyank.com/posts/weird-mac-networking-glitch/
https://kevinyank.com/posts/weird-mac-networking-glitch/