<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Callum Flack</title>
    <description>The latest articles on DEV Community by Callum Flack (@callumflack).</description>
    <link>https://dev.to/callumflack</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F409909%2Ff8729e88-3fa4-4776-9ce5-3db1a0f65c80.jpg</url>
      <title>DEV Community: Callum Flack</title>
      <link>https://dev.to/callumflack</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/callumflack"/>
    <language>en</language>
    <item>
      <title>Configuring Tailwind as a Design System</title>
      <dc:creator>Callum Flack</dc:creator>
      <pubDate>Thu, 19 Sep 2024 05:56:51 +0000</pubDate>
      <link>https://dev.to/callumflack/configuring-tailwind-as-a-design-system-2f5h</link>
      <guid>https://dev.to/callumflack/configuring-tailwind-as-a-design-system-2f5h</guid>
      <description>&lt;p&gt;For design systems, consistency and comprehension are everything. A good design system ensures consistency of implementation through the configuration of code that implements it. It needs to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easy to comprehend without foregoing the nuance that good design requires;&lt;/li&gt;
&lt;li&gt;scalable and maintainable without compromising consistency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using my default stack of &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt; with &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, I'll show you how setting your own defaults for typography, colour and spacing is not just the starting point for differentiating your app's look and feel. More importantly, it drastically cuts down the code we have to write and maintain, which reduces the mental load of implementing styles in a systematic, consistent and error-free way.&lt;/p&gt;

&lt;p&gt;I'll start with a major criticism that I see all the time, and then breakdown a series of configuration steps that I use to solve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ease of use does not equate to ease of knowledge
&lt;/h2&gt;

&lt;p&gt;Tailwind makes it easy for developers to write styles, which is great for rapid prototyping. But that ease doesn't guarantee good design or a scalable, maintainable design system.&lt;/p&gt;

&lt;p&gt;Defaults and zero-config tools like Tailwind are the &lt;a href="https://jollycontrarian.com/index.php?title=Pace_layering" rel="noopener noreferrer"&gt;infrastructure pace layer&lt;/a&gt; that create more time for building. But if you're scaling an app that uses a design system to differentiate itself, you can't rely solely on "free as in lunch" out-of-the-box configs.&lt;/p&gt;

&lt;p&gt;If you run with the default Tailwind config and push style management to the application of classes on components, the result is often a mess of hard-to-reason-about classes spread across components, masquerading as a design system.&lt;/p&gt;

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

&lt;p&gt;Above is a prime example. It is almost illegible and takes signficant time to understand, let alone manipulate. Attempts to do so are highly likely to lead to duplication and error, spiralling away from design consistency throughout the app.&lt;/p&gt;

&lt;p&gt;It is easy to smush your design classes into a single className. But there is no ease of knowledge in doing so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure your system for ease of knowledge
&lt;/h2&gt;

&lt;p&gt;Ease of use comes with trade-offs. Using someone else's standard means relying on their knowhow. This can be beneficial, but it can also be a trap. Let's take a step back and think about what the basics of a design system consist of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;typography&lt;/li&gt;
&lt;li&gt;colour&lt;/li&gt;
&lt;li&gt;spacing&lt;/li&gt;
&lt;li&gt;responsiveness (which includes color mode)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the context of React with Tailwind, these and many other design system elements are set in the Tailwind config, which we can customise.&lt;/p&gt;

&lt;p&gt;{/* prettier-ignore */}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Typographic defaults
&lt;/h3&gt;

&lt;p&gt;Have you ever struggled to remember the correct letter-spacing for your small text? What if you could set it once and forget about it?&lt;/p&gt;

&lt;p&gt;We can set leading (line-height) and tracking (letter-spacing) as parameters for each font size tuple directly in &lt;code&gt;tailwind.config&lt;/code&gt;. This means we don't need to set leading or tracking when we use a font-size class. No need to remember (or fail to look up) what the letter-spacing of small text is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;small&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;13px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;letterSpacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.015em&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;16px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;letterSpacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;text-small&lt;/code&gt; now sets font-size, line-height and letter-spacing. Enclosing the core typographic tuple together in one class centralises the implementation of these values into the config instead of across a codebase. A huge win for maintainability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* 13px/1.5 with 0.015em letter-spacing */&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-small"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Color defaults
&lt;/h3&gt;

&lt;p&gt;We can use CSS variables to set responsive colours under &lt;code&gt;:root&lt;/code&gt; and &lt;code&gt;html.dark&lt;/code&gt; scopes. This means we write and manage one class, such as &lt;code&gt;bg-canvas&lt;/code&gt;, instead of two, such as &lt;code&gt;bg-gray-100 dark:bg-gray-800&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@radix-ui/colors/gray.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"@radix-ui/colors/gray-dark.css"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-gray-base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--color-gray-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--color-gray-line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--color-gray-border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--color-gray-solid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--color-gray-fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because I'm using &lt;a href="https://www.radix-ui.com/colors" rel="noopener noreferrer"&gt;Radix Colors&lt;/a&gt; here, I don't need to set the &lt;code&gt;.dark&lt;/code&gt; scope as that's &lt;a href="https://github.com/radix-ui/colors/blob/8a03dad3bc93ea4ed48ce2b70847a3538097e02f/scripts/build-css-modules.js#L19" rel="noopener noreferrer"&gt;already done for me&lt;/a&gt;. If you don't like the Radix colors, you can &lt;a href="https://www.radix-ui.com/colors/custom" rel="noopener noreferrer"&gt;customise them&lt;/a&gt;, use another library or write your own.&lt;/p&gt;

