<?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: Caio Rodrigues</title>
    <description>The latest articles on DEV Community by Caio Rodrigues (@babyreptile).</description>
    <link>https://dev.to/babyreptile</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1101553%2F58a0b451-a915-4d6d-a181-209a41940d87.png</url>
      <title>DEV Community: Caio Rodrigues</title>
      <link>https://dev.to/babyreptile</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/babyreptile"/>
    <language>en</language>
    <item>
      <title>responsive-tailwind: The Utility That Refuses To Build Your Class Names</title>
      <dc:creator>Caio Rodrigues</dc:creator>
      <pubDate>Fri, 07 Aug 2026 14:12:42 +0000</pubDate>
      <link>https://dev.to/babyreptile/responsive-tailwind-the-utility-that-refuses-to-build-your-class-names-1hlj</link>
      <guid>https://dev.to/babyreptile/responsive-tailwind-the-utility-that-refuses-to-build-your-class-names-1hlj</guid>
      <description>&lt;p&gt;&lt;em&gt;Compile-time breakpoint validation, and the redundancy that makes it work&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every time someone sees the API for the first time, I get the same reaction:&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="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex flex-col gap-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md:flex-row&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;"Why am I typing &lt;code&gt;md&lt;/code&gt; twice?"&lt;/p&gt;

&lt;p&gt;Fair question. The obvious version of this library writes the prefix for you: you pass &lt;code&gt;md: "flex-row"&lt;/code&gt;, it returns &lt;code&gt;"md:flex-row"&lt;/code&gt;, everyone goes home happy. I didn't build that, and not because I ran out of time. That version is broken by design, and the redundancy you're looking at is the entire reason this thing exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailwind Never Runs Your Code
&lt;/h2&gt;

&lt;p&gt;This is the fact everything else hangs off, and it's the one people forget most often.&lt;/p&gt;

&lt;p&gt;Tailwind doesn't analyze your program. It doesn't have a type checker, it doesn't evaluate expressions, it doesn't know what your functions return. At build time it reads your source files as &lt;strong&gt;plain text&lt;/strong&gt; and looks for things that look like class names. Whatever it finds, it generates CSS for. Whatever it doesn't find, doesn't exist.&lt;/p&gt;

&lt;p&gt;Which means this compiles, runs, passes review, and produces nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:flex-row`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Tailwind scanned your file and saw "${bp}:flex-row".&lt;/span&gt;
&lt;span class="c1"&gt;// No CSS rule was generated. The class is in the DOM, styling nothing.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You already know this rule. You've probably hit it with dynamic color classes (&lt;code&gt;`bg-${color}-500`&lt;/code&gt;) and learned to write the full string out. But the moment someone hands you a &lt;em&gt;helper&lt;/em&gt; that builds class names, the rule quietly stops applying — not because the helper is smarter, but because the string construction moved one file away where you can't see it. A helper that turns &lt;code&gt;md: "flex-row"&lt;/code&gt; into &lt;code&gt;"md:flex-row"&lt;/code&gt; at runtime has the exact same problem as the template literal above. Tailwind never sees &lt;code&gt;md:flex-row&lt;/code&gt; anywhere in your source, so &lt;code&gt;md:flex-row&lt;/code&gt; never lands in your stylesheet.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;responsive-tailwind&lt;/code&gt; doesn't construct anything. Every class you want in your CSS is written out, in full, in a place the scanner can read it. The object key doesn't &lt;em&gt;generate&lt;/em&gt; the prefix — it &lt;strong&gt;asserts&lt;/strong&gt; what the prefix must be, and TypeScript enforces it.&lt;/p&gt;

&lt;p&gt;You still type it twice. The compiler now cares if the two copies disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Actually Does
&lt;/h2&gt;

&lt;p&gt;Three jobs, none of them glamorous:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Groups your classes by breakpoint so a 40-class &lt;code&gt;className&lt;/code&gt; stops being one unreadable line.&lt;/li&gt;
&lt;li&gt;Validates at compile time that every token under &lt;code&gt;md&lt;/code&gt; really starts with &lt;code&gt;md:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Merges the result through &lt;a href="https://github.com/dcastil/tailwind-merge" rel="noopener noreferrer"&gt;&lt;code&gt;tailwind-merge&lt;/code&gt;&lt;/a&gt; so conflicting utilities resolve instead of stacking.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;responsive-tailwind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;responsive&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;responsive-tailwind&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&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="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex flex-col gap-4 p-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md:flex-row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lg:p-8&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="si"&gt;}&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;"flex-1"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Content&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-1"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sidebar&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;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;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flex flex-col gap-4 p-4 md:flex-row lg:p-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same string you'd have written by hand. That's the point — the output is boring on purpose. What changed is that a typo in it is now a build error instead of a component that silently doesn't respond at 768px.&lt;/p&gt;

&lt;p&gt;Arbitrary breakpoints work the same way:&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="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid&lt;/span&gt;&lt;span class="dl"&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;min-[900px]&lt;/span&gt;&lt;span class="dl"&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;min-[900px]:grid-cols-3&lt;/span&gt;&lt;span class="dl"&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;max-[500px]&lt;/span&gt;&lt;span class="dl"&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;max-[500px]:hidden&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;h2&gt;
  
  
  Where The Type Safety Comes From
&lt;/h2&gt;

&lt;p&gt;No plugin, no build step, no codegen. It's one recursive conditional type, and it's small enough to read in full:&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;type&lt;/span&gt; &lt;span class="nx"&gt;ValidatePrefixedTokens&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Prefix&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;Tail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;ValidatePrefixedTokens&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tail&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InvalidBreakpointPrefix&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InvalidBreakpointPrefix&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It splits the string on the first space, checks the head against the prefix, recurses on the tail. Token by token, at compile time, on the literal type of the string you wrote.&lt;/p&gt;

&lt;p&gt;When a token fails, the failure branch doesn't return &lt;code&gt;never&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; — it returns a template literal type that spells out what went wrong. So the error message &lt;em&gt;is&lt;/em&gt; a type:&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="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex-row&lt;/span&gt;&lt;span class="dl"&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 plaintext"&gt;&lt;code&gt;error TS2322: Type '"flex-row"' is not assignable to
type '"Class 'flex-row' in md must start with 'md:'"'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It rides inside an assignability error, so there's some TypeScript noise wrapped around it. But the sentence you actually need to read is right there in the message, naming the offending token and the prefix it should have had. Hovering the property in your editor shows the same thing.&lt;/p&gt;

&lt;p&gt;Arbitrary breakpoints get checked against their own key, so a mismatch you'd never spot by eye gets caught:&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="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;min-[900px]&lt;/span&gt;&lt;span class="dl"&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;min-[800px]:grid-cols-3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Class 'min-[800px]:grid-cols-3' in min-[900px] must start with 'min-[900px]:'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One note on the recursion: validating string literals token by token is exactly the kind of type-level work that used to make people nervous about editor responsiveness. I built and tested this against TypeScript 7, where that calculus is a lot friendlier than it used to be. Worth mentioning since it shaped how comfortable I was leaning on recursive types at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;tailwind-merge&lt;/code&gt; Is Doing Real Work Here
&lt;/h2&gt;

&lt;p&gt;Easy to write this off as a dependency I added for the badge. It isn't.&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="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-2 p-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// → "p-4"&lt;/span&gt;

&lt;span class="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md:gap-4 md:gap-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// → "md:gap-8"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conflicts collapse to the last one, which is what you want when classes get composed from a base and an override. But notice what &lt;em&gt;doesn't&lt;/em&gt; collapse:&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="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex-col&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md:flex-row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// → "flex-col md:flex-row"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;flex-col&lt;/code&gt; and &lt;code&gt;md:flex-row&lt;/code&gt; aren't a conflict — they're different variants, and killing one would break the whole idea of responsive design. &lt;code&gt;tailwind-merge&lt;/code&gt; understands the variant axis, which is precisely why I didn't hand-roll a dedupe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Doesn't Change
&lt;/h2&gt;

&lt;p&gt;The list I'd want to read first, if someone handed me this library:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your CSS output.&lt;/strong&gt; Same utilities in, same stylesheet out. Nothing is generated, so nothing new can appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your &lt;code&gt;tailwind.config&lt;/code&gt;.&lt;/strong&gt; No plugin to register, no preset, no PostCSS entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind's scanner.&lt;/strong&gt; It keeps working exactly as it always did, because every class is still a literal string in your source. That's the whole design constraint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your runtime.&lt;/strong&gt; The type layer erases at build time and costs nothing. The only thing executing in production is one &lt;code&gt;twMerge&lt;/code&gt; call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your existing components.&lt;/strong&gt; &lt;code&gt;responsive()&lt;/code&gt; returns a string. Drop it into one &lt;code&gt;className&lt;/code&gt; and leave everything else alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  This Is Not &lt;code&gt;clsx&lt;/code&gt;, And It's Not &lt;code&gt;cva&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;These get lumped together and they solve different axes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;clsx&lt;/code&gt; / &lt;code&gt;classnames&lt;/code&gt;&lt;/strong&gt; answer &lt;em&gt;"which classes apply right now?"&lt;/em&gt; — conditional joining based on props and state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cva&lt;/code&gt; / &lt;code&gt;tailwind-variants&lt;/code&gt;&lt;/strong&gt; answer &lt;em&gt;"what are this component's variants?"&lt;/em&gt; — a design-system-shaped API over size, intent, and so on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;responsive-tailwind&lt;/code&gt;&lt;/strong&gt; answers &lt;em&gt;"are my breakpoint classes actually correct?"&lt;/em&gt; — one axis, checked at compile time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They compose fine, because all three are just producing strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;clsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;responsive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex flex-col&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;md:flex-row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;isActive&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ring-2 ring-blue-500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;If you already have a variant system you like, keep it. This slots underneath it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honestly: What v1 Doesn't Do Yet
&lt;/h2&gt;

&lt;p&gt;The asterisks, since a features list with no limitations section is marketing, not documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Breakpoint keys are a fixed set.&lt;/strong&gt; &lt;code&gt;sm&lt;/code&gt;, &lt;code&gt;md&lt;/code&gt;, &lt;code&gt;lg&lt;/code&gt;, &lt;code&gt;xl&lt;/code&gt;, &lt;code&gt;2xl&lt;/code&gt;, plus &lt;code&gt;min-[...]&lt;/code&gt; and &lt;code&gt;max-[...]&lt;/code&gt;. If you've defined custom named screens in your Tailwind config, the type doesn't know about them — use the arbitrary syntax for now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only breakpoints are validated.&lt;/strong&gt; &lt;code&gt;dark:&lt;/code&gt;, &lt;code&gt;hover:&lt;/code&gt;, &lt;code&gt;group-*&lt;/code&gt; and friends aren't breakpoint keys, so they pass through untouched. Stacking them on a validated breakpoint works exactly as you'd expect (&lt;code&gt;md: "md:hover:bg-blue-500"&lt;/code&gt; is valid), they just aren't the axis this library checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The token splitter wants single spaces.&lt;/strong&gt; The recursion splits on one space at a time, so a double space or a line break inside a breakpoint value confuses it and you get an error naming an empty token. Keep breakpoint values on one line with single spaces and you'll never see it. It's on my list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-literal strings are skipped by design.&lt;/strong&gt; If you pass a value typed as &lt;code&gt;string&lt;/code&gt; rather than a literal, there's nothing for the compiler to inspect, so it's allowed through. Useful as an escape hatch, worth knowing it's a hole.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation is compile-time only.&lt;/strong&gt; Nothing is checked at runtime. If you're not type-checking your project, this library does nothing for you but call &lt;code&gt;twMerge&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Guardrail, Not An Abstraction
&lt;/h2&gt;

&lt;p&gt;The reason I keep coming back to that "why twice?" question is that it's the whole thesis in miniature.&lt;/p&gt;

&lt;p&gt;An abstraction would hide the prefix and hand you a cleaner-looking API that quietly disagrees with how Tailwind actually works. A guardrail leaves the prefix exactly where the scanner can see it and just refuses to let you get it wrong. The first one feels better in a README. The second one is the one that still works when your component doesn't reflow at 768px in production and you have twenty minutes to figure out why.&lt;/p&gt;

&lt;p&gt;Small library. Opinionated about one thing. That's the pitch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/responsive-tailwind" rel="noopener noreferrer"&gt;&lt;code&gt;responsive-tailwind&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/babyreptile/responsive-tailwind" rel="noopener noreferrer"&gt;babyreptile/responsive-tailwind&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Issues and API feedback welcome — v1.0.0 is the smallest thing I thought was worth publishing, and the shape of v1.1 depends a lot on what people actually hit.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tailwindcss</category>
      <category>react</category>
      <category>npm</category>
    </item>
    <item>
      <title>SOFT SKILLS - What I have learned with John Sonmez's book. #1</title>
      <dc:creator>Caio Rodrigues</dc:creator>
      <pubDate>Fri, 23 Jun 2023 10:54:33 +0000</pubDate>
      <link>https://dev.to/babyreptile/soft-skills-what-i-have-learned-with-john-sonmezs-book-1-711</link>
      <guid>https://dev.to/babyreptile/soft-skills-what-i-have-learned-with-john-sonmezs-book-1-711</guid>
      <description>&lt;h2&gt;
  
  
  #1 - You have just started
&lt;/h2&gt;

&lt;p&gt;This is the first part of a series of chapters where I'm going to share with you what I have learned from the book "Soft Skills: The Software Developer's Life Manual" written by John Sonmez.&lt;/p&gt;

&lt;p&gt;Please note that this article is nothing more than a summary with my opinions. I still strongly recommend that you read the book as it brings various life experiences and important insights into the life of a software developer.&lt;/p&gt;

&lt;p&gt;In the first section, John talks about the career, so let's start from there.&lt;/p&gt;

&lt;h3&gt;
  
  
  You are a business guy, just accept it
&lt;/h3&gt;

&lt;p&gt;One of the most interesting tips, which involves a change in how we approach our work, is right at the beginning of the book: You are not just a worker; you have a business, and that is how you should treat your entire developer career—not as an employee working 40 hours a week for someone else, but as someone who is running their own business and constantly seeking ways to improve, add value, and evolve. Even if you are working in a company with a signed contract and all, in John's words: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"It's better to think of an employer as a customer for your software development business" (Sonmez, John. Soft Skills: The Software Developer's Life Manual).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  To eat a pizza, it's better in slices
&lt;/h3&gt;

&lt;p&gt;After teaching us how to look at our career in a way that constantly strives for improvement, the author provides us with a great method for setting goals of any scale and always keeping them in sight, maintaining a sense of progress. For those who have studied calculus in college, you know that the area under the curve can be calculated by summing up the areas of tiny bars:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5jn028scnhxaq6r271p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5jn028scnhxaq6r271p.png" alt=" " width="564" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the fundamental concept of integrals and serves as the foundation for achieving big goals, according to John: by setting smaller goals that are part of the larger goal. The sum of your efforts will result in the accomplishment you desire, so always set tangible smaller goals that are steps towards something bigger.&lt;/p&gt;

&lt;h3&gt;
  
  
  You have a mouth and you will use it
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/esR1eKgmOnxWKR627f/giphy-downsized-large.gif" class="article-body-image-wrapper"&gt;&lt;img width="480" src="https://i.giphy.com/media/esR1eKgmOnxWKR627f/giphy-downsized-large.gif" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moving forward, John shares a truth that may be somewhat painful for some of us: development is not just about writing code; in fact, there may be days when writing code won't fit into your tight schedule. I myself have an experience in my career that supports this statement. When I was an intern, one of the senior developers in the company was helping me with some implementations while we were pair coding. He was sharing his screen to show me some interesting things when he received four consecutive email notifications, and guess what? Four meetings scheduled for the same day. He simply said, "Okay, today I won't be coding." And that's exactly what John mentions in the book. Software development is not just about programming; you will still need to attend meetings, discuss your tasks, and provide input on relevant topics. In other words, it's possible that 50% or more of your work will involve dealing with people, so you need to know how to do that. Yes, that's right. Your course on "Learning JavaScript in a Week" is not enough. You will be a much more valuable professional if you can effectively communicate with your coworkers.&lt;/p&gt;

&lt;h4&gt;
  
  
  Summarizing this first part of the series
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Treat your career like a business&lt;/li&gt;
&lt;li&gt;Break down your goals into smaller objectives&lt;/li&gt;
&lt;li&gt;Enhance your skills in dealing with others&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next chapter on the teachings of the book "Soft Skills: The Software Developer's Life Manual" by Sonmez, John, we will discuss the importance of good resumes, the three paths of a software development career, and why you should specialize.&lt;/p&gt;

</description>
      <category>career</category>
      <category>skills</category>
      <category>books</category>
    </item>
  </channel>
</rss>