&lt;p&gt;Then set the CSS variables in the Tailwind config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--color-gray-base)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--color-gray-bg)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--color-gray-line)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--color-gray-border)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--color-gray-solid)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--color-gray-fill-contrast)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;bg-canvas&lt;/code&gt; now sets the appropriate color in light or dark mode. Removing this duplication across a codebase centralises color management to our config instead of spreading it across the implementation of classes on components. A huge win for cognitiion and maintainability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* sets --gray-1 as #fcfcfc on :root or #111111 on html.dark */&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-canvas"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Semantic naming
&lt;/h3&gt;

&lt;p&gt;I advocate semantic names for colours and font-sizes because semantic naming is a forcing function that ties meaning to use. Doing so removes implementation guess work and reduces error.&lt;/p&gt;

&lt;p&gt;I've seen countless projects where inconsistent &lt;code&gt;gray-50&lt;/code&gt;, &lt;code&gt;gray-100&lt;/code&gt; or &lt;code&gt;gray-200&lt;/code&gt; are all used for backgrounds. This is easily solved by defining a color called &lt;code&gt;background&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the same way, it is easier to remember the names for dark and light text colors when they are called &lt;code&gt;fill&lt;/code&gt; and &lt;code&gt;solid&lt;/code&gt;. It's harder and more error-prone when they're called &lt;code&gt;gray-900&lt;/code&gt; and &lt;code&gt;gray-600&lt;/code&gt; because then you have to remember specifically that it wasn't &lt;code&gt;gray-950&lt;/code&gt; and &lt;code&gt;gray-500&lt;/code&gt;, or &lt;code&gt;gray-800&lt;/code&gt; and &lt;code&gt;gray-700&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But naming things—and agreeing on naming—is hard. In the spirit of zero-config, I suggest taking &lt;a href="https://www.radix-ui.com/colors" rel="noopener noreferrer"&gt;Radix Color&lt;/a&gt;'s backgrounds, borders, solids &amp;amp; fills paradigm. Or &lt;a href="https://github.com/rainbow-me/rainbowkit/blob/d8c64ee4baf865d3452a6b92e0525c123f680ec1/site/css/tokens.ts#L120" rel="noopener noreferrer"&gt;this&lt;/a&gt; palette semantics.&lt;/p&gt;

&lt;p&gt;And once you've set this in &lt;code&gt;tailwind.config&lt;/code&gt;, Typescript will jog your memory at your fingertips with autocomplete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid namespace clashes
&lt;/h3&gt;

&lt;p&gt;If you're &lt;a href="https://tailwindcss.com/docs/plugins#extending-the-configuration" rel="noopener noreferrer"&gt;extending a Tailwind theme&lt;/a&gt; and not writing your own, don't use a scale key that's already been used. You may inadvertently overwrite a class that you need to use.&lt;/p&gt;

&lt;p&gt;You'll note in the previous colour config example that I set the &lt;code&gt;--color-gray-base&lt;/code&gt; var to &lt;code&gt;canvas&lt;/code&gt;, not &lt;code&gt;base&lt;/code&gt;. If I used &lt;code&gt;base&lt;/code&gt; then using this color scale as a text colour (&lt;code&gt;text-base&lt;/code&gt;) would clash with the &lt;a href="https://github.com/tailwindlabs/tailwindcss/blob/818d10ab8461e682a185475dd4718e741103a4e3/stubs/config.full.js#L327" rel="noopener noreferrer"&gt;default font-size base value&lt;/a&gt;, which is also &lt;code&gt;text-base&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This isn't a downfall of customising the Tailwind config, it's a legacy of its theme naming: setting font-size or color classes in Tailwind both use &lt;code&gt;text-*&lt;/code&gt;.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Spacing defaults
&lt;/h3&gt;

&lt;p&gt;We can also use CSS variables to set spacings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--height-nav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--height-tab&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;54px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--space-inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--container-text-px&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;660px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--container-hero-px&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;em&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1em&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* relate icon size to parent font-size */&lt;/span&gt;
  &lt;span class="nx"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--height-nav)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--space-inset)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--container-text)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;hero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--container-hero)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One could argue this is over-engineering. Except that when it comes time to compute complex interactive layouts like sticky headers, scroll margins and so on, this upfront configuration work makes it straight forward and error-free, to the pixel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"top-[calc(theme(spacing.nav)+theme(spacing.tab))]"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"scroll-mt-[calc(theme(spacing.nav)+theme(spacing.tab))]"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    /* ... */
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note again the use of semantic naming makes it easy to remember and use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Augmenting your Tailwind config
&lt;/h2&gt;

&lt;p&gt;We have now configured typography, colour and spacing tokens in a manner that is easy to understand and maintain in a single, centralised place. And we don't need to wrire as many classes to implement the system. Winning. And there's further steps we can take to reduce this implementation overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clamp() your classes
&lt;/h3&gt;

&lt;p&gt;What if I told you there's a way to completely avoid writing &lt;code&gt;text-lg lg:text-xl xl:text-2xl p-2 md:p-4 lg:p-8&lt;/code&gt; everywhere?&lt;/p&gt;

&lt;p&gt;We can avoid setting responsive font-size classes by using clamp as a a font-size value in &lt;code&gt;tailwind.config&lt;/code&gt;. Here's the &lt;a href="https://github.com/callumflack/callum-website/blob/3746ca4eef7eaae1663e39584bb75a35e732ccf1/packages/config-tailwind/generate-clamp-size.ts#L7" rel="noopener noreferrer"&gt;simple clamp function&lt;/a&gt; I use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="cm"&gt;/* clamp(17px, 14.1429px + 0.5714vw, 21px) */&lt;/span&gt;
    &lt;span class="nf"&gt;generateClampSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;letterSpacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-0.015em&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of writing &lt;code&gt;text-lg lg:text-xl xl:text-2xl&lt;/code&gt; we can just write &lt;code&gt;text-title&lt;/code&gt;. Once again, by hoisting font-size responsiveness into a clamp value, we avoid the "implement classes" pitfall again, saving mental effort, errors and debugging time.&lt;/p&gt;

&lt;p&gt;Keep in mind, this means we've moved from &lt;code&gt;text-lg lg:text-xl xl:text-2xl leading-none tracking-wide&lt;/code&gt; to &lt;code&gt;text-title&lt;/code&gt; by properly configuring Tailwind. Winning!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* 17px at 500px, 21px at 1200, fluidly calculated inbetween */&lt;/span&gt;
&lt;span class="cm"&gt;/* …with default line-height and letter-spacing also specified */&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;Heading&lt;/span&gt; &lt;span class="nx"&gt;copy&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also do this for spacing. When extending a theme, I prefix these keys with &lt;code&gt;d&lt;/code&gt; for "dynamic" to differentiate it from the default spacing scale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* lower value is 2/3 of upper value */&lt;/span&gt;
  &lt;span class="nl"&gt;d4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateClampSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;d8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateClampSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;d16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateClampSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;d24&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateClampSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;d64&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateClampSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;171&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to write &lt;code&gt;py-d24&lt;/code&gt; instead of &lt;code&gt;py-16 md:py-20 lg:py-24&lt;/code&gt;. This alleviates the weight of holding a range of website versions for each media-query in our minds. Instead it encourages us to picture fluidly responsive layouts where measurements don't matter as much as consistent relationships.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pt-d24 pb-d64 space-y-w8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;container max-w-hero space-y-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;container space-y-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/article&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Well-crafted UI is your last defense against the coming slopwave of careless AI apps. Here's how customizing Tailwind can save you time and headaches so you can focus on the irrational amount of care it takes to build UI that works in the blink of an eye:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;tailwind.config&lt;/code&gt; to its full potential. Centralize and group your design tokens and avoid the "implement classes everywhere" trap.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;clamp()&lt;/code&gt; for fluid typography and spacing.&lt;/li&gt;
&lt;li&gt;Set color variables on &lt;code&gt;:root&lt;/code&gt; and &lt;code&gt;.dark&lt;/code&gt; for effortless dark mode.&lt;/li&gt;
&lt;li&gt;Name colors and spacing semantically: &lt;code&gt;background&lt;/code&gt; beats &lt;code&gt;gray-100&lt;/code&gt; any day.&lt;/li&gt;
&lt;li&gt;Relate icons to text size with &lt;code&gt;size-em&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, there's an upfront time cost. But it pays off in spades: less code, fewer errors, greater design consistency, and a team that actually understands the system.&lt;/p&gt;

&lt;p&gt;Next up: We'll explore how to use &lt;a href="https://cva.style/docs" rel="noopener noreferrer"&gt;Class Variance Authority&lt;/a&gt; to create a bulletproof styling API with semantic props drawn from Tailwind. Stay tuned.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;This is also why I dislike using &lt;a href="https://github.com/dcastil/tailwind-merge" rel="noopener noreferrer"&gt;tailwind-merge&lt;/a&gt; to remove duplicate Tailwind classes in JSX. More often than not, I find it removing a &lt;code&gt;text-color&lt;/code&gt; in favour of a &lt;code&gt;text-fontSize&lt;/code&gt; when both are needed. I'm surprised more developers don't raise this issue. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>css</category>
      <category>tailwindcss</category>
      <category>designsystem</category>
    </item>
  </channel>
</rss>
