<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged tailwindcss</title>
<link>https://coderlegion.com/tag/tailwindcss</link>
<description>Powered by Question2Answer</description>
<item>
<title>Your Flex Child Won't Shrink, and overflow: hidden Isn't the Fix (It's min-width: 0)&quot;</title>
<link>https://coderlegion.com/32086/your-flex-child-wont-shrink-and-overflow-hidden-isnt-the-fix-its-min-width-0</link>
<description>&lt;p&gt;You build a dashboard. Sidebar on the left, content on the right, a table or a code block inside. It looks perfect with your sample data.&lt;/p&gt;
&lt;p&gt;Then a user pastes a long URL into a field, and the whole page grows a horizontal scrollbar. The sidebar gets squeezed, the layout breaks, and you start adding &lt;code&gt;overflow: hidden&lt;/code&gt; to everything until the symptoms go away.&lt;/p&gt;
&lt;p&gt;There is a one-line fix, and it is not &lt;code&gt;overflow: hidden&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-layout-that-breaks&quot;&gt;The Layout That Breaks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&quot;layout&quot;&amp;gt;
  &amp;lt;aside class=&quot;sidebar&quot;&amp;gt;Menu&amp;lt;/aside&amp;gt;
  &amp;lt;main class=&quot;content&quot;&amp;gt;
    &amp;lt;pre&amp;gt;https://example.com/a-very-long-url-with-no-break-points-in-it-at-all-1234567890&amp;lt;/pre&amp;gt;
  &amp;lt;/main&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.layout {
  display: flex;
}
.sidebar {
  width: 240px;
  flex-shrink: 0;
}
.content {
  flex: 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.content&lt;/code&gt; is told to take the remaining space with &lt;code&gt;flex: 1&lt;/code&gt;. Nobody told it to stay &lt;em&gt;inside&lt;/em&gt; that space, and that is the entire problem.&lt;/p&gt;
&lt;h3 id=&quot;why-it-happens&quot;&gt;Why It Happens&lt;/h3&gt;
&lt;p&gt;Flex items and grid items have a default of &lt;code&gt;min-width: auto&lt;/code&gt;. For a flex item, &lt;code&gt;auto&lt;/code&gt; means:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Do not shrink narrower than the smallest size your content can be.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For a paragraph of normal words, the smallest size is the longest single word, because the line can wrap between words. For a long URL, a &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; block, a wide table or an image, there is nowhere to wrap, so the smallest size is the &lt;strong&gt;full width of the content&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So the browser is not ignoring your layout. It is obeying a rule you never knew existed: the item refuses to shrink below its content, and the overflow spills into the page.&lt;/p&gt;
&lt;h3 id=&quot;why-overflow-hidden-looks-like-a-fix-sometimes-&quot;&gt;Why &lt;code&gt;overflow: hidden&lt;/code&gt; Looks Like a Fix (Sometimes)&lt;/h3&gt;
&lt;p&gt;Setting &lt;code&gt;overflow&lt;/code&gt; to anything other than &lt;code&gt;visible&lt;/code&gt; changes how &lt;code&gt;min-width: auto&lt;/code&gt; is calculated. The automatic minimum becomes 0 for that item. That is why &lt;code&gt;overflow: hidden&lt;/code&gt; on the right element makes the bug vanish.&lt;/p&gt;
&lt;p&gt;It works by accident, and it has a cost: it clips content, can cut off focus outlines and dropdowns, and does nothing useful if you put it on the wrong element. Fixing the cause is cleaner.&lt;/p&gt;
&lt;h3 id=&quot;fix-1-min-width-0-on-the-flex-child&quot;&gt;Fix 1: min-width: 0 on the Flex Child&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.content {
  flex: 1;
  min-width: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You are telling the item that it is allowed to shrink below its content. Now &lt;code&gt;.content&lt;/code&gt; stays inside its space, and the long URL can be scrolled, wrapped or cut off by something you control:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.content pre {
  overflow-x: auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Tailwind, the utility is &lt;code&gt;min-w-0&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;main class=&quot;flex-1 min-w-0&quot;&amp;gt;
  &amp;lt;pre class=&quot;overflow-x-auto&quot;&amp;gt;...&amp;lt;/pre&amp;gt;
&amp;lt;/main&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;fix-2-grid-s-1fr-has-the-same-trap&quot;&gt;Fix 2: Grid's 1fr Has the Same Trap&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.layout {
  display: grid;
  grid-template-columns: 240px 1fr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;1fr&lt;/code&gt; is not &quot;one equal share, and no more&quot;. It is shorthand for &lt;code&gt;minmax(auto, 1fr)&lt;/code&gt;, so the column cannot get narrower than its content. The same long URL stretches the column and the page.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.layout {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;minmax(0, 1fr)&lt;/code&gt; allows the column to shrink to nothing if it has to. A useful side note for Tailwind users: &lt;code&gt;grid-cols-2&lt;/code&gt; and its siblings already compile to &lt;code&gt;repeat(2, minmax(0, 1fr))&lt;/code&gt;, which is why many people never meet this bug in the grid, and then meet it as soon as they write a custom template column.&lt;/p&gt;
&lt;h3 id=&quot;fix-3-truncating-text-with-an-ellipsis-inside-flex&quot;&gt;Fix 3: Truncating Text With an Ellipsis Inside Flex&lt;/h3&gt;
&lt;p&gt;This one catches almost everybody:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.row {
  display: flex;
  gap: 8px;
}
.title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does not truncate, because the flex item sizes itself to the text. Add &lt;code&gt;min-width: 0&lt;/code&gt; to &lt;code&gt;.title&lt;/code&gt; and the ellipsis appears. In Tailwind, &lt;code&gt;truncate min-w-0&lt;/code&gt; on the same element does it.&lt;/p&gt;
&lt;h3 id=&quot;fix-4-long-words-need-the-right-overflow-wrap&quot;&gt;Fix 4: Long Words Need the Right overflow-wrap&lt;/h3&gt;
&lt;p&gt;For user-generated text such as comments, names and pasted links, allow breaking inside a word:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.comment {
  overflow-wrap: anywhere;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;overflow-wrap: break-word&lt;/code&gt; looks equivalent, but it does not change the item's minimum size calculation, so inside a flex or grid item it can still leave the layout stretched. &lt;code&gt;anywhere&lt;/code&gt; does take the break points into account, and the item can shrink.&lt;/p&gt;
&lt;h3 id=&quot;which-one-when&quot;&gt;Which One When&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Symptom &lt;/th&gt;&lt;th&gt;Fix&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Flex child pushes the page wider &lt;/td&gt;&lt;td&gt;&lt;code&gt;min-width: 0&lt;/code&gt; on that child&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Grid column grows past its space &lt;/td&gt;&lt;td&gt;&lt;code&gt;minmax(0, 1fr)&lt;/code&gt; instead of &lt;code&gt;1fr&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Ellipsis not appearing in a flex row &lt;/td&gt;&lt;td&gt;&lt;code&gt;min-width: 0&lt;/code&gt; plus the truncation rules&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Long pasted word or URL &lt;/td&gt;&lt;td&gt;&lt;code&gt;overflow-wrap: anywhere&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Wide table or code block &lt;/td&gt;&lt;td&gt;Own scroll container with &lt;code&gt;overflow-x: auto&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;the-rule&quot;&gt;The Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Flex and grid items refuse to shrink below their content by default. If something inside a layout can be wider than the space it is given, tell the container it is allowed to shrink with &lt;code&gt;min-width: 0&lt;/code&gt; or &lt;code&gt;minmax(0, 1fr)&lt;/code&gt;, and decide where the overflow should scroll or wrap.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Quick audit: open your app and paste a 200-character string with no spaces into every input that is later displayed. Then widen a table to 40 columns. If the page grows a horizontal scrollbar, you have found a missing &lt;code&gt;min-width: 0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I test every layout in my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com&quot;&gt;Next.js and Tailwind templates&lt;/a&gt; against this kind of hostile content, because real users do paste long strings, and a dashboard has to survive it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What is the ugliest layout bug a single piece of user content has caused you? Drop it in the comments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32086/your-flex-child-wont-shrink-and-overflow-hidden-isnt-the-fix-its-min-width-0</guid>
<pubDate>Thu, 22 Oct 2026 15:08:46 +0000</pubDate>
</item>
<item>
<title>Build a Slide-In Mobile Menu in TanStack Start with Tailwind v4</title>
<link>https://coderlegion.com/31644/build-a-slide-in-mobile-menu-in-tanstack-start-with-tailwind-v4</link>
<description>&lt;p&gt;A mobile menu that pops in and out works, but it feels unfinished. In this article we turn it into a drawer that slides in from the right with a fading backdrop, using only Tailwind classes and no animation library. We also keep it usable for keyboard and screen-reader users, lock the page scroll while it is open, and respect the reduced-motion setting.&lt;/p&gt;
&lt;p&gt;This is part of the &lt;strong&gt;TanStack Start Basics&lt;/strong&gt; series. It builds on the responsive &lt;code&gt;Header&lt;/code&gt; and the separate &lt;code&gt;NavLinks&lt;/code&gt; component from the previous part.&lt;/p&gt;
&lt;p&gt;You can view the video &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=4oE4P7uGF3Y&quot;&gt;Here&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;remove-the-old-mobile-menu&quot;&gt;Remove the old mobile menu&lt;/h3&gt;
&lt;p&gt;In the previous part, the mobile menu lived in the &lt;code&gt;Header&lt;/code&gt;, underneath the flex container, and the hamburger button toggled it. Delete it. This is the code to remove:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;{open &amp;amp;&amp;amp; (
  &amp;lt;nav
    id=&quot;mobile-menu&quot;
    aria-label=&quot;Mobile&quot;
    className=&quot;border-t md:hidden&quot;
  &amp;gt;
    &amp;lt;NavLinks
      listClass=&quot;flex flex-col px-6 py-2&quot;
      linkClass=&quot;block py-3 text-base font-medium text-neutral-600 hover:text-black&quot;
      onNavigate={() =&amp;gt; setOpen(false)}
    /&amp;gt;
  &amp;lt;/nav&amp;gt;
)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;open&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;, React removes this menu from the page completely. A CSS transition needs an element that exists in both the start and end states, so there is nothing to animate. The fix is a side panel that is always on the page, which we slide off screen with Tailwind.&lt;/p&gt;
&lt;h3 id=&quot;always-render-the-panel&quot;&gt;Always render the panel&lt;/h3&gt;
&lt;p&gt;Here is the side panel. It replaces the old menu in the same position in the header. It uses a &lt;code&gt;cn&lt;/code&gt; helper, so you will also need this function in the &lt;code&gt;utils&lt;/code&gt; file in your &lt;code&gt;lib&lt;/code&gt; folder. If you use shadcn/ui, you already have it. Otherwise, install &lt;code&gt;clsx&lt;/code&gt; and &lt;code&gt;tailwind-merge&lt;/code&gt; (&lt;code&gt;npm i clsx tailwind-merge&lt;/code&gt;) and add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/lib/utils.ts
import type { ClassValue } from 'clsx'
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The panel also uses the &lt;code&gt;X&lt;/code&gt; icon, so add these imports at the top of &lt;code&gt;Header.tsx&lt;/code&gt; if you don't have them yet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;nav
  id=&quot;mobile-menu&quot;
  aria-label=&quot;Mobile&quot;
  className={cn(
    'fixed inset-y-0 right-0 z-50 w-72 bg-white p-6 shadow-xl md:hidden',
  )}
&amp;gt;
  &amp;lt;button
    type=&quot;button&quot;
    onClick={() =&amp;gt; setOpen(false)}
    aria-label=&quot;Close menu&quot;
    className=&quot;mb-6 ml-auto block rounded p-2 hover:bg-gray-100&quot;
  &amp;gt;
    &amp;lt;X className=&quot;size-6&quot; /&amp;gt;
  &amp;lt;/button&amp;gt;
  &amp;lt;NavLinks
    listClass=&quot;flex flex-col gap-4&quot;
    linkClass=&quot;text-lg text-gray-600 hover:text-black&quot;
    onNavigate={() =&amp;gt; setOpen(false)}
  /&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;make-it-jump-in-and-out&quot;&gt;Make it jump in and out&lt;/h3&gt;
&lt;p&gt;To move the panel on and off the screen, add Tailwind's translate classes with a ternary on &lt;code&gt;open&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;open ? 'translate-x-0' : 'translate-x-full',
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add it as a second argument to &lt;code&gt;cn&lt;/code&gt; on the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;nav
  id=&quot;mobile-menu&quot;
  aria-label=&quot;Mobile&quot;
  className={cn(
    'fixed inset-y-0 right-0 z-50 w-72 bg-white p-6 shadow-xl md:hidden',
    open ? 'translate-x-0' : 'translate-x-full',
  )}
&amp;gt;
  {/* the close button and NavLinks stay the same */}
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The panel now jumps on and off the screen, but it doesn't slide yet.&lt;/p&gt;
&lt;p&gt;You may have noticed the helper function &lt;code&gt;cn&lt;/code&gt;. It joins class names, skips the ones that are false, and if two Tailwind classes conflict, the last one wins.&lt;/p&gt;
&lt;h3 id=&quot;add-the-backdrop&quot;&gt;Add the backdrop&lt;/h3&gt;
&lt;p&gt;Next, a dark layer behind the panel. Clicking it closes the menu, which is what people expect from a drawer. It sits one layer below the panel (&lt;code&gt;z-40&lt;/code&gt; against &lt;code&gt;z-50&lt;/code&gt;) and covers the whole screen. When the menu is closed it must not catch clicks, so &lt;code&gt;pointer-events-none&lt;/code&gt; is not optional here. Without it, an invisible full-screen layer would swallow every tap on the page. Put it right above the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;div
  aria-hidden=&quot;true&quot;
  onClick={() =&amp;gt; setOpen(false)}
  className={cn(
    'fixed inset-0 z-40 bg-black/50 md:hidden',
    open ? 'opacity-100' : 'pointer-events-none opacity-0',
  )}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is how it should look in your code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;div
  aria-hidden=&quot;true&quot;
  onClick={() =&amp;gt; setOpen(false)}
  className={cn(
    'fixed inset-0 z-40 bg-black/50 md:hidden',
    open ? 'opacity-100' : 'pointer-events-none opacity-0',
  )}
/&amp;gt;
&amp;lt;nav
  id=&quot;mobile-menu&quot;
  aria-label=&quot;Mobile&quot;
  className={cn(
    'fixed inset-y-0 right-0 z-50 w-72 bg-white p-6 shadow-xl md:hidden',
    open ? 'translate-x-0' : 'translate-x-full',
  )}
&amp;gt;
  &amp;lt;button
    type=&quot;button&quot;
    onClick={() =&amp;gt; setOpen(false)}
    aria-label=&quot;Close menu&quot;
    className=&quot;mb-6 ml-auto block rounded p-2 hover:bg-gray-100&quot;
  &amp;gt;
    &amp;lt;X className=&quot;size-6&quot; /&amp;gt;
  &amp;lt;/button&amp;gt;
  &amp;lt;NavLinks
    listClass=&quot;flex flex-col gap-4&quot;
    linkClass=&quot;text-lg text-gray-600 hover:text-black&quot;
    onNavigate={() =&amp;gt; setOpen(false)}
  /&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;make-it-slide&quot;&gt;Make it slide&lt;/h3&gt;
&lt;p&gt;Now the fun part, and it takes two classes. &lt;code&gt;transition-transform&lt;/code&gt; tells the browser to animate changes to the position, &lt;code&gt;duration-300&lt;/code&gt; makes it take 300 milliseconds, and &lt;code&gt;ease-out&lt;/code&gt; makes it start fast and settle gently. The backdrop gets the same treatment, but it fades, so it uses &lt;code&gt;transition-opacity&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Add this to the panel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;transition-transform duration-300 ease-out
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add this to the backdrop:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;transition-opacity duration-300
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your code should look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;div
  aria-hidden=&quot;true&quot;
  onClick={() =&amp;gt; setOpen(false)}
  className={cn(
    'fixed inset-0 z-40 bg-black/50 transition-opacity duration-300 md:hidden',
    open ? 'opacity-100' : 'pointer-events-none opacity-0',
  )}
/&amp;gt;
&amp;lt;nav
  id=&quot;mobile-menu&quot;
  aria-label=&quot;Mobile&quot;
  className={cn(
    'fixed inset-y-0 right-0 z-50 w-72 bg-white p-6 shadow-xl transition-transform duration-300 ease-out md:hidden',
    open ? 'translate-x-0' : 'translate-x-full',
  )}
&amp;gt;
  &amp;lt;button
    type=&quot;button&quot;
    onClick={() =&amp;gt; setOpen(false)}
    aria-label=&quot;Close menu&quot;
    className=&quot;mb-6 ml-auto block rounded p-2 hover:bg-gray-100&quot;
  &amp;gt;
    &amp;lt;X className=&quot;size-6&quot; /&amp;gt;
  &amp;lt;/button&amp;gt;
  &amp;lt;NavLinks
    listClass=&quot;flex flex-col gap-4&quot;
    linkClass=&quot;text-lg text-gray-600 hover:text-black&quot;
    onNavigate={() =&amp;gt; setOpen(false)}
  /&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One Tailwind v4 detail: &lt;code&gt;translate-x-*&lt;/code&gt; sets the CSS &lt;code&gt;translate&lt;/code&gt; property, not &lt;code&gt;transform&lt;/code&gt;. In v4, &lt;code&gt;transition-transform&lt;/code&gt; includes &lt;code&gt;translate&lt;/code&gt;, so this works. If a slide ever refuses to animate, check that your transition covers &lt;code&gt;translate&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;hide-the-closed-panel&quot;&gt;Hide the closed panel&lt;/h3&gt;
&lt;p&gt;It looks right, but there is a catch. The panel is only parked off screen. Press Tab with the menu closed and focus disappears into links you can't see, and a screen reader will still read them out. The fix is the &lt;code&gt;invisible&lt;/code&gt; class when the menu is closed.&lt;/p&gt;
&lt;p&gt;If we add &lt;code&gt;invisible&lt;/code&gt; and nothing else, the panel vanishes the instant you close it, and the slide-out never shows. So we also tell the transition to animate &lt;code&gt;visibility&lt;/code&gt;, not just position. The browser keeps the panel visible until the slide finishes, then hides it.&lt;/p&gt;
&lt;p&gt;Make two changes to the panel:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Replace &lt;code&gt;transition-transform&lt;/code&gt; with &lt;code&gt;transition-[translate,visibility]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;translate-x-full&lt;/code&gt; with &lt;code&gt;invisible translate-x-full&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your code should look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;div
  aria-hidden=&quot;true&quot;
  onClick={() =&amp;gt; setOpen(false)}
  className={cn(
    'fixed inset-0 z-40 bg-black/50 transition-opacity duration-300 md:hidden',
    open ? 'opacity-100' : 'pointer-events-none opacity-0',
  )}
/&amp;gt;
&amp;lt;nav
  id=&quot;mobile-menu&quot;
  aria-label=&quot;Mobile&quot;
  className={cn(
    'fixed inset-y-0 right-0 z-50 w-72 bg-white p-6 shadow-xl transition-[translate,visibility] duration-300 ease-out md:hidden',
    open ? 'translate-x-0' : 'invisible translate-x-full',
  )}
&amp;gt;
  &amp;lt;button
    type=&quot;button&quot;
    onClick={() =&amp;gt; setOpen(false)}
    aria-label=&quot;Close menu&quot;
    className=&quot;mb-6 ml-auto block rounded p-2 hover:bg-gray-100&quot;
  &amp;gt;
    &amp;lt;X className=&quot;size-6&quot; /&amp;gt;
  &amp;lt;/button&amp;gt;
  &amp;lt;NavLinks
    listClass=&quot;flex flex-col gap-4&quot;
    linkClass=&quot;text-lg text-gray-600 hover:text-black&quot;
    onNavigate={() =&amp;gt; setOpen(false)}
  /&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;stop-the-page-from-scrolling&quot;&gt;Stop the page from scrolling&lt;/h3&gt;
&lt;p&gt;With the drawer open, scrolling still moves the page behind it, which feels sloppy. We already have an effect that listens for the Escape key, so we extend it: lock scrolling when the menu opens, and unlock it in the cleanup. Replace the &lt;code&gt;useEffect&lt;/code&gt; from the previous part, the one that closes the menu on Escape, with this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;useEffect(() =&amp;gt; {
  if (!open) return

  const onKeyDown = (e: KeyboardEvent) =&amp;gt; {
    if (e.key === 'Escape') setOpen(false)
  }

  document.addEventListener('keydown', onKeyDown)
  document.body.style.overflow = 'hidden'

  return () =&amp;gt; {
    document.removeEventListener('keydown', onKeyDown)
    document.body.style.overflow = ''
  }
}, [open])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cleanup function runs when the menu closes and also if the &lt;code&gt;Header&lt;/code&gt; ever unmounts, so the page can never get stuck locked.&lt;/p&gt;
&lt;h3 id=&quot;respect-reduced-motion-optional-&quot;&gt;Respect reduced motion (optional)&lt;/h3&gt;
&lt;p&gt;Some people turn on &quot;reduce motion&quot; in their system settings because animation makes them dizzy. Tailwind gives us a variant for that, &lt;code&gt;motion-reduce:&lt;/code&gt;, so honoring it costs one class per element. If that's you, add &lt;code&gt;motion-reduce:transition-none&lt;/code&gt; to the panel and the backdrop, like so.&lt;/p&gt;
&lt;p&gt;Panel:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;className={cn(
  'fixed inset-y-0 right-0 z-50 w-72 bg-white p-6 shadow-xl transition-[translate,visibility] duration-300 ease-out motion-reduce:transition-none md:hidden',
  open ? 'translate-x-0' : 'invisible translate-x-full',
)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Backdrop:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;className={cn(
  'fixed inset-0 z-40 bg-black/50 transition-opacity duration-300 motion-reduce:transition-none md:hidden',
  open ? 'opacity-100' : 'pointer-events-none opacity-0',
)}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;test-it-and-wrap-up&quot;&gt;Test it and wrap up&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Open the menu: the panel slides in and the backdrop fades.&lt;/li&gt;
&lt;li&gt;Click the backdrop: the menu closes.&lt;/li&gt;
&lt;li&gt;Open it again and press Escape: it closes.&lt;/li&gt;
&lt;li&gt;Open it and click a link: it navigates and closes.&lt;/li&gt;
&lt;li&gt;Close the menu and press Tab: focus skips the hidden links.&lt;/li&gt;
&lt;li&gt;Open the menu and try to scroll the page: it stays put.&lt;/li&gt;
&lt;li&gt;Turn on reduced motion in your system settings: the menu appears with no animation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's recap. We always render the panel and move it with &lt;code&gt;translate&lt;/code&gt;. The backdrop fades and closes the menu. &lt;code&gt;transition-[translate,visibility]&lt;/code&gt; keeps the slide-out visible, then hides the panel from keyboards and screen readers. We locked the page scroll, and we respect reduced motion. All of it is plain Tailwind classes and one small effect.&lt;/p&gt;
&lt;h3 id=&quot;good-to-know-and-what-to-try-next&quot;&gt;Good to know and what to try next&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;A gotcha:&lt;/strong&gt; a &lt;code&gt;fixed&lt;/code&gt; element is positioned against the viewport unless an ancestor has a &lt;code&gt;transform&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt; or &lt;code&gt;backdrop-filter&lt;/code&gt;. If your header uses &lt;code&gt;backdrop-blur&lt;/code&gt;, move the backdrop and panel outside the &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; element or the drawer will be sized to the header instead of the screen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tell screen readers the menu state:&lt;/strong&gt; the hamburger button from the previous part should announce whether the menu is open. Add &lt;code&gt;aria-expanded&lt;/code&gt; and &lt;code&gt;aria-controls&lt;/code&gt; (the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; already has &lt;code&gt;id=&quot;mobile-menu&quot;&lt;/code&gt;), and keep the label constant, because the state comes from &lt;code&gt;aria-expanded&lt;/code&gt; and a label that flips between &quot;Open&quot; and &quot;Close&quot; can make it get announced twice. If your button already has a label like &quot;Open menu&quot;, change it to a fixed one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;button
  type=&quot;button&quot;
  onClick={() =&amp;gt; setOpen((o) =&amp;gt; !o)}
  aria-expanded={open}
  aria-controls=&quot;mobile-menu&quot;
  aria-label=&quot;Menu&quot;
&amp;gt;
  {/* your hamburger icon */}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Try next:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Close the menu automatically when the window grows past the &lt;code&gt;md&lt;/code&gt; breakpoint, so the scroll lock can never be left on.&lt;/li&gt;
&lt;li&gt;Move focus to the Close button when the menu opens, and back to the hamburger when it closes.&lt;/li&gt;
&lt;li&gt;Swap the slide for a left-side drawer by changing &lt;code&gt;right-0&lt;/code&gt; to &lt;code&gt;left-0&lt;/code&gt; and &lt;code&gt;translate-x-full&lt;/code&gt; to &lt;code&gt;-translate-x-full&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next in the series: error handling and a proper not-found page.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31644/build-a-slide-in-mobile-menu-in-tanstack-start-with-tailwind-v4</guid>
<pubDate>Sun, 18 Oct 2026 18:49:44 +0000</pubDate>
</item>
<item>
<title>Responsive Navigation in TanStack Start: Hamburger Menu and Desktop Navbar</title>
<link>https://coderlegion.com/31251/responsive-navigation-in-tanstack-start-hamburger-menu-and-desktop-navbar</link>
<description>&lt;p&gt;A header that works on a phone and on a desktop is one of the first things every site needs. In this part of the TanStack Start Basics series, we'll build one with inline links on wide screens, a hamburger menu on small ones, and a few accessibility details that are easy to skip.&lt;/p&gt;
&lt;p&gt;You can watch the video here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=_-0DDuQseSo&quot;&gt;Responsive Navigation&lt;/a&gt;. Everything below is the written version.&lt;/p&gt;
&lt;h4 id=&quot;write-the-links-once&quot;&gt;Write the links once&lt;/h4&gt;
&lt;p&gt;Start with a single list of links that both menus will read from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/lib/nav.ts
export const navLinks = [
  { to: '/', label: 'Home' },
  { to: '/products', label: 'Products' },
  { to: '/about', label: 'About' },
  { to: '/contact', label: 'Contact' },
] as const;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;as const&lt;/code&gt; lets TypeScript treat every &lt;code&gt;to&lt;/code&gt; as an exact route, so a typo is a compile error and not a broken link.&lt;/p&gt;
&lt;h4 id=&quot;think-mobile-first&quot;&gt;Think mobile first&lt;/h4&gt;
&lt;p&gt;Tailwind is mobile first. Classes without a prefix apply to every screen size, and a prefix such as &lt;code&gt;md:&lt;/code&gt; applies from that width upward. So the plan is to style the phone version first, and then add the desktop changes on top:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hidden md:block&lt;/code&gt; is hidden on phones and shown from 768px up (the desktop links)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;md:hidden&lt;/code&gt; is shown on phones and hidden from 768px up (the menu button and the mobile menu)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;a-separate-navlinks-component&quot;&gt;A separate NavLinks component&lt;/h4&gt;
&lt;p&gt;Both menus need the same list of links, so it gets its own component. Start with the props type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/types/index.ts
export type NavLinksProps = {
  listClass: string;
  linkClass: string;
  activeClass?: string;
  onNavigate?: () =&amp;gt; void;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/NavLinks.tsx
import { Link } from '@tanstack/react-router';
import { navLinks } from '@/lib/nav';
import type { NavLinksProps } from '@/types';

export default function NavLinks({
  listClass,
  linkClass,
  activeClass = 'text-black underline underline-offset-4',
  onNavigate,
}: NavLinksProps) {
  return (
    &amp;lt;ul className={listClass}&amp;gt;
      {navLinks.map((link) =&amp;gt; (
        &amp;lt;li key={link.to}&amp;gt;
          &amp;lt;Link
            to={link.to}
            activeOptions={{ exact: link.to === '/' }}
            className={linkClass}
            activeProps={{ className: activeClass }}
            onClick={onNavigate}
          &amp;gt;
            {link.label}
          &amp;lt;/Link&amp;gt;
        &amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two class props let each menu style the list its own way. &lt;code&gt;activeClass&lt;/code&gt; has a default, so most callers can leave it out, and &lt;code&gt;onNavigate&lt;/code&gt; is optional, so the desktop menu doesn't need it. Because the component lives in its own file, you can reuse it later, in a footer for example, without copying anything.&lt;/p&gt;
&lt;h4 id=&quot;the-header&quot;&gt;The header&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/Header.tsx
import { useEffect, useState } from 'react';
import { Link } from '@tanstack/react-router';
import { Menu, X } from 'lucide-react';
import NavLinks from '@/components/NavLinks';

export default function Header() {
  const [open, setOpen] = useState(false);

  useEffect(() =&amp;gt; {
    if (!open) return;
    const onKeyDown = (e: KeyboardEvent) =&amp;gt; {
      if (e.key === 'Escape') setOpen(false);
    };
    window.addEventListener('keydown', onKeyDown);
    return () =&amp;gt; window.removeEventListener('keydown', onKeyDown);
  }, [open]);

  return (
    &amp;lt;header className=&quot;border-b bg-white&quot;&amp;gt;
      &amp;lt;div className=&quot;mx-auto flex h-16 max-w-6xl items-center justify-between px-6&quot;&amp;gt;
        &amp;lt;Link to=&quot;/&quot; className=&quot;text-lg font-bold&quot;&amp;gt;
          My Store
        &amp;lt;/Link&amp;gt;

        &amp;lt;nav aria-label=&quot;Main&quot; className=&quot;hidden md:block&quot;&amp;gt;
          &amp;lt;NavLinks
            listClass=&quot;flex items-center gap-6&quot;
            linkClass=&quot;text-sm font-medium text-neutral-600 hover:text-black&quot;
          /&amp;gt;
        &amp;lt;/nav&amp;gt;

        &amp;lt;button
          type=&quot;button&quot;
          onClick={() =&amp;gt; setOpen((o) =&amp;gt; !o)}
          aria-label={open ? 'Close menu' : 'Open menu'}
          aria-expanded={open}
          aria-controls=&quot;mobile-menu&quot;
          className=&quot;flex size-11 items-center justify-center rounded-md md:hidden&quot;
        &amp;gt;
          {open ? &amp;lt;X className=&quot;size-6&quot; /&amp;gt; : &amp;lt;Menu className=&quot;size-6&quot; /&amp;gt;}
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;

      {open &amp;amp;&amp;amp; (
        &amp;lt;nav
          id=&quot;mobile-menu&quot;
          aria-label=&quot;Mobile&quot;
          className=&quot;border-t md:hidden&quot;
        &amp;gt;
          &amp;lt;NavLinks
            listClass=&quot;flex flex-col px-6 py-2&quot;
            linkClass=&quot;block py-3 text-base font-medium text-neutral-600 hover:text-black&quot;
            onNavigate={() =&amp;gt; setOpen(false)}
          /&amp;gt;
        &amp;lt;/nav&amp;gt;
      )}
    &amp;lt;/header&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;closing-the-menu-when-you-navigate&quot;&gt;Closing the menu when you navigate&lt;/h4&gt;
&lt;p&gt;If you skip this, you'll notice a bug: you tap a link, the page changes, and the menu stays open. The header lives in a layout route, and a layout stays mounted while the pages inside it change, so its state is never reset.&lt;/p&gt;
&lt;p&gt;The fix is to close the menu in the link's click handler, which is where the user's action happens. React's documentation recommends handling things caused by a user action in the event handler for that action, and not in an effect. So &lt;code&gt;NavLinks&lt;/code&gt; gets an optional &lt;code&gt;onNavigate&lt;/code&gt; prop, and only the mobile menu passes &lt;code&gt;() =&amp;gt; setOpen(false)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This also closes the menu when you tap the link for the page you're already on, which an effect that watches the path would miss. If you also want the menu to close when the user presses the browser's back button, you can add an effect that watches the path on top of this, but the click handler covers the main case.&lt;/p&gt;
&lt;h4 id=&quot;active-links&quot;&gt;Active links&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Link&lt;/code&gt; knows when it points at the current page, and &lt;code&gt;activeProps&lt;/code&gt; lets you style that state. Every path starts with a slash, so the home link would look active everywhere. &lt;code&gt;activeOptions={{ exact: true }}&lt;/code&gt; fixes that, and the code above applies it only to the home link.&lt;/p&gt;
&lt;h4 id=&quot;accessibility-basics&quot;&gt;Accessibility basics&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Use a real &lt;code&gt;button&lt;/code&gt; for the toggle, with &lt;code&gt;type=&quot;button&quot;&lt;/code&gt;, so the keyboard can reach it&lt;/li&gt;
&lt;li&gt;Label it with &lt;code&gt;aria-label&lt;/code&gt;, and update the label with the state&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;aria-expanded&lt;/code&gt; and &lt;code&gt;aria-controls&lt;/code&gt; so assistive technology knows whether the menu is open&lt;/li&gt;
&lt;li&gt;Close the menu with the Escape key&lt;/li&gt;
&lt;li&gt;Give each &lt;code&gt;nav&lt;/code&gt; its own label, so a screen reader user can tell them apart&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;use-it-in-the-layout&quot;&gt;Use it in the layout&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/routes/_appLayout.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router';
import Header from '@/components/Header';

export const Route = createFileRoute('/_appLayout')({
  component: AppLayout,
});

function AppLayout() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;main className=&quot;mx-auto max-w-6xl px-6 py-8&quot;&amp;gt;
        &amp;lt;Outlet /&amp;gt;
      &amp;lt;/main&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;try-it&quot;&gt;Try it&lt;/h4&gt;
&lt;p&gt;Open your browser's device toolbar and drag the width between 375px and 1024px. The hamburger should appear below 768px, and the inline links above it. Tap a link in the mobile menu and check that it closes.&lt;/p&gt;
&lt;h4 id=&quot;what-s-next&quot;&gt;What's next&lt;/h4&gt;
&lt;p&gt;In the next part, we'll look at slide-in animation and a dark mode toggle. If you got stuck anywhere, leave a comment on the video.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31251/responsive-navigation-in-tanstack-start-hamburger-menu-and-desktop-navbar</guid>
<pubDate>Fri, 16 Oct 2026 02:14:20 +0000</pubDate>
</item>
<item>
<title>Why Your Tailwind Classes Work in Development and Silently Vanish in Production</title>
<link>https://coderlegion.com/31201/why-your-tailwind-classes-work-in-development-and-silently-vanish-in-production</link>
<description>&lt;p&gt;Almost every Tailwind developer hits this exact bug once, usually right after a deploy, and the confusing part is that nothing about it looks wrong in the code, nothing throws an error, and it worked completely fine five minutes earlier on the same machine.&lt;/p&gt;
&lt;h3 id=&quot;the-code-that-looks-completely-reasonable&quot;&gt;The Code That Looks Completely Reasonable&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;function StatusBadge({ status }: { status: 'success' | 'warning' | 'error' }) {
  const color = status === 'success' ? 'green' : status === 'warning' ? 'yellow' : 'red';

  return (
    &amp;lt;span className={`bg-${color}-100 text-${color}-800 px-2 py-1 rounded`}&amp;gt;
      {status}
    &amp;lt;/span&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In development, this renders exactly as expected, colored badges for each status, built dynamically from a single reusable component instead of three separate hardcoded ones. Then you deploy, and every badge renders as a plain, unstyled span. No color, no padding in some setups, nothing, depending on exactly what else got purged alongside it.&lt;/p&gt;
&lt;h3 id=&quot;why-this-happens-tailwind-doesn-t-actually-execute-your-code&quot;&gt;Why This Happens: Tailwind Doesn't Actually Execute Your Code&lt;/h3&gt;
&lt;p&gt;Tailwind doesn't run your JavaScript to figure out which classes you're using. It scans your source files as plain text, looking for strings that match known utility class patterns, and only generates CSS for the classes it actually finds written out, literally, somewhere in that scanned text.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;className={`bg-${color}-100 text-${color}-800`}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tailwind's scanner sees the literal text &lt;code&gt;bg-${color}-100&lt;/code&gt;, not &lt;code&gt;bg-green-100&lt;/code&gt;, &lt;code&gt;bg-yellow-100&lt;/code&gt;, or &lt;code&gt;bg-red-100&lt;/code&gt;. Template literal interpolation happens at runtime, in the browser or on your server, long after Tailwind's build step has already finished scanning your source files and generating the final CSS. Since none of the three actual color classes your component needs ever appears as literal, complete text anywhere in your codebase, Tailwind never generates CSS for any of them, and the class names that do get applied to the DOM at runtime simply don't exist in your stylesheet at all.&lt;/p&gt;
&lt;h3 id=&quot;why-this-specifically-survives-local-development&quot;&gt;Why This Specifically Survives Local Development&lt;/h3&gt;
&lt;p&gt;This is the part that makes the bug genuinely sneaky rather than just a misunderstanding: in development mode, especially with certain dev server configurations or cached builds from before the dynamic class was introduced, you can sometimes see the correct styling anyway, either because an earlier full build still has the class generated from some other literal usage elsewhere in the project, or because of how some dev tooling handles incremental rebuilds. The failure shows up specifically in a clean production build, which scans and purges from scratch, with nothing left over from earlier development sessions to accidentally mask the problem.&lt;/p&gt;
&lt;h3 id=&quot;the-fix-always-write-complete-class-names-literally&quot;&gt;The Fix: Always Write Complete Class Names, Literally&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;function StatusBadge({ status }: { status: 'success' | 'warning' | 'error' }) {
  const styles = {
    success: 'bg-green-100 text-green-800',
    warning: 'bg-yellow-100 text-yellow-800',
    error: 'bg-red-100 text-red-800',
  };

  return (
    &amp;lt;span className={`${styles[status]} px-2 py-1 rounded`}&amp;gt;
      {status}
    &amp;lt;/span&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every complete, literal class name Tailwind needs now appears as actual text somewhere in the source file, &lt;code&gt;bg-green-100&lt;/code&gt;, &lt;code&gt;bg-yellow-100&lt;/code&gt;, &lt;code&gt;bg-red-100&lt;/code&gt;, &lt;code&gt;text-green-800&lt;/code&gt;, and so on, even though the specific one that gets applied is still chosen dynamically at runtime based on &lt;code&gt;status&lt;/code&gt;. Tailwind's scanner finds all three as literal strings during the build, generates CSS for all three, and the correct one gets applied correctly regardless of which branch of the mapping object actually executes.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-that-generalizes-beyond-this-one-example&quot;&gt;The Rule That Generalizes Beyond This One Example&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Tailwind only generates CSS for class names that exist as complete, literal text somewhere in your scanned source files, string concatenation and template literal interpolation don't count, no matter how confident the resulting string looks at runtime.&lt;/strong&gt; This applies to any dynamic styling pattern, not just color variants, conditionally built spacing classes, responsive variants built from a variable, state-based classes constructed from a prop, all of them need the complete literal class name written out somewhere Tailwind's scanner can see it, usually in a lookup object exactly like the one above, rather than assembled through string interpolation.&lt;/p&gt;
&lt;p&gt;I keep every dynamic variant mapped out explicitly like this across the component libraries in my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com&quot;&gt;Next.js and Tailwind templates&lt;/a&gt;, specifically because this exact bug is so easy to reintroduce months later when someone, often future-you, refactors a component back toward the more compact-looking interpolated version without remembering why it was written the longer way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Go check your own codebase for &lt;code&gt;className={&lt;/code&gt;...${variable}...&lt;code&gt;}&lt;/code&gt; patterns building Tailwind classes dynamically. If you find one, do a clean production build and actually look at it, don't trust how it renders in dev. Drop what you find in the comments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31201/why-your-tailwind-classes-work-in-development-and-silently-vanish-in-production</guid>
<pubDate>Thu, 15 Oct 2026 12:05:37 +0000</pubDate>
</item>
<item>
<title>Setting Up Our Frontend with TanStack Start</title>
<link>https://coderlegion.com/26506/setting-up-our-frontend-with-tanstack-start</link>
<description>&lt;p&gt;We've finished our backend series — a Directus instance running on a VPS managed by Coolify, backed by PostgreSQL and Redis, with a products collection already built and secured with roles and permissions.&lt;/p&gt;
&lt;p&gt;Now it's time to build a frontend to actually display that data. For this, we're using &lt;strong&gt;TanStack Start&lt;/strong&gt;, &lt;strong&gt;Tailwind CSS&lt;/strong&gt;, and &lt;strong&gt;shadcn/ui&lt;/strong&gt;. This post covers getting TanStack Start installed and running.&lt;/p&gt;
&lt;h3 id=&quot;what-is-tanstack-start-&quot;&gt;What Is TanStack Start?&lt;/h3&gt;
&lt;p&gt;Straight from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://tanstack.com/start/latest/docs/framework/react/overview&quot;&gt;official docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;TanStack Start is a full-stack React framework powered by TanStack Router. It provides full-document SSR, streaming, server functions, client/server builds, and more. With Vite and Rsbuild support, it's ready to develop and deploy to the hosting provider or runtime you want.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In short: it offers the same kind of full-stack capability as frameworks like Next.js or Remix, built on top of TanStack Router.&lt;/p&gt;
&lt;h3 id=&quot;creating-the-project&quot;&gt;Creating the Project&lt;/h3&gt;
&lt;p&gt;First, create a folder for your app. I'm naming mine &lt;code&gt;app_fe&lt;/code&gt; — &quot;fe&quot; for front end.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Naming convention tip:&lt;/strong&gt; I use &lt;code&gt;&amp;lt;app-name&amp;gt;_fe&lt;/code&gt; for all my frontend project folders — so an e-commerce project would be &lt;code&gt;store_fe&lt;/code&gt;. Makes the folder's purpose obvious at a glance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From inside that folder, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @tanstack/cli@latest create .
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note the trailing dot.&lt;/strong&gt; Since we created the folder ourselves beforehand, the dot tells the CLI to install directly into the current directory. Omit the dot if you'd rather the CLI create a new folder for you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;walking-through-the-setup-prompts&quot;&gt;Walking Through the Setup Prompts&lt;/h3&gt;
&lt;p&gt;The CLI will walk you through a series of choices:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Prompt &lt;/th&gt;&lt;th&gt;Selection&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Select Framework &lt;/td&gt;&lt;td&gt;React&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Select Toolchain &lt;/td&gt;&lt;td&gt;ESLint&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Select Deployment Adapter &lt;/td&gt;&lt;td&gt;None (self-hosting on our own VPS)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Include demo/example pages? &lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;What add-ons would you like? &lt;/td&gt;&lt;td&gt;shadcn/ui&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Initialize a new git repository? &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Continue with these settings? &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Wait for the install to finish.&lt;/p&gt;
&lt;h3 id=&quot;running-the-app&quot;&gt;Running the App&lt;/h3&gt;
&lt;p&gt;Navigate into your app directory (if you aren't already there) and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open your browser to &lt;code&gt;http://localhost:3000&lt;/code&gt;. Congratulations — your TanStack Start app is up and running.&lt;/p&gt;
&lt;h3 id=&quot;editing-the-home-page&quot;&gt;Editing the Home Page&lt;/h3&gt;
&lt;p&gt;Open &lt;code&gt;src/routes/index.tsx&lt;/code&gt;. Delete everything inside the div, and save — your home page should now be completely blank. This file is your landing page, the first thing visitors see.&lt;/p&gt;
&lt;p&gt;Add an &lt;code&gt;h1&lt;/code&gt; tag inside the empty div:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save, and you'll see &quot;Home&quot; rendered on the page. Now let's add some Tailwind styling:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;h1 className=&quot;font-bold text-5xl text-slate-700&quot;&amp;gt;Home&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save again — notice Tailwind works right out of the box, no extra configuration needed.&lt;/p&gt;
&lt;h3 id=&quot;file-based-routing&quot;&gt;File-Based Routing&lt;/h3&gt;
&lt;p&gt;TanStack Start runs on TanStack Router, which uses &lt;strong&gt;file-based routing&lt;/strong&gt;. Every file added to the &lt;code&gt;routes&lt;/code&gt; folder automatically becomes a navigable page.&lt;/p&gt;
&lt;h4 id=&quot;adding-an-about-page&quot;&gt;Adding an About Page&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;src/routes/about.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;div className=&quot;p-8&quot;&amp;gt;
  &amp;lt;h1 className=&quot;font-bold text-5xl text-slate-700&quot;&amp;gt;About&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save, then visit &lt;code&gt;http://localhost:3000/about&lt;/code&gt;. Notice the home page lives at &lt;code&gt;localhost:3000/&lt;/code&gt;, and the about page lives at &lt;code&gt;localhost:3000/about&lt;/code&gt; — TanStack Start handled that routing automatically, purely based on the filename.&lt;/p&gt;
&lt;h4 id=&quot;adding-a-products-page&quot;&gt;Adding a Products Page&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;src/routes/products.tsx&lt;/code&gt; the same way:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;div className=&quot;p-8&quot;&amp;gt;
  &amp;lt;h1 className=&quot;font-bold text-5xl text-slate-700&quot;&amp;gt;Products&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visit &lt;code&gt;localhost:3000/products&lt;/code&gt;, and there's your new page. That's as simple as basic routing gets — there's plenty more you can do with nested and dynamic routes, which we'll cover as it becomes relevant.&lt;/p&gt;
&lt;h3 id=&quot;what-is-that-root-tsx-file-&quot;&gt;What Is That &lt;code&gt;__root.tsx&lt;/code&gt; File?&lt;/h3&gt;
&lt;p&gt;You've probably noticed a file called &lt;code&gt;__root.tsx&lt;/code&gt; sitting in your routes folder. Here's the simplest way to think about it:&lt;/p&gt;
&lt;p&gt;Picture any large website — Amazon, YouTube, anything. Every page has a header up top and often a footer at the bottom that never change, no matter what page you're on. But the content in the middle changes depending on where you navigated.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;__root.tsx&lt;/code&gt; is the file that builds that &quot;never changes&quot; part. It's the frame around every page in your app. Every route you build — home, about, products — gets rendered &lt;em&gt;inside&lt;/em&gt; this one file. Write it once, and every page automatically inherits it.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;A word of caution:&lt;/strong&gt; this file is central to how TanStack Router works under the hood. Avoid modifying it until you understand exactly what each piece does — we'll take a closer look at it in an upcoming post, exactly when it becomes relevant to what we're building.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;In this part, we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installed and configured a new TanStack Start project&lt;/li&gt;
&lt;li&gt;Ran it locally and confirmed Tailwind works out of the box&lt;/li&gt;
&lt;li&gt;Learned file-based routing by building About and Products pages&lt;/li&gt;
&lt;li&gt;Got introduced to &lt;code&gt;__root.tsx&lt;/code&gt;, the file behind every page in the app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That wraps up our frontend setup. Next up, we'll start pulling real data from our Directus backend into this frontend — connecting everything we've built across this series.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Found this useful? Follow for more Directus and TanStack Start tutorials, or check out the video version above.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26506/setting-up-our-frontend-with-tanstack-start</guid>
<pubDate>Tue, 08 Sep 2026 15:45:44 +0000</pubDate>
</item>
<item>
<title>Why I Replaced Tailwind CSS With Vanilla CSS</title>
<link>https://coderlegion.com/24214/why-i-replaced-tailwind-css-with-vanilla-css</link>
<description>&lt;p&gt;I removed Tailwind CSS from this site and rewrote every style by hand in vanilla CSS. The reason is short: modern CSS now does natively what I was pulling Tailwind in to get. Cascade layers, custom properties, nesting, and &lt;code&gt;:has()&lt;/code&gt; all ship in every current browser, and Tailwind v4 is built on those exact primitives. Once the browser speaks the language directly, a build step that generates it for you reads as overhead rather than leverage. When I &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/the-new-website&quot;&gt;rebuilt this site in December 2025&lt;/a&gt; I reached for &lt;a rel=&quot;nofollow&quot; href=&quot;https://tailwindcss.com&quot;&gt;Tailwind v4&lt;/a&gt; on instinct. This month I took it back out.&lt;/p&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The features I used Tailwind for (design tokens, nesting, scoping, a consistent scale) are now native CSS or handled by Astro's scoped styles.&lt;/li&gt;
&lt;li&gt;Tailwind v4 is itself built on native cascade layers, custom properties, and &lt;code&gt;color-mix()&lt;/code&gt;, with &lt;a rel=&quot;nofollow&quot; href=&quot;https://lightningcss.dev&quot;&gt;Lightning CSS&lt;/a&gt; doing the build. I was shipping an engine to generate CSS the browser already understands.&lt;/li&gt;
&lt;li&gt;The migration dropped three Tailwind packages, moved global styles into native &lt;code&gt;@layer&lt;/code&gt;s, and put every component's styles in Astro's scoped &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; blocks.&lt;/li&gt;
&lt;li&gt;The site stayed &lt;a rel=&quot;nofollow&quot; href=&quot;https://web.dev/baseline&quot;&gt;Baseline Widely available&lt;/a&gt; the whole way.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-replace-tailwind-at-all-&quot;&gt;Why replace Tailwind at all?&lt;/h3&gt;
&lt;p&gt;Because the job Tailwind did for me is now built into the platform. When I list what I actually reached for it to solve, every item has a native answer today:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;What I used Tailwind for                 &lt;/th&gt;&lt;th&gt;The vanilla replacement                   &lt;/th&gt;&lt;th&gt;Baseline Widely since&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Style layering and predictable overrides &lt;/td&gt;&lt;td&gt;&lt;code&gt;@layer&lt;/code&gt; cascade layers                   &lt;/td&gt;&lt;td&gt;Sept 2024&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Design tokens and theming                &lt;/td&gt;&lt;td&gt;Custom properties (&lt;code&gt;--x&lt;/code&gt;)                 &lt;/td&gt;&lt;td&gt;long-standing&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nesting without a preprocessor           &lt;/td&gt;&lt;td&gt;Native CSS nesting (&lt;code&gt;&amp;amp;&lt;/code&gt;)                  &lt;/td&gt;&lt;td&gt;June 2026&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Parent and state selectors               &lt;/td&gt;&lt;td&gt;&lt;code&gt;:has()&lt;/code&gt;                                  &lt;/td&gt;&lt;td&gt;June 2026&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Component-scoped responsive rules        &lt;/td&gt;&lt;td&gt;Container queries and Astro scoped styles &lt;/td&gt;&lt;td&gt;Aug 2025&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The dates are the point. &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting&quot;&gt;CSS nesting&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/:has&quot;&gt;&lt;code&gt;:has()&lt;/code&gt;&lt;/a&gt; only crossed into Baseline Widely availability around June 2026, roughly two months before I did this. &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@layer&quot;&gt;Cascade layers&lt;/a&gt; got there in September 2024, &lt;code&gt;color-mix()&lt;/code&gt; in November 2025, size container queries in August 2025. The Chrome team's &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.chrome.com/blog/css-wrapped-2025&quot;&gt;CSS Wrapped 2025&lt;/a&gt; counted 22 new CSS features landing in Chrome in a single year. The gap Tailwind was filling closed while I wasn't looking, and 2026 is the year it actually finished closing.&lt;/p&gt;
&lt;p&gt;None of this makes Tailwind bad. It makes it optional for what I'm doing here: a single-author content site where nobody but me touches the CSS.&lt;/p&gt;
&lt;h3 id=&quot;tailwind-v4-already-runs-on-the-css-i-switched-to&quot;&gt;Tailwind v4 already runs on the CSS I switched to&lt;/h3&gt;
&lt;p&gt;The thing that tipped me over was reading how &lt;a rel=&quot;nofollow&quot; href=&quot;https://tailwindcss.com/blog/tailwindcss-v4&quot;&gt;Tailwind v4&lt;/a&gt; works under the hood. Its January 2025 rewrite is explicit that it is built on modern CSS. The announcement lists &quot;native cascade layers,&quot; &quot;registered custom properties,&quot; &quot;&lt;code&gt;color-mix()&lt;/code&gt;,&quot; and logical properties as the foundation. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://tailwindcss.com/blog/tailwindcss-v4-alpha&quot;&gt;earlier post introducing its Oxide engine&lt;/a&gt; is blunter about the plumbing: the engine is &quot;up to 10x faster,&quot; and &quot;the only thing the new engine depends on is Lightning CSS.&quot;&lt;/p&gt;
&lt;p&gt;Read that back with a migration in mind. Tailwind v4 generates native cascade layers, sets custom properties, and runs the output through Lightning CSS for prefixing and minification. That is the same stack I now write directly. I was running an engine whose job was to emit CSS features my target browsers already implement. For an app with dozens of engineers and a shared design system, that engine buys you consistency and guardrails worth paying for. For this site, it was a generator sitting between me and CSS I could just write.&lt;/p&gt;
&lt;p&gt;There is a small irony worth stating plainly. Tailwind v4 leans on &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@property&quot;&gt;registered custom properties&lt;/a&gt; (&lt;code&gt;@property&lt;/code&gt;), which won't reach Baseline Widely availability until around January 2027, so Tailwind ships its own fallbacks for it. My hand-written CSS holds a stricter bar than the framework I removed.&lt;/p&gt;
&lt;h3 id=&quot;what-the-migration-actually-looked-like&quot;&gt;What the migration actually looked like&lt;/h3&gt;
&lt;p&gt;The site is &lt;a rel=&quot;nofollow&quot; href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt;, so it splits cleanly in two: global styles in &lt;code&gt;src/styles&lt;/code&gt;, and per-component styles in each &lt;code&gt;.astro&lt;/code&gt; file's scoped &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; block. I rewrote both.&lt;/p&gt;
&lt;h4 id=&quot;cascade-layers-replace-the-tailwind-import&quot;&gt;Cascade layers replace the Tailwind import&lt;/h4&gt;
&lt;p&gt;The old entry point started with Tailwind's import, its typography plugin, and a &lt;code&gt;@custom-variant&lt;/code&gt; line for every palette so theme selectors would compile:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import &quot;tailwindcss&quot;;
@plugin &quot;@tailwindcss/typography&quot;;

@custom-variant tokyo-night (&amp;amp;:where([data-theme=&quot;tokyo-night&quot;]));
@custom-variant dracula (&amp;amp;:where([data-theme=&quot;dracula&quot;]));
/* ...thirteen more palettes... */
@custom-variant dark (&amp;amp;:where([data-mode=&quot;dark&quot;], [data-mode=&quot;dark&quot;] *));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fifteen &lt;code&gt;@custom-variant&lt;/code&gt; declarations, one per palette, plus one for dark mode, all to teach the framework about selectors the browser already understands. The replacement is one line of real CSS that declares the cascade order, then a plain file per layer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@layer reset, tokens, base, primitives, prose, utilities;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ordering the layers by hand turned out to be the feature I most underrated in Tailwind. Layers win by declaration order regardless of selector specificity, so the reset can be dead simple, tokens can sit under everything, and utilities win last without a single &lt;code&gt;!important&lt;/code&gt;. I get the exact override behavior Tailwind gave me, spelled out in one readable statement.&lt;/p&gt;
&lt;h4 id=&quot;design-tokens-are-just-custom-properties&quot;&gt;Design tokens are just custom properties&lt;/h4&gt;
&lt;p&gt;Theming didn't really change, which is the tell. Under Tailwind the palettes were already CSS variables swapped by a &lt;code&gt;data-theme&lt;/code&gt; attribute; the framework just wrapped them in &lt;code&gt;@theme&lt;/code&gt; and &lt;code&gt;@custom-variant&lt;/code&gt;. Stripping that wrapper left plain rules:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root,
[data-theme=&quot;catppuccin-mocha&quot;] {
  --bg: #11111b;
  --ink: #cdd6f4;
  --accent: #89b4fa;
  /* ...the rest of the reduced token set... */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sixteen of those blocks, one per theme, in a &lt;code&gt;tokens&lt;/code&gt; layer. The switcher sets &lt;code&gt;data-theme&lt;/code&gt; on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, the variables cascade in, and syntax highlighting tracks them through more variables. No config object, no plugin API, just the custom properties that were doing the work all along.&lt;/p&gt;
&lt;h4 id=&quot;components-from-class-lists-to-scoped-styles&quot;&gt;Components: from class lists to scoped styles&lt;/h4&gt;
&lt;p&gt;This is where the day-to-day difference shows. Here is the card component before, as a Tailwind class list:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;article
  class:list={[
    &quot;relative rounded border bg-card p-4 sm:p-6&quot;,
    &quot;has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ring&quot;,
    className,
  ]}
  {...props}
&amp;gt;
  &amp;lt;slot /&amp;gt;
&amp;lt;/article&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And after, as scoped CSS in the same file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;article class={className} {...props}&amp;gt;
  &amp;lt;slot /&amp;gt;
&amp;lt;/article&amp;gt;

&amp;lt;style&amp;gt;
  article {
    position: relative;
    border-radius: var(--radius);
    border-width: 1px;
    background-color: var(--bg-elevated);
    padding: 1rem;

    &amp;amp;:has(:focus-visible) {
      outline: 2px solid var(--accent);
      outline-offset: 2px;
    }
  }

  @media (width &amp;gt;= 40rem) {
    article {
      padding: 1.5rem;
    }
  }
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The markup goes quiet and the intent moves into CSS that reads like CSS. Native nesting holds the &lt;code&gt;:has()&lt;/code&gt; state rule next to the base rule, and a normal media query handles the responsive padding that &lt;code&gt;sm:p-6&lt;/code&gt; used to encode. &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.astro.build/en/guides/styling/#scoped-styles&quot;&gt;Astro scopes these styles&lt;/a&gt; through a generated &lt;code&gt;data-astro-cid&lt;/code&gt; attribute, so a bare &lt;code&gt;article&lt;/code&gt; selector is enough and the element needs no class of its own. And because scoped styles compile unlayered, they beat the global layers without any specificity games. That last property is what made the migration safe to do component by component: a scoped rule always wins over a global primitive, so I could move one file at a time without the two systems fighting.&lt;/p&gt;
&lt;h4 id=&quot;lightning-css-does-the-vendor-work&quot;&gt;Lightning CSS does the vendor work&lt;/h4&gt;
&lt;p&gt;The reflex worry with dropping a framework is that you inherit its chores: prefixing, downleveling new syntax, minifying. I inherited none of them, because &lt;a rel=&quot;nofollow&quot; href=&quot;https://lightningcss.dev&quot;&gt;Lightning CSS&lt;/a&gt; was already in the pipeline. It's a Rust parser, transformer, and minifier by &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/devongovett&quot;&gt;Devon Govett&lt;/a&gt;, and it is the same tool Tailwind v4's Oxide engine depends on. Astro's Rust compiler &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/astro-7&quot;&gt;uses it too&lt;/a&gt;, so turning it on for my own CSS was one line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;vite: {
  css: { transformer: &quot;lightningcss&quot; },
  build: {
    cssTarget: [&quot;chrome111&quot;, &quot;edge111&quot;, &quot;firefox114&quot;, &quot;safari16.4&quot;, &quot;ios16.4&quot;],
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It adds the handful of &lt;code&gt;-webkit-&lt;/code&gt; prefixes my target browsers still need, lowers nesting for the stragglers, and minifies, all against a Baseline target set. The &lt;code&gt;package.json&lt;/code&gt; diff was the satisfying part: out went &lt;code&gt;tailwindcss&lt;/code&gt;, &lt;code&gt;@tailwindcss/vite&lt;/code&gt;, and &lt;code&gt;@tailwindcss/typography&lt;/code&gt;; in went a single &lt;code&gt;lightningcss&lt;/code&gt; dev dependency that was effectively already there. The long-form article styling followed the same instinct I wrote about in &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/shadcn-typeset-vs-tailwind-prose&quot;&gt;shadcn/typeset vs Tailwind Typography&lt;/a&gt;: own the file, delete the rules you don't want.&lt;/p&gt;
&lt;h3 id=&quot;did-the-site-stay-baseline-compliant-&quot;&gt;Did the site stay Baseline compliant?&lt;/h3&gt;
&lt;p&gt;Yes, and that was a hard constraint, not a nice-to-have. Every feature this site ships has to be &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/baseline-2026-web-platform-apis&quot;&gt;Baseline Widely available&lt;/a&gt;, interoperable across the core browsers for at least 30 months. That's exactly why the timing worked: the two features I lean on hardest, native nesting and &lt;code&gt;:has()&lt;/code&gt;, only reached Widely availability around June 2026. If I'd tried this a year earlier they'd have been Baseline Newly at best, and the card component above would have needed an &lt;code&gt;@supports&lt;/code&gt; gate with a full fallback. Doing it now, they're safe to use unguarded. The one place I still gate behind &lt;code&gt;@supports&lt;/code&gt; is the anchor-positioned reading indicator in the table of contents, which is genuinely still Newly available.&lt;/p&gt;
&lt;h3 id=&quot;do-you-still-need-tailwind-in-2026-&quot;&gt;Do you still need Tailwind in 2026?&lt;/h3&gt;
&lt;p&gt;Only if someone other than you owns the CSS. That is the honest split, and it decides whether replacing Tailwind with vanilla CSS is worth it for you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A team with a shared design system:&lt;/strong&gt; probably keep Tailwind. The constraints that feel like overhead on a solo site are the whole point when twenty people style the same product. Consistency you don't have to enforce by review is worth a build step.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A solo project or a content site:&lt;/strong&gt; the trade-off has flipped. You get design tokens from custom properties, override control from cascade layers, nesting and state selectors natively, and scoping from your framework's components. The framework's value is real but no longer necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm not claiming Tailwind lost. &lt;a rel=&quot;nofollow&quot; href=&quot;https://kevinpowell.co&quot;&gt;Kevin Powell&lt;/a&gt; has spent years arguing that learning CSS itself pays off, and the platform finally caught up to make that cheap. Tailwind v4 is a genuinely good piece of engineering, built on the same modern CSS that made me comfortable leaving it. For this site, hand-written CSS wired to theme variables is less code, one fewer dependency, and output I can read top to bottom. That's the same own-the-source call I keep making here, and this time the browser did most of the work for me.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24214/why-i-replaced-tailwind-css-with-vanilla-css</guid>
<pubDate>Thu, 06 Aug 2026 12:39:32 +0000</pubDate>
</item>
<item>
<title>Tailwind CSS for Beginners: From Zero to Your First Component</title>
<link>https://coderlegion.com/22619/tailwind-css-for-beginners-from-zero-to-your-first-component</link>
<description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The first time I saw a Tailwind CSS class list — something like flex items-center justify-between p-4 bg-slate-800 rounded-lg shadow-md — I genuinely thought it looked messy. Why not just write regular CSS?&lt;/p&gt;
&lt;p&gt;Then I actually used it on a real project, and within a week I understood why so many developers (myself included now) switched permanently. You stop context-switching between HTML and a separate CSS file, you stop naming classes like .card-wrapper-inner-2, and you build UI noticeably faster.&lt;/p&gt;
&lt;p&gt;This is the guide I wish I had when I started.&lt;/p&gt;
&lt;h2 id=&quot;what-tailwind-css-actually-is&quot;&gt;What Tailwind CSS Actually Is&lt;/h2&gt;
&lt;p&gt;Tailwind is a utility-first CSS framework. Instead of writing custom CSS classes, you compose your design directly in your HTML using small, single-purpose utility classes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Regular CSS:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.button {
  padding: 8px 16px;
  background-color: #3b82f6;
  border-radius: 8px;
  color: white;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Tailwind:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button class=&quot;px-4 py-2 bg-blue-500 rounded-lg text-white&quot;&amp;gt;
  Click Me
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same result. No separate CSS file needed for this button.&lt;/p&gt;
&lt;h3 id=&quot;setting-it-up-react-vite-&quot;&gt;Setting It Up (React + Vite)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm create vite@latest my-app -- --template react
cd my-app
npm install tailwindcss @tailwindcss/vite
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;In vite.config.js:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tailwindcss from '@tailwindcss/vite'

export default {
  plugins: [tailwindcss()],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;In your main CSS file:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@import &quot;tailwindcss&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it — no separate config file required for modern Tailwind setups. Restart your dev server and you're ready to use utility classes anywhere.&lt;/p&gt;
&lt;h2 id=&quot;core-concepts-you-need-to-know&quot;&gt;Core Concepts You Need to Know&lt;/h2&gt;
&lt;h3 id=&quot;1-spacing&quot;&gt;1. Spacing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;p-4&lt;/strong&gt; = padding on all sides, &lt;strong&gt;px-4&lt;/strong&gt; = horizontal padding only, &lt;strong&gt;py-4&lt;/strong&gt; = vertical only, &lt;strong&gt;m-4&lt;/strong&gt; = margin. The number is a spacing scale &lt;strong&gt;(4 = 1rem = 16px)&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;2-flexbox-amp-grid-utilities&quot;&gt;2. Flexbox &amp;amp; Grid utilities&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;flex, items-center, justify-between, grid, grid-cols-3, gap-4&lt;/strong&gt; — everything from the Flexbox/Grid article, just as classes.&lt;/p&gt;
&lt;h3 id=&quot;3-responsive-design&quot;&gt;3. Responsive Design&lt;/h3&gt;
&lt;p&gt;Prefix any class with a breakpoint: &lt;strong&gt;md:flex-row&lt;/strong&gt; means &quot;apply this on medium screens and up.&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;flex flex-col md:flex-row gap-4&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This stacks items vertically on mobile, then switches to a row on tablets and larger.&lt;/p&gt;
&lt;h3 id=&quot;4-hover-amp-state-variants&quot;&gt;4. Hover &amp;amp; State Variants&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button class=&quot;bg-blue-500 hover:bg-blue-600 transition&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;hover:&lt;/strong&gt; applies only on hover. Same pattern works for &lt;strong&gt;focus:, active:, disabled:&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;building-your-first-component-a-pricing-card&quot;&gt;Building Your First Component: A Pricing Card&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;max-w-sm mx-auto bg-slate-800 rounded-2xl p-6 shadow-lg&quot;&amp;gt;
  &amp;lt;h3 class=&quot;text-xl font-bold text-white mb-2&quot;&amp;gt;Starter Plan&amp;lt;/h3&amp;gt;
  &amp;lt;p class=&quot;text-slate-400 mb-4&quot;&amp;gt;Perfect for small projects&amp;lt;/p&amp;gt;
  &amp;lt;p class=&quot;text-3xl font-bold text-white mb-6&quot;&amp;gt;$9&amp;lt;span class=&quot;text-base font-normal text-slate-400&quot;&amp;gt;/mo&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;button class=&quot;w-full py-2 bg-blue-500 hover:bg-blue-600 rounded-lg text-white font-medium transition&quot;&amp;gt;
    Get Started
  &amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice what's happening: &lt;strong&gt;max-w-sm mx-auto&lt;/strong&gt; centers and constrains the card width, &lt;strong&gt;rounded-2xl&lt;/strong&gt; and &lt;strong&gt;shadow-lg&lt;/strong&gt; give it depth, and everything is readable directly from the class names — no jumping to a separate file to understand the styling.&lt;/p&gt;
&lt;h2 id=&quot;common-beginner-mistakes&quot;&gt;Common Beginner Mistakes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Fighting the spacing scale. Don't reach for custom pixel values&lt;br&gt;
immediately — Tailwind's default scale (4, 8, 12, 16...) covers 95%&lt;br&gt;
of real use cases.&lt;/li&gt;
&lt;li&gt;Not using @apply for repeated patterns. If you're copy-pasting the&lt;br&gt;
same 8 classes everywhere, extract them into a component instead of&lt;br&gt;
a CSS class — that's the React way to do it, not @apply.&lt;/li&gt;
&lt;li&gt;Ignoring the official docs. Tailwind's docs (tailwindcss.com) are&lt;br&gt;
genuinely excellent — searchable, example-driven, and the fastest&lt;br&gt;
way to find the exact class you need.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Tailwind feels weird for about a week, then it feels like the only sane way to write CSS. The speed difference is real — I build UI noticeably faster now than when I wrote custom CSS files for every component.&lt;/p&gt;
&lt;p&gt;Start small: rebuild one existing component using Tailwind instead of your old CSS, and see how it feels.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Muhammad Farhan&lt;/strong&gt; is a Frontend Developer specializing in React.js and Tailwind CSS, based in Dera Ismail Khan, Pakistan.&lt;br&gt;
Portfolio: muhammad-farhan-dev.github.io/muhammadfarhan.dev&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22619/tailwind-css-for-beginners-from-zero-to-your-first-component</guid>
<pubDate>Tue, 21 Jul 2026 05:00:00 +0000</pubDate>
</item>
<item>
<title>shadcn/typeset vs Tailwind Typography for Markdown</title>
<link>https://coderlegion.com/22961/shadcn-typeset-vs-tailwind-typography-for-markdown</link>
<description>&lt;p&gt;You render some markdown, get back a pile of unstyled HTML, and then you style it. Headings, paragraphs, lists, tables, code. You do it for the blog. Then you do it again for the docs. Then a third time for the chat UI that streams model output token by token. Every context, the same elements, slightly different rules.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/shadcn&quot;&gt;shadcn&lt;/a&gt; shipped a take on this on July 10, 2026, called &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/docs/typeset&quot;&gt;shadcn/typeset&lt;/a&gt;. It's worth a look even if you don't use the rest of shadcn/ui, because it isn't a component and it isn't a CLI install. It's one CSS file you copy into your project and own outright.&lt;/p&gt;
&lt;h3 id=&quot;what-is-shadcn-typeset-&quot;&gt;What is shadcn/typeset?&lt;/h3&gt;
&lt;p&gt;Typeset is a styling system for rendered HTML and markdown, delivered as a single CSS file. You wrap your content in a &lt;code&gt;.typeset&lt;/code&gt; container and everything inside it gets styled: headings, paragraphs, lists, tables, code, blockquotes, links, footnotes, even MathML and &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;div className=&quot;typeset typeset-docs&quot;&amp;gt;
  &amp;lt;YourMarkdownRenderer&amp;gt;{content}&amp;lt;/YourMarkdownRenderer&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.typeset&lt;/code&gt; turns the styles on. &lt;code&gt;.typeset-docs&lt;/code&gt; is a preset, a small class you layer on top to tune the look for a specific context. You can define as many presets as you have contexts: one for the blog, one for the docs, one for chat.&lt;/p&gt;
&lt;p&gt;The thing to internalize up front: there's no npm package and no config layer. As the &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/docs/changelog/2026-07-typeset&quot;&gt;changelog&lt;/a&gt; puts it, &quot;the file lives in your project,&quot; nothing to update, nothing to work around. If a heading annoys you, you open the file and change the rule. That's the whole distribution model.&lt;/p&gt;
&lt;p&gt;This is a different thing from shadcn/ui's &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/docs/components/base/typography&quot;&gt;Typography component&lt;/a&gt;, which gives you styled React heading and paragraph components to author by hand. Typeset styles HTML you didn't write, the output of a markdown renderer.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-install-shadcn-typeset-&quot;&gt;How do you install shadcn/typeset?&lt;/h3&gt;
&lt;p&gt;You don't run a CLI. As of this release there's no &lt;code&gt;npx shadcn@latest add typeset&lt;/code&gt;, and expecting one will send you looking for a command that doesn't exist.&lt;/p&gt;
&lt;p&gt;Instead you build the file in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/typeset&quot;&gt;interactive builder&lt;/a&gt;, pick your sizes and fonts, and copy the generated &lt;code&gt;typeset.css&lt;/code&gt; into your project. Then you import it after Tailwind:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import &quot;tailwindcss&quot;;
@import &quot;./typeset.css&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import order matters, and I'll come back to why in a moment. Once it's in, the wrapper from the first snippet is all you need.&lt;/p&gt;
&lt;h3 id=&quot;size-leading-and-flow&quot;&gt;Size, leading, and flow&lt;/h3&gt;
&lt;p&gt;Typeset condenses typographic tuning into three CSS variables it calls &quot;rhythm,&quot; and everything else derives from them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.typeset {
  --typeset-size: 1em;      /* base font size */
  --typeset-leading: 1.75;  /* line-height */
  --typeset-flow: 1.25em;   /* vertical space between blocks */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A preset is just those three variables set to different values under a class name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.typeset-chat {
  --typeset-flow: 1em;
  --typeset-leading: 1.6;
}
.typeset-docs {
  --typeset-size: 15px;
  --typeset-flow: 1.5em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because it's all CSS custom properties, you can override a single value inline without touching the file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;article className=&quot;typeset [--typeset-flow:1.75em]&quot;&amp;gt;{content}&amp;lt;/article&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sizing is container-relative rather than a fixed &lt;code&gt;rem&lt;/code&gt; scale. Below &lt;code&gt;48rem&lt;/code&gt; the base size gets a &lt;code&gt;1.125x&lt;/code&gt; bump and then settles back to the raw value on larger screens, so text reads comfortably on a phone without a separate small-screen variant. Colors and borders pull from your app's theme tokens (&lt;code&gt;--color-foreground&lt;/code&gt;, &lt;code&gt;--color-muted-foreground&lt;/code&gt;, &lt;code&gt;--color-border&lt;/code&gt;, &lt;code&gt;--radius&lt;/code&gt;) with sensible fallbacks, so dark mode just works when your tokens flip. There's no second inverted palette to maintain.&lt;/p&gt;
&lt;p&gt;One deliberate omission: Typeset doesn't set a &lt;code&gt;max-width&lt;/code&gt;. Your layout owns the measure. The builder's Measure control adds a &lt;code&gt;max-width&lt;/code&gt; to the wrapper if you want it, but the stylesheet itself stays out of your layout's business.&lt;/p&gt;
&lt;h3 id=&quot;why-your-tailwind-utilities-win-without-important&quot;&gt;Why your Tailwind utilities win without !important&lt;/h3&gt;
&lt;p&gt;Here's the detail I liked most. Typeset lives in the CSS &lt;code&gt;@components&lt;/code&gt; layer and uses zero-specificity &lt;code&gt;:where()&lt;/code&gt; selectors for every element rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@layer components {
  .typeset {
    &amp;amp; :where(h1) { font-size: 1.75em; /* ... */ }
    &amp;amp; :where(p)  { margin-block-start: var(--typeset-flow); }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The override comes from cascade layers, not specificity. Typeset's rules live in the &lt;code&gt;@components&lt;/code&gt; layer, and Tailwind's utilities live in the &lt;code&gt;@utilities&lt;/code&gt; layer, which is declared after it. When rules in two different layers conflict, the later layer wins outright, no matter how specific either selector is. So a utility beats a Typeset rule with no &lt;code&gt;!important&lt;/code&gt; and no special modifier syntax. Slap &lt;code&gt;.text-3xl&lt;/code&gt; on a heading and it just wins.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;:where()&lt;/code&gt; does a related but separate job: it holds every element rule at zero specificity, so an override that isn't already shielded by a later layer, like plain CSS you write yourself or a rule in the same layer, can still win without a specificity fight. That's also why import order matters. Importing Tailwind first establishes its layer order, which puts &lt;code&gt;@utilities&lt;/code&gt; after Typeset's &lt;code&gt;@components&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to pull a whole subtree out, there's an escape hatch: &lt;code&gt;.not-typeset&lt;/code&gt; (or the &lt;code&gt;data-not-typeset&lt;/code&gt; attribute), which cascades to descendants including any nested &lt;code&gt;.typeset&lt;/code&gt; container.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Card className=&quot;not-typeset&quot;&amp;gt;Untouched component.&amp;lt;/Card&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;shadcn is candid that both of these ideas, the &lt;code&gt;:where()&lt;/code&gt; guard and the opt-out class, are borrowed from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tailwindlabs/tailwindcss-typography&quot;&gt;&lt;code&gt;@tailwindcss/typography&lt;/code&gt;&lt;/a&gt;, which has a &lt;code&gt;.not-prose&lt;/code&gt; of its own. Which brings us to the comparison you actually clicked for.&lt;/p&gt;
&lt;h3 id=&quot;shadcn-typeset-vs-tailwind-typography&quot;&gt;shadcn/typeset vs Tailwind Typography&lt;/h3&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tailwindlabs/tailwindcss-typography&quot;&gt;Tailwind CSS Typography&lt;/a&gt; plugin, and the &lt;code&gt;.prose&lt;/code&gt; class it hands you, has been the default answer for styling rendered markdown for years, and it's still excellent at what it was built for. Typeset isn't a drop-in replacement or a wrapper around it. It's a different set of trade-offs. The docs lay them out directly:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Tailwind Typography (&lt;code&gt;.prose&lt;/code&gt;) &lt;/th&gt;&lt;th&gt;Typeset&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Sizing &lt;/td&gt;&lt;td&gt;Fixed &lt;code&gt;rem&lt;/code&gt; scale, &lt;code&gt;.prose-sm&lt;/code&gt; to &lt;code&gt;.prose-2xl&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Relative to the container, any size&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Dark mode &lt;/td&gt;&lt;td&gt;&lt;code&gt;.prose-invert&lt;/code&gt;, a second palette &lt;/td&gt;&lt;td&gt;Your tokens flip, nothing to add&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Overrides &lt;/td&gt;&lt;td&gt;&lt;code&gt;prose-a:&lt;/code&gt;, &lt;code&gt;prose-headings:&lt;/code&gt; modifier API &lt;/td&gt;&lt;td&gt;Plain utilities and CSS win&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Streaming &lt;/td&gt;&lt;td&gt;No append-stability contract &lt;/td&gt;&lt;td&gt;Designed for stable appends&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Distribution &lt;/td&gt;&lt;td&gt;npm plugin, generated CSS &lt;/td&gt;&lt;td&gt;One CSS file you own&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The way I'd frame it: &lt;code&gt;.prose&lt;/code&gt; is a well-designed dependency you configure, and Typeset is a starting point you edit. If you like the modifier API and want a maintained plugin tracking Tailwind releases, &lt;code&gt;.prose&lt;/code&gt; is still a great pick. If you'd rather own the file and delete the rules you don't want, Typeset fits that instinct better. It's the same philosophy as the rest of shadcn/ui: copy the code in, make it yours.&lt;/p&gt;
&lt;h3 id=&quot;what-makes-it-streaming-safe-&quot;&gt;What makes it streaming-safe?&lt;/h3&gt;
&lt;p&gt;This is the part that made Typeset feel designed for 2026 rather than 2020. If you've built a chat UI that renders an LLM's markdown as it streams, you've watched the layout twitch as each token lands. Typeset treats that as a first-class constraint.&lt;/p&gt;
&lt;p&gt;The rule is that there are no forward-looking selectors in the layout. &lt;code&gt;:last-child&lt;/code&gt;, &lt;code&gt;:has()&lt;/code&gt;, and &lt;code&gt;:empty&lt;/code&gt; are deliberately kept out of spacing rules, because what they match changes as content appends, which restyles blocks that were already on screen. Spacing flows in one direction only, using &lt;code&gt;margin-block-start&lt;/code&gt; and never &lt;code&gt;margin-block-end&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&amp;amp; :where(p) {
  margin-block-start: var(--typeset-flow);
  margin-block-end: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same discipline shows up in tables: the row separators sit on the cells rather than on &lt;code&gt;tr:last-child&lt;/code&gt;, so appending a row never re-runs a &lt;code&gt;:last-child&lt;/code&gt; match and shifts the borders above it. When you add a new block to a Typeset container, nothing before it moves. That's a genuinely hard property to get right by hand, and it's baked into the stylesheet.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs-you&quot;&gt;What it costs you&lt;/h3&gt;
&lt;p&gt;Two things are worth knowing before you commit.&lt;/p&gt;
&lt;p&gt;First, the stylesheet leans on modern CSS, though most of it is safe by now: &lt;code&gt;color-mix()&lt;/code&gt; and &lt;code&gt;oklch()&lt;/code&gt; colors have been widely supported since 2023, and logical properties like &lt;code&gt;margin-block-start&lt;/code&gt; work everywhere. The real outlier is &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/margin-trim&quot;&gt;&lt;code&gt;margin-trim&lt;/code&gt;&lt;/a&gt;, which as of 2026 still ships only in Safari and other WebKit browsers (16.4 and up). There's no fallback for it, so in Chrome and Firefox that rule quietly does nothing. Worth knowing before you count on the margin trimming it's meant to handle.&lt;/p&gt;
&lt;p&gt;Second, wide tables wrap to fit by default. Horizontal scroll is opt-in through a &lt;code&gt;.typeset-scroll&lt;/code&gt; wrapper you have to add in your renderer's table component or a small rehype plugin. It won't happen automatically.&lt;/p&gt;
&lt;h3 id=&quot;should-you-use-it-&quot;&gt;Should you use it?&lt;/h3&gt;
&lt;p&gt;If you're already in the shadcn/ui world and you're styling rendered markdown, especially anything that streams, Typeset is an easy yes: it's free, it's one file, and the streaming stability alone justifies it. If you're outside that ecosystem and happy with &lt;code&gt;.prose&lt;/code&gt;, there's no migration to rush. It's an alternative with a clearer answer to a couple of specific problems, not an upgrade you're missing out on.&lt;/p&gt;
&lt;p&gt;What I appreciate is the framing. shadcn keeps betting that the right unit of distribution for UI is source you own rather than a dependency you configure, and Typeset applies that bet to typography. I made a similar call &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/the-new-website&quot;&gt;rebuilding this site&lt;/a&gt;: the long-form styling here is hand-written Tailwind wired to theme variables rather than a plugin, for exactly the reason Typeset exists. The same instinct runs through &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/generating-og-images-with-satori-and-astro&quot;&gt;generating OG images with Satori&lt;/a&gt;: own the design, skip the abstraction. Owning the CSS means when something looks off, you fix the rule instead of fighting the abstraction. Go &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/typeset&quot;&gt;build one in the builder&lt;/a&gt; and read the file it hands you. It's short, and reading it teaches you more about typographic CSS than the docs do.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22961/shadcn-typeset-vs-tailwind-typography-for-markdown</guid>
<pubDate>Sat, 18 Jul 2026 16:44:34 +0000</pubDate>
</item>
<item>
<title>SaaS Starter Architecture with Next.js 2026</title>
<link>https://coderlegion.com/22786/saas-starter-architecture-with-next-js-2026</link>
<description>&lt;p&gt;You've got the idea. You're pumped. You open your terminal, run &lt;code&gt;npx create-next-app&lt;/code&gt;, and then... you stare at a blank &lt;code&gt;page.tsx&lt;/code&gt; wondering — &lt;em&gt;where do I even begin?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is the silent killer of SaaS projects. Not the idea. Not the tech. The architecture. Most developers start hacking features together and three weeks in, the codebase is a mess of spaghetti — auth logic mixed with UI, API calls scattered everywhere, no clear structure. It's painful to extend and even more painful to hand off.&lt;/p&gt;
&lt;p&gt;The good news? A solid SaaS architecture isn't complicated. It's just a set of decisions made &lt;em&gt;upfront&lt;/em&gt; — decisions about folders, responsibilities, and boundaries. Get those right and building features becomes fast, predictable, and even enjoyable.&lt;/p&gt;
&lt;p&gt;By the end of this guide, you'll have a clear, production-ready SaaS folder structure with authentication, billing, protected routes, and a dashboard layout — all wired together with Next.js App Router, TypeScript, Tailwind CSS, and React Compiler.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-most-saas-projects-fall-apart-early&quot;&gt;Why Most SaaS Projects Fall Apart Early&lt;/h3&gt;
&lt;p&gt;Here's the honest truth: most developers (even experienced ones) start with a feature-first mindset. They add auth, then billing, then a dashboard — each piece bolted on without a plan. It works for a while. Then it doesn't.&lt;/p&gt;
&lt;p&gt;The problems that show up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can't tell where business logic lives (is it in the component? the API route? a hook?)&lt;/li&gt;
&lt;li&gt;Protected and public pages share the same layout, causing weird rendering bugs&lt;/li&gt;
&lt;li&gt;Adding a new feature means touching five different files across three folders&lt;/li&gt;
&lt;li&gt;Onboarding a new developer takes days just to explain the structure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fix isn't a framework or a library. It's &lt;strong&gt;deliberate architecture&lt;/strong&gt; — deciding upfront how your app is organized and what each layer is responsible for.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-we-re-building&quot;&gt;What We're Building&lt;/h3&gt;
&lt;p&gt;A scalable SaaS starter with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ App Router (Next.js latest)&lt;/li&gt;
&lt;li&gt;✅ React Compiler (automatic optimization — no manual &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ TypeScript (strict mode)&lt;/li&gt;
&lt;li&gt;✅ Tailwind CSS&lt;/li&gt;
&lt;li&gt;✅ Authentication-ready structure (works with Clerk, Auth0, NextAuth, etc.)&lt;/li&gt;
&lt;li&gt;✅ Billing-ready structure (works with Stripe)&lt;/li&gt;
&lt;li&gt;✅ Separate layouts for marketing, auth, and dashboard&lt;/li&gt;
&lt;li&gt;✅ Proxy-based route protection&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 20+ installed&lt;/li&gt;
&lt;li&gt;Basic familiarity with Next.js App Router&lt;/li&gt;
&lt;li&gt;A fresh Next.js project (we'll scaffold one below)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-scaffold-the-project&quot;&gt;Step 1: Scaffold the Project&lt;/h3&gt;
&lt;p&gt;Let's create a fresh Next.js app with all the defaults we need.&lt;/p&gt;
&lt;p&gt;Run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-next-app@latest saas-starter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Choose options according to your preferences.&lt;/p&gt;
&lt;p&gt;Recommended setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Tailwind CSS&lt;/li&gt;
&lt;li&gt;App Router&lt;/li&gt;
&lt;li&gt;React Compiler&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/&lt;/code&gt; directory&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;react-compiler&quot;&gt;React Compiler&lt;/h4&gt;
&lt;p&gt;React Compiler automatically optimizes your components — it's like having a senior dev go through your entire codebase and add &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt; everywhere they're needed. You get that for free.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Don't manually add &lt;code&gt;useMemo&lt;/code&gt; or &lt;code&gt;useCallback&lt;/code&gt; when React Compiler is enabled. It'll do it better than you. Adding them manually can actually interfere with the compiler's optimizations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-plan-the-folder-structure&quot;&gt;Step 2: Plan the Folder Structure&lt;/h3&gt;
&lt;p&gt;This is the most important step. A well-thought-out folder structure acts like a map — anyone who opens the project immediately knows where everything lives.&lt;/p&gt;
&lt;p&gt;Here's the structure we're going for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;saas-starter/
├── src/
│   ├── app/
│   │   ├── (marketing)/          # Public-facing pages (landing, pricing, about)
│   │   │   ├── page.tsx          # Homepage
│   │   │   ├── pricing/
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx        # Marketing layout (navbar, footer)
│   │   │
│   │   ├── (auth)/               # Auth pages (login, signup, forgot password)
│   │   │   ├── login/
│   │   │   │   └── page.tsx
│   │   │   ├── signup/
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx        # Minimal auth layout (just centered card)
│   │   │
│   │   ├── (dashboard)/          # Protected app pages (only logged-in users)
│   │   │   ├── dashboard/
│   │   │   │   └── page.tsx
│   │   │   ├── settings/
│   │   │   │   └── page.tsx
│   │   │   └── layout.tsx        # Dashboard layout (sidebar, topbar)
│   │   │
│   │   ├── api/                  # API routes (backend endpoints)
│   │   │   ├── webhooks/
│   │   │   │   └── stripe/
│   │   │   │       └── route.ts
│   │   │   └── user/
│   │   │       └── route.ts
│   │   │
│   │   ├── layout.tsx            # Root layout (fonts, global providers)
│   │   └── globals.css
│   │
│   ├── components/
│   │   ├── ui/                   # Reusable UI primitives (Button, Input, Modal)
│   │   ├── marketing/            # Components only used on marketing pages
│   │   ├── dashboard/            # Components only used in the dashboard
│   │   └── shared/               # Components used everywhere (Avatar, Logo)
│   │
│   ├── lib/
│   │   ├── auth.ts               # Auth helper functions
│   │   ├── stripe.ts             # Stripe client setup
│   │   ├── db.ts                 # Database client setup
│   │   └── utils.ts              # General utility functions
│   │
│   ├── hooks/                    # Custom React hooks
│   ├── types/                    # TypeScript type definitions
│   └── proxy.ts                  # Route protection logic
└── .env.local                    # Environment variables (never commit this!)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's break down the key decisions here:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Route Groups &lt;code&gt;(marketing)&lt;/code&gt;, &lt;code&gt;(auth)&lt;/code&gt;, &lt;code&gt;(dashboard)&lt;/code&gt;&lt;/strong&gt; — The parentheses are a Next.js feature. They let you group routes &lt;em&gt;without&lt;/em&gt; affecting the URL. So &lt;code&gt;app/(marketing)/pricing/page.tsx&lt;/code&gt; lives at &lt;code&gt;/pricing&lt;/code&gt;, not &lt;code&gt;/marketing/pricing&lt;/code&gt;. The real power is that each group gets its own &lt;code&gt;layout.tsx&lt;/code&gt; — meaning your dashboard gets a sidebar, your auth pages get a clean centered layout, and your marketing pages get a navbar. No mixing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;lib/&lt;/code&gt; folder&lt;/strong&gt; — This is where your &quot;glue code&quot; lives. Auth helpers, database clients, Stripe setup. These are not components, not hooks — they're plain functions and configurations that any part of the app can import.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;types/&lt;/code&gt; folder&lt;/strong&gt; — All your TypeScript interfaces and types live here. This prevents the chaos of defining &lt;code&gt;type User = {...}&lt;/code&gt; in five different files.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Important: In Next.js 16, Middleware has been renamed to Proxy. In older versions, you'll see middleware.ts used instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-set-up-route-groups-amp-layouts&quot;&gt;Step 3: Set Up Route Groups &amp;amp; Layouts&lt;/h3&gt;
&lt;p&gt;Let's create the three layout files that power the different sections of the app.&lt;/p&gt;
&lt;h4 id=&quot;root-layout&quot;&gt;Root Layout&lt;/h4&gt;
&lt;p&gt;The root layout wraps &lt;em&gt;everything&lt;/em&gt;. It's the right place for global providers (auth context, theme, etc.) and fonts.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/layout.tsx
import type { Metadata } from &quot;next&quot;;
import { Inter } from &quot;next/font/google&quot;;
import &quot;./globals.css&quot;;

const inter = Inter({ subsets: [&quot;latin&quot;] });

export const metadata: Metadata = {
  title: {
    template: &quot;%s | YourSaaS&quot;,
    default: &quot;YourSaaS — The tagline goes here&quot;,
  },
  description: &quot;Your SaaS description here.&quot;,
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;body className={inter.className}&amp;gt;
        {/* Global providers go here (auth, theme, etc.) */}
        {children}
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;marketing-layout&quot;&gt;Marketing Layout&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/(marketing)/layout.tsx
import { Navbar } from &quot;@/components/marketing/Navbar&quot;;
import { Footer } from &quot;@/components/marketing/Footer&quot;;

export default function MarketingLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &amp;lt;div className=&quot;flex min-h-screen flex-col&quot;&amp;gt;
      &amp;lt;Navbar /&amp;gt;
      &amp;lt;main className=&quot;flex-1&quot;&amp;gt;{children}&amp;lt;/main&amp;gt;
      &amp;lt;Footer /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;auth-layout&quot;&gt;Auth Layout&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/(auth)/layout.tsx
export default function AuthLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &amp;lt;div className=&quot;flex min-h-screen items-center justify-center bg-gray-50&quot;&amp;gt;
      &amp;lt;div className=&quot;w-full max-w-md px-4&quot;&amp;gt;{children}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;dashboard-layout&quot;&gt;Dashboard Layout&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/(dashboard)/layout.tsx
import { Sidebar } from &quot;@/components/dashboard/Sidebar&quot;;
import { Topbar } from &quot;@/components/dashboard/Topbar&quot;;

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &amp;lt;div className=&quot;flex h-screen overflow-hidden bg-gray-100&quot;&amp;gt;
      &amp;lt;Sidebar /&amp;gt;
      &amp;lt;div className=&quot;flex flex-1 flex-col overflow-hidden&quot;&amp;gt;
        &amp;lt;Topbar /&amp;gt;
        &amp;lt;main className=&quot;flex-1 overflow-y-auto p-6&quot;&amp;gt;{children}&amp;lt;/main&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Why separate layouts matter:&lt;/strong&gt; Without this, you'd need conditional logic inside a single layout to decide whether to show the sidebar. That gets messy fast. Route groups keep things clean — each section of your app has its own look, independently.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-protect-routes-with-proxy&quot;&gt;Step 4: Protect Routes with Proxy&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Proxy&lt;/strong&gt; — think of it as a security guard that stands at the entrance of your app. Every request passes through it &lt;em&gt;before&lt;/em&gt; hitting a page. This is the perfect place to check: &quot;Is this user logged in? If not, redirect them to &lt;code&gt;/login&lt;/code&gt;.&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// proxy.ts  (lives at the ROOT of your project, not inside /app)
import { NextRequest, NextResponse } from &quot;next/server&quot;;

// These are the routes that require the user to be logged in
const protectedRoutes = [&quot;/dashboard&quot;, &quot;/settings&quot;, &quot;/billing&quot;];

// These are routes that logged-in users shouldn't see (e.g. login page)
const authRoutes = [&quot;/login&quot;, &quot;/signup&quot;];

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // Check for your auth token/session cookie
  // Replace &quot;auth_token&quot; with whatever your auth provider uses
  const isAuthenticated = request.cookies.has(&quot;auth_token&quot;);

  // If the user is trying to access a protected route but isn't logged in
  if (protectedRoutes.some((route) =&amp;gt; pathname.startsWith(route))) {
    if (!isAuthenticated) {
      return NextResponse.redirect(new URL(&quot;/login&quot;, request.url));
    }
  }

  // If the user is already logged in and tries to visit /login or /signup
  if (authRoutes.includes(pathname) &amp;amp;&amp;amp; isAuthenticated) {
    return NextResponse.redirect(new URL(&quot;/dashboard&quot;, request.url));
  }

  return NextResponse.next();
}

// Tell Next.js which paths this proxy should run on
export const config = {
  matcher: [&quot;/((?!api|_next/static|_next/image|favicon.ico).*)&quot;],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here, line by line:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;protectedRoutes&lt;/code&gt; — the list of URL paths only logged-in users can visit&lt;/li&gt;
&lt;li&gt;&lt;code&gt;authRoutes&lt;/code&gt; — pages that make no sense for logged-in users (you're already in!)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;request.cookies.has(&quot;auth_token&quot;)&lt;/code&gt; — checks if the auth cookie exists. Your auth provider (Clerk, NextAuth, etc.) will set this automatically&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NextResponse.redirect(...)&lt;/code&gt; — sends the user to a different URL&lt;/li&gt;
&lt;li&gt;&lt;code&gt;matcher&lt;/code&gt; — tells proxy to skip static files and images (no need to auth-check a &lt;code&gt;.png&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Forgetting the &lt;code&gt;matcher&lt;/code&gt; config. Without it, proxy runs on &lt;em&gt;every single request&lt;/em&gt; — including CSS files, images, and fonts. That's unnecessary overhead and can cause weird issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/how-to-add-clerk-authentication-in-nextjs-16&quot;&gt;Related: How to Add Clerk Authentication in Next.js 16&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-5-set-up-the-types-folder&quot;&gt;Step 5: Set Up the Types Folder&lt;/h3&gt;
&lt;p&gt;Define your core types once, use them everywhere. This prevents TypeScript errors and keeps your data shapes consistent.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// types/index.ts
export type User = {
  id: string;
  email: string;
  name: string;
  avatarUrl?: string;
  plan: &quot;free&quot; | &quot;pro&quot; | &quot;enterprise&quot;;
  createdAt: Date;
};

export type Subscription = {
  id: string;
  userId: string;
  status: &quot;active&quot; | &quot;cancelled&quot; | &quot;past_due&quot; | &quot;trialing&quot;;
  plan: &quot;pro&quot; | &quot;enterprise&quot;;
  currentPeriodEnd: Date;
};

export type ApiResponse&amp;lt;T&amp;gt; = {
  data: T | null;
  error: string | null;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Why a central &lt;code&gt;types/&lt;/code&gt; folder?&lt;/strong&gt; If you define &lt;code&gt;User&lt;/code&gt; in five files, changing one field means updating five places. One file, one source of truth.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-6-set-up-core-lib-files&quot;&gt;Step 6: Set Up Core Lib Files&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;lib/&lt;/code&gt; folder holds your setup code — the stuff that's not a component but needs to exist before anything works.&lt;/p&gt;
&lt;h4 id=&quot;database-example-with-supabase-&quot;&gt;Database (example with Supabase)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/db.ts
import { createClient } from &quot;@supabase/supabase-js&quot;;

// process.env is how you access environment variables in Next.js
// These are secret keys stored in your .env.local file — never hardcode them!
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;

export const db = createClient(supabaseUrl, supabaseKey);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/nextjs-16-supabase-todo-app&quot;&gt;Related: Build a Todo App with Next.js 16 and Supabase (2026 Guide)&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;stripe-client&quot;&gt;Stripe Client&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/stripe.ts
import Stripe from &quot;stripe&quot;;

// The &quot;!&quot; tells TypeScript &quot;trust me, this value exists&quot;
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: &quot;latest supported version&quot;,
  typescript: true,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-nextjs-16-guide&quot;&gt;Related: How to Integrate Stripe Checkout with Next.js 16 (2026 Edition)&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;utility-functions&quot;&gt;Utility Functions&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// lib/utils.ts

// Combines Tailwind class names without conflicts
// Install clsx and tailwind-merge first: npm install clsx tailwind-merge
import { clsx, type ClassValue } from &quot;clsx&quot;;
import { twMerge } from &quot;tailwind-merge&quot;;

export function cn(...inputs: ClassValue[]): string {
  return twMerge(clsx(inputs));
}

// Format a date to a readable string
export function formatDate(date: Date): string {
  return new Intl.DateTimeFormat(&quot;en-US&quot;, {
    month: &quot;long&quot;,
    day: &quot;numeric&quot;,
    year: &quot;numeric&quot;,
  }).format(date);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;The &lt;code&gt;cn()&lt;/code&gt; function&lt;/strong&gt; is one of the most used utilities in any Tailwind project. It lets you merge class names conditionally without worrying about conflicting Tailwind classes. Almost every component will use it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-7-environment-variables&quot;&gt;Step 7: Environment Variables&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Environment variables&lt;/strong&gt; are secret keys and configuration values that your app needs but should &lt;em&gt;never&lt;/em&gt; be publicly visible. Things like your database password, Stripe secret key, or auth secret.&lt;/p&gt;
&lt;p&gt;Create a &lt;code&gt;.env.local&lt;/code&gt; file at the root of your project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# .env.local

# Authentication (example with Clerk)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

# Database (example with Supabase)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

# Stripe
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# App
NEXT_PUBLIC_APP_URL=http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Critical:&lt;/strong&gt; Add &lt;code&gt;.env.local&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; immediately. If you push secret keys to GitHub, bad actors can find them and abuse your accounts. This is not hypothetical — it happens all the time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Variables starting with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; are exposed to the browser. Everything else is server-only. Never put secret keys in &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; variables.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;can-i-use-this-structure-with-the-pages-router-instead-of-app-router-&quot;&gt;Can I use this structure with the Pages Router instead of App Router?&lt;/h4&gt;
&lt;p&gt;You can, but the route groups &lt;code&gt;(marketing)&lt;/code&gt;, &lt;code&gt;(auth)&lt;/code&gt;, and &lt;code&gt;(dashboard)&lt;/code&gt; are an App Router feature — they won't work in Pages Router. With Pages Router you'd achieve similar separation using custom &lt;code&gt;_app.tsx&lt;/code&gt; logic and manual layout wrappers, but it's messier. App Router is the recommended approach going forward.&lt;/p&gt;
&lt;h4 id=&quot;do-i-have-to-use-clerk-or-supabase-specifically-&quot;&gt;Do I have to use Clerk or Supabase specifically?&lt;/h4&gt;
&lt;p&gt;Not at all. The architecture is auth-provider agnostic. Whether you use Clerk, NextAuth, Auth0, or Supabase Auth — the folder structure stays the same. You just swap out the cookie name in &lt;code&gt;proxy.ts&lt;/code&gt; and the client setup in &lt;code&gt;lib/auth.ts&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;what-if-my-app-grows-and-the-structure-doesn-t-scale-&quot;&gt;What if my app grows and the structure doesn't scale?&lt;/h4&gt;
&lt;p&gt;This structure handles the vast majority of SaaS apps well. If you hit a point where a single &lt;code&gt;components/dashboard/&lt;/code&gt; folder gets too crowded, break it into feature folders — e.g. &lt;code&gt;components/dashboard/billing/&lt;/code&gt;, &lt;code&gt;components/dashboard/team/&lt;/code&gt;. The core principle stays the same: group by where things are used.&lt;/p&gt;
&lt;h4 id=&quot;is-react-compiler-production-ready-&quot;&gt;Is React Compiler production-ready?&lt;/h4&gt;
&lt;p&gt;Yes. As of 2025, React Compiler is stable and recommended for new projects. It's been battle-tested by the Meta team on large-scale apps. Just remember — once it's on, stop writing manual &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt;. Let the compiler do its job.&lt;/p&gt;
&lt;h4 id=&quot;where-should-i-put-shared-api-logic-that-multiple-routes-use-&quot;&gt;Where should I put shared API logic that multiple routes use?&lt;/h4&gt;
&lt;p&gt;Put it in &lt;code&gt;lib/&lt;/code&gt;. For example, a &lt;code&gt;lib/user.ts&lt;/code&gt; file with a &lt;code&gt;getUserById(id)&lt;/code&gt; function that your API routes and server components can both import. This avoids duplicating database queries across files.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-final-picture&quot;&gt;The Final Picture&lt;/h3&gt;
&lt;p&gt;Here's what you've got now:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;What lives here&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;app/(marketing)&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Landing page, pricing, about — with navbar &amp;amp; footer&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;app/(auth)&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Login, signup — minimal centered layout&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;app/(dashboard)&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Protected app pages — sidebar + topbar&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;proxy.ts&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Route protection — redirects unauthenticated users&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;lib/&lt;/code&gt; &lt;/td&gt;&lt;td&gt;DB client, Stripe setup, utility functions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;types/&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Shared TypeScript types — User, Subscription, etc.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;components/&lt;/code&gt; &lt;/td&gt;&lt;td&gt;UI organized by where it's used&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This architecture scales. Whether you're building alone or with a team of five, adding a new feature means you know &lt;em&gt;exactly&lt;/em&gt; which folder it belongs in. There's no guessing, no hunting through files, no spaghetti.&lt;/p&gt;
&lt;p&gt;From here, the next steps are wiring in your auth provider of choice, setting up your Stripe webhook handler in &lt;code&gt;app/api/webhooks/stripe/route.ts&lt;/code&gt;, and building out your dashboard pages. Each of those is its own focused task — and now you have a solid foundation to do them on.&lt;/p&gt;
&lt;p&gt;Happy building. 🚀&lt;/p&gt;
&lt;h3 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/how-to-add-clerk-authentication-in-nextjs-16&quot;&gt;Add Authentication with Clerk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/stripe-payment-element-nextjs-16-guide&quot;&gt;Add Stripe Billing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/nextjs-16-supabase-todo-app&quot;&gt;Add Database with Supabase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/resend-nextjs-16-guide&quot;&gt;Add Transactional Emails with Resend&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;useful-resources&quot;&gt;Useful Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/api-reference/file-conventions/proxy&quot;&gt;Next.js Proxy Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/api-reference/file-conventions/route-groups&quot;&gt;Next.js Route Groups Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/learn/react-compiler&quot;&gt;React Compiler Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22786/saas-starter-architecture-with-next-js-2026</guid>
<pubDate>Thu, 16 Jul 2026 05:16:29 +0000</pubDate>
</item>
<item>
<title>15 Free Frontend Tools Every Developer Should Know in 2026</title>
<link>https://coderlegion.com/22579/15-free-frontend-tools-every-developer-should-know-in-2026</link>
<description>&lt;h2 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;When I started learning frontend development, I thought I needed expensive software to build things that looked &quot;professional.&quot; Turns out, that's completely false.&lt;/p&gt;
&lt;p&gt;Some of the best frontend tools out there are free — and a lot of developers, even experienced ones, don't know half of them exist. Whether you're picking colors, generating CSS animations, testing responsiveness, or just trying to find the right icon, there's a free tool for almost everything.&lt;/p&gt;
&lt;p&gt;In this post, I'm sharing 15 tools I personally use (or have used) while building real projects — from client websites to personal portfolios. No fluff, no &quot;100 tools you'll never open&quot; lists. Just the ones that actually save time.&lt;/p&gt;
&lt;p&gt;Let's get into it.&lt;/p&gt;
&lt;h3 id=&quot;1-design-amp-color-tools&quot;&gt;1. Design &amp;amp; Color Tools&lt;/h3&gt;
&lt;p&gt;Good design starts with good colors and layout inspiration. These tools take the guesswork out of it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Coolors&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Coolors is the fastest way to generate a color palette. Hit the spacebar and it keeps generating combinations until you find one you like. You can lock colors you love and regenerate the rest.&lt;/p&gt;
&lt;p&gt;Why beginners love it: No design theory needed — just pick what looks good.&lt;br&gt;
🔗 coolors.co&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adobe Color&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;More advanced than Coolors, this lets you build palettes based on color theory rules (complementary, triadic, monochromatic, etc.) and even extract a palette from an image.&lt;/p&gt;
&lt;p&gt;Why it's useful: Great when you want a palette that matches a client's brand photo or logo.&lt;br&gt;
🔗 color.adobe.com&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-css-generators-stop-writing-gradients-by-hand-&quot;&gt;2. CSS Generators (Stop Writing Gradients by Hand)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CSS Gradient&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pick colors, choose the direction, and copy the ready-made CSS. Simple, fast, and works for linear and radial gradients.&lt;br&gt;
🔗 cssgradient.io&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Neumorphism.io&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want that soft, &quot;pressed-in&quot; neumorphic UI style, this generator gives you the exact box-shadow code — adjustable by distance, blur, and intensity, with light/dark background support.&lt;br&gt;
🔗 neumorphism.io&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clippy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Need a custom shape (hexagon, arrow, triangle) using clip-path? Clippy lets you drag points visually and copies the CSS for you. No more guessing polygon coordinates.&lt;br&gt;
🔗 bennettfeely.com/clippy&lt;/p&gt;
&lt;p&gt;Pro tip: These generators are amazing for learning too — generate the code, then read it and understand why it works. That's how I actually learned box-shadow and clip-path properties.&lt;/p&gt;
&lt;h3 id=&quot;3-icons-that-don-t-look-generic&quot;&gt;3. Icons That Don't Look Generic&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Lucide Icons&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A clean, consistent icon set that works beautifully with React and Tailwind CSS. Lightweight, tree-shakable, and has an official React package (lucide-react).&lt;br&gt;
🔗 lucide.dev&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Heroicons&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Made by the Tailwind CSS team, so naturally it pairs perfectly with Tailwind projects. Comes in outline and solid styles.&lt;br&gt;
🔗 heroicons.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Font Awesome (Free Tier)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The classic. Massive icon library, and the free tier alone covers most needs — social icons, UI icons, brand logos.&lt;br&gt;
🔗 fontawesome.com&lt;/p&gt;
&lt;p&gt;My pick for React + Tailwind projects: Lucide. It's the one I use across my own builds because it's lightweight and easy to customize with Tailwind classes.&lt;/p&gt;
&lt;h3 id=&quot;4-fonts-amp-typography&quot;&gt;4. Fonts &amp;amp; Typography&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Google Fonts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The obvious one, but don't just grab the first font you see. Google Fonts also shows you popularity, styles, and lets you preview your own text before choosing.&lt;br&gt;
🔗 fonts.google.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fontjoy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Struggling to pair a heading font with a body font? Fontjoy uses AI to suggest font combinations that actually work together — and you can lock one font and regenerate the other.&lt;br&gt;
🔗 fontjoy.com&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;5-online-code-playgrounds&quot;&gt;5. Online Code Playgrounds&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CodePen&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Great for quick CSS/JS experiments, sharing snippets, and browsing what other developers are building. Perfect for testing an animation idea before adding it to a real project.&lt;br&gt;
🔗 codepen.io&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;StackBlitz&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A full online IDE that runs actual React, Vue, or Node projects in your browser — no local setup needed. I use this a lot when testing a quick React component idea.&lt;br&gt;
🔗 stackblitz.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CodeSandbox&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Similar to StackBlitz, with strong support for sharing full working React apps and instant deployment previews. Great for collaborating or showing a client a live demo without deploying anywhere.&lt;br&gt;
🔗 codesandbox.io&lt;/p&gt;
&lt;h3 id=&quot;6-image-optimization-tools&quot;&gt;6. Image Optimization Tools&lt;/h3&gt;
&lt;p&gt;Heavy images kill website speed — and speed affects both user experience and SEO rankings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Squoosh&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Made by Google. Drag in an image, compare compression formats (WebP, AVIF, JPEG) side by side, and download the optimized version — all in your browser, nothing uploaded to a server.&lt;br&gt;
🔗 squoosh.app&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TinyPNG&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Bulk compress PNG and JPEG images with barely noticeable quality loss. I run every client project's images through this before deployment.&lt;br&gt;
🔗 tinypng.com&lt;/p&gt;
&lt;h3 id=&quot;7-responsive-amp-accessibility-testing&quot;&gt;7. Responsive &amp;amp; Accessibility Testing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Responsively App&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A free desktop app that shows your website on multiple device sizes at once, side by side, and syncs scrolling and clicks across all of them. Saves so much time compared to resizing your browser manually.&lt;br&gt;
🔗 responsively.app&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Chrome DevTools (Lighthouse)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built right into Chrome. Open DevTools → Lighthouse tab → run an audit. It scores your site on performance, accessibility, best practices, and SEO, and tells you exactly what to fix.&lt;/p&gt;
&lt;p&gt;Why this matters for freelancers: Clients love seeing a 90+ Lighthouse score. It's an easy way to prove your work is solid.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WAVE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A free accessibility checker. Paste your URL and it visually flags missing alt text, low contrast, and other accessibility issues directly on the page.&lt;br&gt;
🔗 wave.webaim.org&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;8-reference-amp-learning&quot;&gt;8. Reference &amp;amp; Learning&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;MDN Web Docs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The single most reliable source for HTML, CSS, and JavaScript documentation. When in doubt, check MDN before trusting a random blog post (including this one).&lt;br&gt;
🔗 developer.mozilla.org&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can I Use&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before using a newer CSS or JS feature, check browser support here. Saves you from shipping something that breaks in Safari or older browsers.&lt;br&gt;
🔗 caniuse.com&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You don't need a paid design subscription or premium plugin to build clean, professional websites. The tools above cover almost every stage of frontend work — design, coding, testing, and optimization — and they're all free.&lt;/p&gt;
&lt;p&gt;My honest advice as someone who's been learning and building for the past couple of years: don't try to learn all 15 tools at once. Pick 2–3 that solve a problem you're facing right now, use them in your next project, and the rest will come naturally as you build more.&lt;/p&gt;
&lt;p&gt;If you found this useful, save it — you'll probably come back to this list more than once.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Written by &lt;strong&gt;Muhammad Farhan&lt;/strong&gt; — Frontend Developer specializing in React.js &amp;amp; Tailwind CSS, based in &lt;strong&gt;Dera Ismail Khan, Pakistan&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; muhammad-farhan-dev.github.io/muhammadfarhan.dev &lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; github.com/muhammad-farhan-dev&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22579/15-free-frontend-tools-every-developer-should-know-in-2026</guid>
<pubDate>Tue, 14 Jul 2026 06:38:30 +0000</pubDate>
</item>
<item>
<title>shadcn/typeset vs Tailwind Typography: Styling Markdown</title>
<link>https://coderlegion.com/22285/shadcn-typeset-vs-tailwind-typography-styling-markdown</link>
<description>&lt;p&gt;You render some markdown, get back a pile of unstyled HTML, and then you style it. Headings, paragraphs, lists, tables, code. You do it for the blog. Then you do it again for the docs. Then a third time for the chat UI that streams model output token by token. Every context, the same elements, slightly different rules.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/shadcn&quot;&gt;shadcn&lt;/a&gt; shipped a take on this on July 10, 2026, called &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/docs/typeset&quot;&gt;shadcn/typeset&lt;/a&gt;. It's worth a look even if you don't use the rest of shadcn/ui, because it isn't a component and it isn't a CLI install. It's one CSS file you copy into your project and own outright.&lt;/p&gt;
&lt;h3 id=&quot;what-is-shadcn-typeset--1&quot;&gt;What is shadcn/typeset?&lt;/h3&gt;
&lt;p&gt;Typeset is a styling system for rendered HTML and markdown, delivered as a single CSS file. You wrap your content in a &lt;code&gt;.typeset&lt;/code&gt; container and everything inside it gets styled: headings, paragraphs, lists, tables, code, blockquotes, links, footnotes, even MathML and &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;div className=&quot;typeset typeset-docs&quot;&amp;gt;
  &amp;lt;YourMarkdownRenderer&amp;gt;{content}&amp;lt;/YourMarkdownRenderer&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.typeset&lt;/code&gt; turns the styles on. &lt;code&gt;.typeset-docs&lt;/code&gt; is a preset, a small class you layer on top to tune the look for a specific context. You can define as many presets as you have contexts: one for the blog, one for the docs, one for chat.&lt;/p&gt;
&lt;p&gt;The thing to internalize up front: there's no npm package and no config layer. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/docs/changelog/2026-07-typeset&quot;&gt;changelog&lt;/a&gt; puts it plainly: &quot;one CSS file that styles everything inside a &lt;code&gt;typeset&lt;/code&gt; container. The file lives in your project, so you can change it directly when you need to.&quot; If a heading annoys you, you open the file and change the rule. That's the whole distribution model.&lt;/p&gt;
&lt;p&gt;This is a different thing from shadcn/ui's &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/docs/components/typography&quot;&gt;Typography component&lt;/a&gt;, which gives you styled React heading and paragraph components to author by hand. Typeset styles HTML you didn't write, the output of a markdown renderer.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-install-shadcn-typeset--1&quot;&gt;How do you install shadcn/typeset?&lt;/h3&gt;
&lt;p&gt;You don't run a CLI. As of this release there's no &lt;code&gt;npx shadcn@latest add typeset&lt;/code&gt;, and expecting one will send you looking for a command that doesn't exist.&lt;/p&gt;
&lt;p&gt;Instead you build the file in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/typeset&quot;&gt;interactive builder&lt;/a&gt;, pick your sizes and fonts, and copy the generated &lt;code&gt;typeset.css&lt;/code&gt; into your project. Then you import it after Tailwind:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@import &quot;tailwindcss&quot;;
@import &quot;./typeset.css&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import order matters, and I'll come back to why in a moment. Once it's in, the wrapper from the first snippet is all you need.&lt;/p&gt;
&lt;h3 id=&quot;size-leading-and-flow-1&quot;&gt;Size, leading, and flow&lt;/h3&gt;
&lt;p&gt;Typeset condenses typographic tuning into three CSS variables it calls &quot;rhythm,&quot; and everything else derives from them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.typeset {
  --typeset-size: 1em;      /* base font size */
  --typeset-leading: 1.75;  /* line-height */
  --typeset-flow: 1.25em;   /* vertical space between blocks */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A preset is just those three variables set to different values under a class name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.typeset-chat {
  --typeset-flow: 1em;
  --typeset-leading: 1.6;
}
.typeset-docs {
  --typeset-size: 15px;
  --typeset-flow: 1.5em;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because it's all CSS custom properties, you can override a single value inline without touching the file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;article className=&quot;typeset [--typeset-flow:1.75em]&quot;&amp;gt;{content}&amp;lt;/article&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sizing is container-relative rather than a fixed &lt;code&gt;rem&lt;/code&gt; scale. Below &lt;code&gt;48rem&lt;/code&gt; the base size gets a &lt;code&gt;1.125x&lt;/code&gt; bump and then settles back to the raw value on larger screens, so text reads comfortably on a phone without a separate small-screen variant. Colors and borders pull from your app's theme tokens (&lt;code&gt;--color-foreground&lt;/code&gt;, &lt;code&gt;--color-muted-foreground&lt;/code&gt;, &lt;code&gt;--color-border&lt;/code&gt;, &lt;code&gt;--radius&lt;/code&gt;) with sensible fallbacks, so dark mode just works when your tokens flip. There's no second inverted palette to maintain.&lt;/p&gt;
&lt;p&gt;One deliberate omission: Typeset doesn't set a &lt;code&gt;max-width&lt;/code&gt;. Your layout owns the measure. The builder's Measure control adds a &lt;code&gt;max-width&lt;/code&gt; to the wrapper if you want it, but the stylesheet itself stays out of your layout's business.&lt;/p&gt;
&lt;h3 id=&quot;why-your-tailwind-utilities-win-without-important-1&quot;&gt;Why your Tailwind utilities win without !important&lt;/h3&gt;
&lt;p&gt;Here's the detail I liked most. Typeset lives in the CSS &lt;code&gt;@components&lt;/code&gt; layer and uses zero-specificity &lt;code&gt;:where()&lt;/code&gt; selectors for every element rule:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@layer components {
  .typeset {
    &amp;amp; :where(h1) { font-size: 1.75em; /* ... */ }
    &amp;amp; :where(p)  { margin-block-start: var(--typeset-flow); }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The override comes from cascade layers, not specificity. Typeset's rules live in the &lt;code&gt;@components&lt;/code&gt; layer, and Tailwind's utilities live in the &lt;code&gt;@utilities&lt;/code&gt; layer, which is declared after it. When rules in two different layers conflict, the later layer wins outright, no matter how specific either selector is. So a utility beats a Typeset rule with no &lt;code&gt;!important&lt;/code&gt; and no special modifier syntax. Slap &lt;code&gt;.text-3xl&lt;/code&gt; on a heading and it just wins.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;:where()&lt;/code&gt; does a related but separate job: it holds every element rule at zero specificity, so an override that isn't already shielded by a later layer, like plain CSS you write yourself or a rule in the same layer, can still win without a specificity fight. That's also why import order matters. Importing Tailwind first establishes its layer order, which puts &lt;code&gt;@utilities&lt;/code&gt; after Typeset's &lt;code&gt;@components&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to pull a whole subtree out, there's an escape hatch: &lt;code&gt;.not-typeset&lt;/code&gt; (or the &lt;code&gt;data-not-typeset&lt;/code&gt; attribute), which cascades to descendants including any nested &lt;code&gt;.typeset&lt;/code&gt; container.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Card className=&quot;not-typeset&quot;&amp;gt;Untouched component.&amp;lt;/Card&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;shadcn is candid that both of these ideas, the &lt;code&gt;:where()&lt;/code&gt; guard and the opt-out class, are borrowed from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tailwindlabs/tailwindcss-typography&quot;&gt;&lt;code&gt;@tailwindcss/typography&lt;/code&gt;&lt;/a&gt;, which has a &lt;code&gt;.not-prose&lt;/code&gt; of its own. Which brings us to the comparison you actually clicked for.&lt;/p&gt;
&lt;h3 id=&quot;shadcn-typeset-vs-tailwind-typography-1&quot;&gt;shadcn/typeset vs Tailwind Typography&lt;/h3&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tailwindlabs/tailwindcss-typography&quot;&gt;Tailwind CSS Typography&lt;/a&gt; plugin, and the &lt;code&gt;.prose&lt;/code&gt; class it hands you, has been the default answer for styling rendered markdown for years, and it's still excellent at what it was built for. Typeset isn't a drop-in replacement or a wrapper around it. It's a different set of trade-offs. The docs lay them out directly:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Tailwind Typography (&lt;code&gt;.prose&lt;/code&gt;) &lt;/th&gt;&lt;th&gt;Typeset&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Sizing &lt;/td&gt;&lt;td&gt;Fixed &lt;code&gt;rem&lt;/code&gt; scale, &lt;code&gt;.prose-sm&lt;/code&gt; to &lt;code&gt;.prose-2xl&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Relative to the container, any size&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Dark mode &lt;/td&gt;&lt;td&gt;&lt;code&gt;.prose-invert&lt;/code&gt;, a second palette &lt;/td&gt;&lt;td&gt;Your tokens flip, nothing to add&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Overrides &lt;/td&gt;&lt;td&gt;&lt;code&gt;prose-a:&lt;/code&gt;, &lt;code&gt;prose-headings:&lt;/code&gt; modifier API &lt;/td&gt;&lt;td&gt;Plain utilities and CSS win&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Streaming &lt;/td&gt;&lt;td&gt;No append-stability contract &lt;/td&gt;&lt;td&gt;Designed for stable appends&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Distribution &lt;/td&gt;&lt;td&gt;npm plugin, generated CSS &lt;/td&gt;&lt;td&gt;One CSS file you own&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The way I'd frame it: &lt;code&gt;.prose&lt;/code&gt; is a well-designed dependency you configure, and Typeset is a starting point you edit. If you like the modifier API and want a maintained plugin tracking Tailwind releases, &lt;code&gt;.prose&lt;/code&gt; is still a great pick. If you'd rather own the file and delete the rules you don't want, Typeset fits that instinct better. It's the same philosophy as the rest of shadcn/ui: copy the code in, make it yours.&lt;/p&gt;
&lt;h3 id=&quot;what-makes-it-streaming-safe--1&quot;&gt;What makes it streaming-safe?&lt;/h3&gt;
&lt;p&gt;This is the part that made Typeset feel designed for 2026 rather than 2020. If you've built a chat UI that renders an LLM's markdown as it streams, you've watched the layout twitch as each token lands. Typeset treats that as a first-class constraint.&lt;/p&gt;
&lt;p&gt;The rule is that there are no forward-looking selectors in the layout. &lt;code&gt;:last-child&lt;/code&gt;, &lt;code&gt;:has()&lt;/code&gt;, and &lt;code&gt;:empty&lt;/code&gt; are deliberately kept out of spacing rules, because what they match changes as content appends, which restyles blocks that were already on screen. Spacing flows in one direction only, using &lt;code&gt;margin-block-start&lt;/code&gt; and never &lt;code&gt;margin-block-end&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;&amp;amp; :where(p) {
  margin-block-start: var(--typeset-flow);
  margin-block-end: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same discipline shows up in tables: the row separators sit on the cells rather than on &lt;code&gt;tr:last-child&lt;/code&gt;, so appending a row never re-runs a &lt;code&gt;:last-child&lt;/code&gt; match and shifts the borders above it. When you add a new block to a Typeset container, nothing before it moves. That's a genuinely hard property to get right by hand, and it's baked into the stylesheet.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs-you-1&quot;&gt;What it costs you&lt;/h3&gt;
&lt;p&gt;Two things are worth knowing before you commit.&lt;/p&gt;
&lt;p&gt;First, the stylesheet leans on modern CSS, though most of it is safe by now: &lt;code&gt;color-mix()&lt;/code&gt; and &lt;code&gt;oklch()&lt;/code&gt; colors have been widely supported since 2023, and logical properties like &lt;code&gt;margin-block-start&lt;/code&gt; work everywhere. The real outlier is &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/margin-trim&quot;&gt;&lt;code&gt;margin-trim&lt;/code&gt;&lt;/a&gt;, which as of 2026 still ships only in Safari and other WebKit browsers (16.4 and up). There's no fallback for it, so in Chrome and Firefox that rule quietly does nothing. Worth knowing before you count on the margin trimming it's meant to handle.&lt;/p&gt;
&lt;p&gt;Second, wide tables wrap to fit by default. Horizontal scroll is opt-in through a &lt;code&gt;.typeset-scroll&lt;/code&gt; wrapper you have to add in your renderer's table component or a small rehype plugin. It won't happen automatically.&lt;/p&gt;
&lt;h3 id=&quot;should-you-use-it--1&quot;&gt;Should you use it?&lt;/h3&gt;
&lt;p&gt;If you're already in the shadcn/ui world and you're styling rendered markdown, especially anything that streams, Typeset is an easy yes: it's free, it's one file, and the streaming stability alone justifies it. If you're outside that ecosystem and happy with &lt;code&gt;.prose&lt;/code&gt;, there's no migration to rush. It's an alternative with a clearer answer to a couple of specific problems, not an upgrade you're missing out on.&lt;/p&gt;
&lt;p&gt;What I appreciate is the framing. shadcn keeps betting that the right unit of distribution for UI is source you own rather than a dependency you configure, and Typeset applies that bet to typography. I made a similar call &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/the-new-website&quot;&gt;rebuilding this site&lt;/a&gt;: the long-form styling here is hand-written Tailwind wired to theme variables rather than a plugin, for exactly the reason Typeset exists. Owning the CSS means when something looks off, you fix the rule instead of fighting the abstraction. Go &lt;a rel=&quot;nofollow&quot; href=&quot;https://ui.shadcn.com/typeset&quot;&gt;build one in the builder&lt;/a&gt; and read the file it hands you. It's short, and reading it teaches you more about typographic CSS than the docs do.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22285/shadcn-typeset-vs-tailwind-typography-styling-markdown</guid>
<pubDate>Sat, 11 Jul 2026 08:23:11 +0000</pubDate>
</item>
<item>
<title>Tailwind CSS on Large, Long-Lived Projects How Do You Keep It Manageable?</title>
<link>https://coderlegion.com/21967/tailwind-css-on-large-long-lived-projects-how-do-you-keep-it-manageable</link>
<description>&lt;p&gt;I've been using Tailwind CSS for a while now, and I genuinely enjoy it; it's fast to prototype with, the developer experience is smooth, and I rarely have to context-switch between files to style a component.&lt;/p&gt;
&lt;p&gt;But the more I use it, the more a question keeps coming back to me.&lt;br&gt;
On large projects that live and evolve for a long time, does Tailwind start becoming a pain to maintain? Between utility classes scattered across every single template, design systems that change over time, and teams where everyone has slightly different habits, I wonder if things can get messy fast.&lt;/p&gt;
&lt;p&gt;Before Tailwind, I was heavy on SCSS. Variables, mixins, partials, a clear folder structure. It felt organized and easy to evolve. But it also came with its own overhead.&lt;br&gt;
I'd love to hear from people with real experience on this:&lt;/p&gt;
&lt;p&gt;Are you using Tailwind on large, long-lived applications?&lt;br&gt;
Do you combine it with SCSS, CSS Modules, or something else?&lt;br&gt;
Have you put conventions or rules in place to keep things from going off the rails over time?&lt;/p&gt;
&lt;p&gt;What's worked, what hasn't? I'm genuinely curious about the good and the bad. 👇&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/21967/tailwind-css-on-large-long-lived-projects-how-do-you-keep-it-manageable</guid>
<pubDate>Sun, 05 Jul 2026 20:09:18 +0000</pubDate>
</item>
<item>
<title>Gridzzly: Build CSS Grid Layouts Visually - No Code Required</title>
<link>https://coderlegion.com/20593/gridzzly-build-css-grid-layouts-visually-no-code-required</link>
<description>&lt;p&gt;CSS Grid is one of the most powerful layout systems ever created. But let's be honest - remembering &lt;code&gt;grid-template-columns&lt;/code&gt;, &lt;code&gt;grid-area&lt;/code&gt;, and all those line numbers can be a headache, especially when you're just trying to prototype an idea.&lt;/p&gt;
&lt;p&gt;That's why I built &lt;strong&gt;Gridzzly&lt;/strong&gt; - a visual playground where you &lt;strong&gt;click and drag&lt;/strong&gt; to create grid items, edit track sizes in real time, and export production‑ready HTML/CSS with one click.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://gridzzly.vercel.app&quot;&gt;https://gridzzly.vercel.app&lt;/a&gt;&lt;br&gt;
 &lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/byllzz/gridzzly&quot;&gt;github.com/byllzz/gridzzly&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-makes-gridzzly-different-&quot;&gt;What Makes Gridzzly Different?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt; Visual Drawing&lt;/strong&gt; - Click and drag across the grid. Release to create a new grid item. No writing &lt;code&gt;grid-row&lt;/code&gt; or &lt;code&gt;grid-column&lt;/code&gt; manually.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Live Track Editing&lt;/strong&gt; - Edit column/row sizes directly in inputs above the grid. Use &lt;code&gt;1fr&lt;/code&gt;, &lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;200px&lt;/code&gt;, &lt;code&gt;minmax()&lt;/code&gt; - anything CSS Grid supports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Instant Code Export&lt;/strong&gt; - Switch between CSS and HTML tabs, copy the code, and paste it into your project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Built‑in Templates &amp;amp; Random Generator&lt;/strong&gt; - Stuck? Load a preset or hit &quot;Random Pattern&quot; for instant inspiration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Overlapping Items&lt;/strong&gt; - Place items on top of each other (great for magazine‑style layouts).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Dark Theme&lt;/strong&gt; - Easy on the eyes for long coding sessions.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-it-works-under-the-hood-&quot;&gt;How It Works (Under the Hood)&lt;/h3&gt;
&lt;p&gt;The entire app is built with &lt;strong&gt;React&lt;/strong&gt; and &lt;strong&gt;Tailwind CSS&lt;/strong&gt;. The core logic lives inside a custom hook: &lt;code&gt;useGridGenerator.js&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;1-grid-state&quot;&gt;1. Grid State&lt;/h4&gt;
&lt;p&gt;We store:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of columns/rows&lt;/li&gt;
&lt;li&gt;Gap values (in pixels)&lt;/li&gt;
&lt;li&gt;Column/row size strings (e.g., &lt;code&gt;['1fr', '2fr', '1fr']&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Placed items: each item is an object &lt;code&gt;{ id, rStart, cStart, rEnd, cEnd }&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-drawing-mechanism&quot;&gt;2. Drawing Mechanism&lt;/h4&gt;
&lt;p&gt;When the user mouses down on a cell, we start tracking the starting row/column. On mouse move, we calculate the &lt;strong&gt;rectangle&lt;/strong&gt; between the start and current cell, highlight all cells, and show a live badge (&lt;code&gt;3 cols × 2 rows&lt;/code&gt;). On mouse up, we create a new item and reset the selection.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Simplified from useGridGenerator.js
const startDrawing = (row, col) =&amp;gt; {
  setIsDrawing(true);
  setStartCell({ row, col });
  setHighlightedCells([{ row, col }]);
};

const updateDrawing = (row, col) =&amp;gt; {
  if (!isDrawing || !startCell) return;
  const rMin = Math.min(startCell.row, row);
  const rMax = Math.max(startCell.row, row);
  const cMin = Math.min(startCell.col, col);
  const cMax = Math.max(startCell.col, col);
  // highlight all cells from rMin..rMax, cMin..cMax
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-code-generation&quot;&gt;3. Code Generation&lt;/h4&gt;
&lt;p&gt;Every time the grid or items change, we generate fresh CSS + HTML. We even compress consecutive identical track sizes into repeat():&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;css&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Instead of: */
grid-template-columns: 1fr 1fr 1fr 1fr;
/* We output: */
grid-template-columns: repeat(4, 1fr);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generated HTML uses simple div elements with classes like .div1, .div2, and a .parent container.&lt;/p&gt;
&lt;h3 id=&quot;quick-usage-guide&quot;&gt;Quick Usage Guide&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Create a grid item: Click and drag across empty cells → release&lt;/li&gt;
&lt;li&gt;Delete an item: Hover the item → click the × Resize a track: Click&lt;/li&gt;
&lt;li&gt;the input above the grid (e.g., 1fr) → type a new value Change grid&lt;/li&gt;
&lt;li&gt;Scroll through &quot;Grid Patterns&quot; → click any preview Random layout:&lt;/li&gt;
&lt;li&gt;Click Random Grid Pattern Export code: Click Please May I have Some&lt;/li&gt;
&lt;li&gt;Code → copy CSS or HTML&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-i-built-this&quot;&gt;Why I Built This&lt;/h3&gt;
&lt;p&gt;I've taught CSS Grid to many developers, and the hardest part is always the mental model of grid lines and spanning. I wanted a tool that gives immediate visual feedback while generating real, copy‑pastable code – no vendor lock‑in, no weird abstractions. Just vanilla CSS Grid.&lt;/p&gt;
&lt;p&gt;Whether you're:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Learning CSS Grid for the first time, &lt;/li&gt;
&lt;li&gt;Prototyping a dashboard layout,&lt;/li&gt;
&lt;li&gt;Or building a complex magazine‑style design,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gridzzly helps you see what you're building and export it instantly.&lt;/p&gt;
&lt;h3 id=&quot;contributions-welcome&quot;&gt;Contributions Welcome&lt;/h3&gt;
&lt;p&gt;Found a bug? Have an idea for a feature? Open an issue or a pull request. The project is MIT licensed, so feel free to fork and build your own version.&lt;/p&gt;
&lt;h3 id=&quot;let-s-connect&quot;&gt;Let's Connect&lt;/h3&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/byllzz/gridzzly&quot;&gt;@byllzz&lt;/a&gt;&lt;br&gt;
Twitter: &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/bilalmlkdev&quot;&gt;@bilalmlkdev&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;em&gt;If you found this useful, please ❤️ the post and share it with your network.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Happy Gridding! 🎉&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20593/gridzzly-build-css-grid-layouts-visually-no-code-required</guid>
<pubDate>Sun, 14 Jun 2026 17:29:19 +0000</pubDate>
</item>
<item>
<title>An open-source CSS animation library with a live preview grid, creator suite, and community submissions</title>
<link>https://coderlegion.com/20487/open-source-animation-library-with-live-preview-grid-creator-suite-community-submissions</link>
<description>&lt;p&gt;&lt;strong&gt;CSSFrames: GPU-Accelerated Motion Without the Runtime Weight:&lt;/strong&gt;&lt;br&gt;
JavaScript animation libraries like GSAP, Framer Motion, and anime.js are powerful tools - but they come with a cost. Bundle weight, parsing overhead, potential layout thrashing, and the complexity of managing animations at runtime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What if there was a better way?&lt;/strong&gt;&lt;br&gt;
CSSFrames is a curated library of production-ready motion presets built entirely on pure CSS keyframes. Browse, preview, customize, and export — all without touching JavaScript. No runtime overhead. No layout shifts. No dropped frames.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://cssframes.vercel.app&quot;&gt;Try it live&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/byllzz/cssframes.git&quot;&gt;View on GitHub&lt;/a&gt; (MIT licensed)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Problem With JavaScript Animations&lt;/strong&gt;&lt;br&gt;
Every animation library comes with a runtime cost. The browser must:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parse and execute JavaScript&lt;/li&gt;
&lt;li&gt;Calculate animated values in real-time&lt;/li&gt;
&lt;li&gt;Trigger layout recalculations (reflow)&lt;/li&gt;
&lt;li&gt;Repaint affected DOM nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For simple, repeatable animations, this is overkill. CSS keyframes, by contrast, are declarative, precomputed, and offloaded directly to the GPU.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Solution: Pure CSS, Pure Performance&lt;/strong&gt;&lt;br&gt;
CSSFrames runs every animation on just two GPU-composited properties: transform and opacity. This means:&lt;/p&gt;
&lt;p&gt;✓ Zero layout shifts - no reflow or repaint&lt;br&gt;
✓ GPU acceleration - animations render on the compositor thread&lt;br&gt;
✓ Drop-in ready - one class, instant animation&lt;br&gt;
✓ Framework agnostic - works in React, Vue, plain HTML, anywhere&lt;/p&gt;
&lt;p&gt;Just add a class. The animation handles the rest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What CSSFrames Includes:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;1. Live Preview Grid&lt;/strong&gt;&lt;br&gt;
Every preset previews in real-time on authentic UI elements: buttons, cards, loaders, icons, and text. Switch to Theater Mode to focus on a single animation full-screen. Understand what each preset does before you export.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Creator Suite&lt;/strong&gt;&lt;br&gt;
Built on Monaco Editor, the Creator Suite lets you author custom keyframes without leaving the browser. Set duration, tweak easing curves, preview live, and export instantly. Perfect for building brand-specific motion systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. One-Click Export&lt;/strong&gt;&lt;br&gt;
Every animation exports in multiple formats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raw CSS — @keyframes definition + class&lt;/li&gt;
&lt;li&gt;Tailwind Config — Ready-to-paste keyframes and animation blocks&lt;/li&gt;
&lt;li&gt;Metadata — Duration and easing values for documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Community Grid&lt;/strong&gt;&lt;br&gt;
Developers can submit custom presets that persist via a REST API (powered by Railway). Browse and use animations created by the community. Every submission is live for all users — no build step required.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What's Included&lt;/strong&gt;&lt;br&gt;
Seven animation categories:&lt;br&gt;
Buttons · Loaders · Entrances · Text · Cards · Icons · Shapes&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preset types:&lt;/strong&gt;&lt;br&gt;
Shimmer sweeps, typewriter effects, morphing blobs, staggered reveals, glitch systems, pulse rings, stroke-draw animations — all GPU-composited, all pure CSS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Stack&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Frontend: React + Vite&lt;/li&gt;
&lt;li&gt;Styling: Tailwind CSS&lt;/li&gt;
&lt;li&gt;Code Editor: Monaco Editor&lt;/li&gt;
&lt;li&gt;Syntax Highlighting: Prism.js&lt;/li&gt;
&lt;li&gt;Backend API: Express.js&lt;/li&gt;
&lt;li&gt;Hosting: Vercel (frontend) · Railway (API)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Get Started&lt;/strong&gt;&lt;br&gt;
Clone the repository and run locally:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;bashgit clone https://github.com/byllzz/cssframes.git
cd cssframes
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the community API server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;bashcd server
npm install
npm start
Then add this to your .env:
VITE_API_URL=http://localhost:3001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why This Matters&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;CSSFrames&lt;/strong&gt; solves a real problem: developers often reach for heavy animation libraries when CSS alone can deliver the same results - faster, lighter, and more reliably.&lt;/p&gt;
&lt;p&gt;If &lt;strong&gt;CSSFrames&lt;/strong&gt; helped you build smoother, more performant UI without bloating your bundle, a ⭐ on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/byllzz/cssframes.git&quot;&gt;GitHub&lt;/a&gt; would mean a lot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dai86gdqjc5abkz7yro9.png&quot; alt=&quot;Cssframes Preview&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6tk5mwcr1464hnwzra59.png&quot; alt=&quot;Cssframes Preview&quot;&gt;&lt;/p&gt;
&lt;p&gt;What's your favorite preset? Drop it in the comments below &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20487/open-source-animation-library-with-live-preview-grid-creator-suite-community-submissions</guid>
<pubDate>Sat, 13 Jun 2026 05:01:45 +0000</pubDate>
</item>
<item>
<title>The Executive Interface: Translating AI Telemetry into C-Level Decisions</title>
<link>https://coderlegion.com/18142/the-executive-interface-translating-ai-telemetry-into-c-level-decisions</link>
<description>&lt;p&gt;The most highly engineered artificial intelligence backend fails to secure financial viability if upper management lacks the mechanism to visualize its direct impact on profitability. Applied &lt;strong&gt;Operations Engineering&lt;/strong&gt; does not terminate inherently within a relational database architecture; the structural cycle invariably concludes on the decision-maker’s screen.&lt;/p&gt;
&lt;p&gt;The asynchronous orchestration model implemented in previous segments (CrewAI piloting an LLM securely against deterministic SQL Supabase arrays) yields robust operational execution. This concluding segment reinforces the deployment by exposing the mathematical core toward a managerial interface deliberately calibrated exclusively to communicate &lt;em&gt;Return on Investment (ROI)&lt;/em&gt; calculations and industrial downtime mitigation.&lt;/p&gt;
&lt;h4 id=&quot;the-standardized-data-contract-formulating-the-analytical-api&quot;&gt;The Standardized Data Contract: Formulating the Analytical API&lt;/h4&gt;
&lt;p&gt;In order to rigorously decouple the cognitive analysis load originating from the underlying agent engines, balancing execution layers evenly across the network, we introduce a simulated Read-Replica structure atop the FastAPI environment (&lt;code&gt;dashboard_api.py&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This REST terminal strictly exposes mathematical telemetry archived implicitly by our evaluating Agent resolving inbound PDNs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.get(&quot;/api/v1/dashboard/risk-metrics&quot;)
def get_risk_metrics():
    # Enforces a static JSON contract for Frontend ingestion
    return {
        &quot;total_risk_eur&quot;: 15450.00,
        &quot;active_alerts&quot;: 2,
        &quot;affected_skus&quot;: [
            {
                &quot;sku&quot;: &quot;DRONE-X1&quot;,
                &quot;margin_at_risk&quot;: 12500.00,
                &quot;status&quot;: &quot;CRITICAL LTB REQUIRED&quot;,
                &quot;trigger_mpn&quot;: &quot;TI-CAP-10U-50&quot;
            }
            # ...
        ],
        &quot;agent_logs&quot;: [
            &quot;[18:31:02] [Webhook] Inbound email parsed.&quot;,
            &quot;[18:31:05] [CrewAI] Parsing semantic text. MPN extracted: TI-CAP-10U-50.&quot;,
            &quot;[18:31:07] [CrewAI] P&amp;amp;L calculated. Retained Margin at Risk: 12,500.00 EUR.&quot;
        ]
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The absolute numerical aggregate indexed directly as &lt;code&gt;total_risk_eur&lt;/code&gt; unifies the financial exposure stemming from synchronous alert computations, guaranteeing clear C-Level synthesis.&lt;/p&gt;
&lt;h4 id=&quot;the-frontend-engineering-paradigm-minimalist-fundamentals&quot;&gt;The Frontend Engineering Paradigm: Minimalist Fundamentals&lt;/h4&gt;
&lt;p&gt;The modern software landscape predominantly cultivates vast dependencies enveloping Node.js-based heavy frameworks (such as React or Angular), frequently extending these burdens even toward purely unidirectional Read-Only telemetry boards.&lt;/p&gt;
&lt;p&gt;Contrasting this convention, a functional operational factory monitor (Andon boarding mechanics) strictly dictates absolute instantaneous availability, zero massive transient module repositories, and static instantaneous rendering boundaries. Employing rigid &lt;em&gt;Dogfooding&lt;/em&gt; and adherence to strict First Principles methodologies, we outline a standard static &lt;em&gt;index.html&lt;/em&gt; directly provisioned via CDN utilities.&lt;/p&gt;
&lt;p&gt;The underlying stack distills precisely toward standard native Web API functionalities mapping the unadorned &lt;code&gt;Fetch()&lt;/code&gt; standard enclosed inside strict &lt;strong&gt;Vanilla JavaScript&lt;/strong&gt; bound dynamically by atomic atomic styling via &lt;strong&gt;Tailwind CSS&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&quot;http://localhost:8001/api/v1/dashboard/risk-metrics&quot;)
    .then(response =&amp;gt; response.json())
    .then(data =&amp;gt; {
        // Enforcing Macro-Econometric bindings
        document.getElementById('totalRisk').innerText =
            new Intl.NumberFormat('en-US', { style: 'currency', currency: 'EUR' }).format(data.total_risk_eur);
       
        // Channeling active event telemetry loops
        const consoleDiv = document.getElementById('telemetryConsole');
        data.agent_logs.forEach(log =&amp;gt; {
            const line = document.createElement('div');
            line.innerText = `&amp;gt; ${log}`;
            consoleDiv.appendChild(line);
        });
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;visual-engineering-for-critical-operations-industrial-ux&quot;&gt;Visual Engineering for Critical Operations: Industrial UX&lt;/h4&gt;
&lt;p&gt;To aggressively preempt “Analysis Paralysis” constraints across executive viewers, structural interface segregation clusters data exclusively within three core categorical axes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Critical Analysis Threshold (Macro KPI):&lt;/strong&gt; Disclosing the consolidated risk metric bound heavily inside an unmissable perimeter. Direct and literal exposure communicating the 15,450.00 EUR justifying the technological expenditure securing AI frameworks within the board room.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constraint Tabulation (Blacklist Registers):&lt;/strong&gt; A starkly structured matrix elevating purely the top-tier commercial product identifier base (&lt;code&gt;DRONE-X1&lt;/code&gt;), deliberately isolating the complex inner web of microscopic resistors below. The target centers entirely upon final operational line constraints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Live Core (Active Telemetry Processing):&lt;/strong&gt; Real-time monitoring console mapped visually to broadcast the raw cognitive iterations driven by the internal CrewAI process. Reassuring operational directors visually confirming the endless, uninterrupted analytic progression actively shielding logistics lines without manual prompting.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;series-culmination-a-blue-ocean-extracted-in-logistics&quot;&gt;Series Culmination: A Blue Ocean Extracted in Logistics&lt;/h4&gt;
&lt;p&gt;This specific deployment sequence concludes our intensive technical trajectory mapped consistently to operational engineering, advancing drastically past speculative archaic forecasting techniques relying broadly on weak statistical projections.&lt;/p&gt;
&lt;p&gt;We acknowledged material component obsolescence strictly as systematic margin erosion outlined prominently across IEC 62402 requirements. We encoded intricate supply interdependencies mapping vectors within rigorous recursive Graph databases. We algorithmically neutralized chaotic raw ingestion inconsistencies integrating Pandas filters; allocated heavily restrained advanced Language Agents dynamically converting abstract linguistic payloads autonomously into structured data matrices, and decisively scaled robust event-driven operations persistently spanning active Background FastAPI nodes.&lt;/p&gt;
&lt;p&gt;Physical logistics obsolescence fundamentally resigns its classification as an uncontrollable, unpredictable Black Swan occurrence. Converted exclusively into a precisely measurable, purely mitigated domestic phenomenon, we have ultimately established a definitive and asymmetric tactical advantage securing systemic industrial resilience.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/18142/the-executive-interface-translating-ai-telemetry-into-c-level-decisions</guid>
<pubDate>Sun, 17 May 2026 07:27:53 +0000</pubDate>
</item>
<item>
<title> Underwind - The Wordpress Starter Theme That Let the Wind Change Direction</title>
<link>https://coderlegion.com/15708/underwind-the-wordpress-starter-theme-that-let-the-wind-change-direction</link>
<description>&lt;p&gt;Once upon a commit in the vast kingdom of WordPress, there lived a humble theme called &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://underscores.me&quot;&gt;Underscores&lt;/a&gt;&lt;/strong&gt; — &lt;em&gt;a barebones starter with just enough HTML5 templates and clean PHP to make even seasoned developers smile&lt;/em&gt;. It didn’t hold much style, but it laid the foundation for dreams to grow. &lt;/p&gt;
&lt;p&gt;For many moons, artisans grafted frameworks like &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://understrap.com&quot;&gt;Understrap&lt;/a&gt;&lt;/strong&gt; upon this sturdy root — binding Underscores’ lean code with Bootstrap’s responsive might, crafting an engine that saved hours in every quest. &lt;/p&gt;
&lt;p&gt;But developers began to whisper of a new wind…&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-the-call-of-a-new-breeze&quot;&gt;️ The Call of a New Breeze&lt;/h3&gt;
&lt;p&gt;Legend whispers that on a morning drenched in dew and VS Code tabs, a band of explorers at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/melasistema&quot;&gt;Melasistema&lt;/a&gt; gazed upon Understrap and sighed. They loved Bootstrap’s lattice and grid, but their hearts beat in utility-first classes and blazing dev environments. Thus, in a stroke of inspiration, they took the &lt;strong&gt;rock-solid foundation of Underscores&lt;/strong&gt; and let the &lt;strong&gt;wind change direction&lt;/strong&gt; — welcoming Tailwind CSS into the fold.&lt;/p&gt;
&lt;p&gt;And they named this creation… &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/melasistema/underwind&quot;&gt;Underwind&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-makes-underwind-magical&quot;&gt;What Makes Underwind Magical&lt;/h3&gt;
&lt;p&gt;In the enchanted lands of theme development, Underwind blends tradition and modern craft. From its README scroll:&lt;/p&gt;
&lt;p&gt;✨ &lt;strong&gt;A Modern Dev Environment&lt;/strong&gt; — Powered by Vite with Hot Module Replacement (HMR), changes appear like magic in the blink of an eye. &lt;/p&gt;
&lt;p&gt;✨ &lt;strong&gt;Utility-First Styling&lt;/strong&gt; — Tailwind CSS lets builders compose UIs directly in markup without wrestling weights of custom classes. &lt;/p&gt;
&lt;p&gt;✨ &lt;strong&gt;Alpine.js Ready&lt;/strong&gt; — For small interactive components that feel alive with almost no overhead.&lt;/p&gt;
&lt;p&gt;✨ &lt;strong&gt;Lean &amp;amp; Solid Foundation&lt;/strong&gt; — Lean Underscores templates keep the bones strong, ready for any bespoke theme you imagine.&lt;/p&gt;
&lt;p&gt;✨ &lt;strong&gt;WooCommerce-Friendly&lt;/strong&gt; — A boon for merchants who venture into online marketplaces.&lt;/p&gt;
&lt;p&gt;✨ &lt;strong&gt;Integrated Code Quality Tools&lt;/strong&gt; — With ESLint, Prettier, and PHP_CodeSniffer, even the fiercest code standards never go astray.&lt;/p&gt;
&lt;p&gt;This was not just another starter — it was &lt;em&gt;a canvas with wings&lt;/em&gt;, inviting creators to craft faster and more joyfully than ever before.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;getting-started-the-hero-s-quest&quot;&gt;Getting Started: The Hero’s Quest&lt;/h3&gt;
&lt;p&gt;To begin your journey with &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/melasistema/underwind&quot;&gt;Underwind&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Clone the repository into your WordPress themes folder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install the dependencies with &lt;code&gt;npm install&lt;/code&gt; and &lt;code&gt;composer install&lt;/code&gt;. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;npm run dev&lt;/code&gt; to watch your creations come alive with instant reloads.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build for production with &lt;code&gt;npm run build&lt;/code&gt;. &lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Suddenly, what once was a blank slate becomes an artisan’s delight — with Tailwind classes flowing like wind through sails.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-underwind-matters&quot;&gt;Why Underwind Matters&lt;/h3&gt;
&lt;p&gt;In the kingdom of WordPress themes, many paths exist: from the bare essence of Underscores to Bootstrap-laden frameworks like Understrap.  Yet few combined &lt;em&gt;modern tooling, utility-first styling, and a refined developer experience&lt;/em&gt; into a single starter ready for today’s workflows.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/melasistema/underwind&quot;&gt;Underwind&lt;/a&gt; doesn’t just give you a foundation — it gives you &lt;em&gt;momentum&lt;/em&gt;. It’s the theme that whispers to you:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Design with joy. Develop with speed. Let the wind carry you forward.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;epilogue&quot;&gt;Epilogue&lt;/h3&gt;
&lt;p&gt;And so, as the sun set on legacy workflows and the first rays of Tailwind dawned on WordPress development, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/melasistema/underwind&quot;&gt;Underwind&lt;/a&gt; stood tall — a new standard for ambitious theme creators.&lt;/p&gt;
&lt;p&gt;May your commits be meaningful,&lt;br&gt;
and your Tailwind classes ever abundant.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15708/underwind-the-wordpress-starter-theme-that-let-the-wind-change-direction</guid>
<pubDate>Fri, 24 Apr 2026 07:34:08 +0000</pubDate>
</item>
<item>
<title>✨ A New Stack — Introducing Redis Nuxt Blog: A Sci-Fi Blog Boilerplate from a Galaxy Not So Far Away</title>
<link>https://coderlegion.com/15670/new-stack-introducing-redis-nuxt-blog-sci-fi-blog-boilerplate-from-a-galaxy-not-so-far-away</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;“A long time ago, in a server rack far, far away, a lone developer sought a faster way to publish their thoughts across the galaxy…”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’re a developer roaming the cosmic void in search of the perfect blogging platform — one fast enough to outrun hyperspace lag, simple enough to configure from a single holocron file, and powered by the mighty Force of Redis — your quest ends here.&lt;/p&gt;
&lt;p&gt;Welcome to &lt;strong&gt;Redis Nuxt Blog&lt;/strong&gt;, a futuristic, high-performance boilerplate forged with &lt;strong&gt;Nuxt 3&lt;/strong&gt;, &lt;strong&gt;Redis Stack&lt;/strong&gt;, and a containerized fleet of &lt;strong&gt;Docker Star Destroyers&lt;/strong&gt;.&lt;br&gt;
Inspired by the legendary &lt;em&gt;Salvatore “antirez” Sanfilippo&lt;/em&gt;, creator of Redis and intergalactic craftsman of absurdly fast data structures, this project aims to honor that sci-fi spirit — equal parts elegance, speed, and space-wizardry.&lt;/p&gt;
&lt;p&gt;Grab your lightsabers, devs. We’re jumping to lightspeed. ⚡&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/melasistema/redis-blog/master/resources/images/redis-blog-poster-image.jpg&quot; alt=&quot;Redis Nuxt Blog&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-another-blog-boilerplate-&quot;&gt;Why Another Blog Boilerplate?&lt;/h3&gt;
&lt;p&gt;Because most blog starters are like broken droids — sluggish, uncustomizable, and likely to beep something rude at you when you change a font.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Redis Nuxt Blog&lt;/strong&gt; is built for developers who want:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A fast, SSR-friendly, SEO-happy front-end&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A database engine so quick it would embarrass a pod-racer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A deeply configurable UI without fiddling with 27 separate config files&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A ready-to-launch Docker setup&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A CLI so efficient it feels like talking to R2-D2 (but without the panic screams)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A theming system powered by Tailwind CSS, customizable from one file&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Markdown-powered post content&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic excerpts and content width control&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-core-features-translated-from-wookiee-&quot;&gt;✨ Core Features (Translated from Wookiee)&lt;/h3&gt;
&lt;h4 id=&quot;nuxt-js-3-frontend&quot;&gt;Nuxt.js 3 Frontend&lt;/h4&gt;
&lt;p&gt;Modern. Fast. SSR-powered.&lt;br&gt;
Perfect for blogs, portals, and broadcasting your galactic manifestos.&lt;/p&gt;
&lt;h4 id=&quot;-redis-stack-as-the-primary-database&quot;&gt;⚡ Redis Stack as the Primary Database&lt;/h4&gt;
&lt;p&gt;Forget SQL. Forget slow disks.&lt;br&gt;
We harness the &lt;strong&gt;Quantum Hyperdrive&lt;/strong&gt; (a.k.a RedisJSON + Redis Sorted Sets) to store posts with blazing speed.&lt;/p&gt;
&lt;p&gt;Salvatore Sanfilippo didn’t intend Redis to be a sci-fi artifact… but let’s be honest, it might as well have been forged from midichlorians.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/melasistema/redis-blog/master/resources/images/redis-blog-hyperdrive-performance.jpg&quot; alt=&quot;Redis Nuxt Blog&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;one-config-file-to-rule-them-all-now-with-full-tailwind-integration-&quot;&gt;One Config File to Rule Them All (Now With Full Tailwind Integration)&lt;/h4&gt;
&lt;p&gt;Every visual setting — fonts, weights, palette, spacing, even content width — comes from:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;config/blog.config.ts&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;This single file now controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Typography per heading level&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Full Tailwind color palette generation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Global content max-width&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Header title &amp;amp; tagline&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Layout constraints&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Balance to the styling Force, at last.&lt;/p&gt;
&lt;h4 id=&quot;-post-excerpts-new-&quot;&gt;✂️ Post Excerpts (New!)&lt;/h4&gt;
&lt;p&gt;Enable &lt;strong&gt;post previews&lt;/strong&gt; on the homepage with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;postExcerpt.enabled&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;postExcerpt.maxLength&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Perfect for teasing your readers like a Holonet trailer.&lt;/p&gt;
&lt;h4 id=&quot;-post-navigation&quot;&gt;↔️ Post Navigation&lt;/h4&gt;
&lt;p&gt;Navigate between posts like browsing encrypted Jedi archives.&lt;/p&gt;
&lt;h4 id=&quot;markdown-support&quot;&gt;Markdown Support&lt;/h4&gt;
&lt;p&gt;Posts are fully Markdown-powered — headings, lists, code blocks, even multi-line input via CLI.&lt;/p&gt;
&lt;p&gt;Write your galactic knowledge in pure Markdown. No HTML acrobatics needed.&lt;/p&gt;
&lt;h4 id=&quot;dockerized-everything&quot;&gt;Dockerized Everything&lt;/h4&gt;
&lt;p&gt;Spin up Nuxt + Redis with one command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker compose up -d --build&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;Your dev environment becomes as consistent as Stormtrooper aim (but… good).&lt;/p&gt;
&lt;h4 id=&quot;content-cli-upgraded-&quot;&gt;Content CLI (Upgraded!)&lt;/h4&gt;
&lt;p&gt;Create, list, delete posts with simple commands:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm run new-post
npm run post:list
npm run post:delete&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;The CLI now supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Multi-line Markdown input&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatically generated slugs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Default author fallback&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You are now the administrator of your own Holocron Library.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-tech-stack-what-our-starship-is-made-of&quot;&gt;️ Tech Stack: What Our Starship Is Made Of&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nuxt 3&lt;/strong&gt; — the elegant lightsaber&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redis Stack w/ RedisJSON&lt;/strong&gt; — the hyperdrive core&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt; — the robe of a modern Jedi developer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google Fonts module&lt;/strong&gt; — for typographic wizardry&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker Compose&lt;/strong&gt; — the Imperial Fleet&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TypeScript + tsx runtime&lt;/strong&gt; — because even smugglers need types&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/melasistema/redis-blog/master/resources/images/redis-blog-the-ultimate-tech-stack.jpg&quot; alt=&quot;Redis Nuxt Blog&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-the-holocron-of-configuration-updated-&quot;&gt;️ The Holocron of Configuration (Updated!)&lt;/h3&gt;
&lt;p&gt;Everything — fonts, colors, layout, excerpts, pagination — lives in:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;config/blog.config.ts&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;New additions include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;headerTitle&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;headerTagline&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dynamic Tailwind theme generation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;contentMaxWidth&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Post excerpts config&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Want your blog to have an imperial blue palette?&lt;br&gt;
Done.&lt;/p&gt;
&lt;p&gt;Want H1 in &lt;em&gt;Englebert&lt;/em&gt; and H2 in &lt;em&gt;Roboto Slab&lt;/em&gt;?&lt;br&gt;
Absolutely.&lt;/p&gt;
&lt;p&gt;Want your content width to feel like the inside of a Star Destroyer corridor?&lt;br&gt;
Just set a new max-width.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;api-endpoints-for-the-droids-among-us-&quot;&gt;API Endpoints (For the Droids Among Us)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Endpoint &lt;/th&gt;&lt;th&gt;Method &lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/api/posts&lt;/code&gt; &lt;/td&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;Retrieve posts. Supports &lt;code&gt;?page=N&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/api/posts/:slug&lt;/code&gt; &lt;/td&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;Fetch single post + neighbors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;/api/tags&lt;/code&gt; &lt;/td&gt;&lt;td&gt;GET &lt;/td&gt;&lt;td&gt;Retrieve all tags&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Perfect for custom frontends, mobile apps, or your own droid companion.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-developers-love-this&quot;&gt;Why Developers Love This&lt;/h3&gt;
&lt;p&gt;Because &lt;strong&gt;simplicity&lt;/strong&gt; + &lt;strong&gt;power&lt;/strong&gt; = developer bliss.&lt;/p&gt;
&lt;p&gt;Because Redis is a piece of sci-fi technology disguised as a database.&lt;/p&gt;
&lt;p&gt;Because Nuxt 3 is smooth as sliding through hyperspace.&lt;/p&gt;
&lt;p&gt;Because Tailwind lets you customize everything without rewriting CSS.&lt;/p&gt;
&lt;p&gt;Because Markdown posts feel like writing Jedi scrolls.&lt;/p&gt;
&lt;p&gt;And because every post stored in Redis Sorted Sets is basically organized by the Force.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;author-amp-origin-story&quot;&gt;Author &amp;amp; Origin Story&lt;/h3&gt;
&lt;p&gt;Built by &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/melasistema&quot;&gt;Luca Visciola&lt;/a&gt;&lt;/strong&gt; — drummer, developer, and happy dad.&lt;/p&gt;
&lt;p&gt;Inspired by &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://antirez.com&quot;&gt;Salvatore “antirez” Sanfilippo&lt;/a&gt;&lt;/strong&gt;, whose creation Redis is widely believed to be running not on silicon… but on compressed fragments of a neutron star.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-transmission&quot;&gt;Final Transmission&lt;/h3&gt;
&lt;p&gt;If you're searching for a fast, flexible, sci-fi-friendly blog engine that handles posts faster than the Millennium Falcon outruns TIE Fighters, this boilerplate may be the one you’re looking for.&lt;/p&gt;
&lt;p&gt;Clone it.&lt;br&gt;
Customize it.&lt;br&gt;
Seed it.&lt;br&gt;
Deploy it.&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;GitHub Repository:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/melasistema/redis-blog&quot;&gt;https://github.com/melasistema/redis-blog&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And may the &lt;strong&gt;Force of Redis&lt;/strong&gt; be with you. ❤️‍&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15670/new-stack-introducing-redis-nuxt-blog-sci-fi-blog-boilerplate-from-a-galaxy-not-so-far-away</guid>
<pubDate>Thu, 23 Apr 2026 16:49:31 +0000</pubDate>
</item>
<item>
<title>Open-Source Spin-the-Wheel Website–No Ads, No Trackers</title>
<link>https://coderlegion.com/15146/open-source-spin-the-wheel-website-no-ads-no-trackers</link>
<description>&lt;p&gt;Picksy is a simple, open-source spin-the-wheel app for choosing a random name from a list without ads, trackers, or unnecessary clutter.&lt;/p&gt;
&lt;p&gt;I originally built this project about four years ago, then forgot about it. After rediscovering the repository, I decided to revisit it, finish what I started, and finally share it.&lt;/p&gt;
&lt;p&gt;I first created it during the pandemic, when spin-the-wheel tools became popular for online team-building, virtual game nights with friends and family, and similar activities. Most of the available options were cluttered with ads and trackers, so I built a simpler alternative without either.&lt;/p&gt;
&lt;p&gt;After refreshing the UI and updating the dependencies, the project is now complete.&lt;/p&gt;
&lt;p&gt;You can try it here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://picksy.sudojoie.com&quot;&gt;Picksy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you want to see the code or contribute, the project is available on GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/joiellantero/picksy&quot;&gt;joiellantero/picksy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2tfohaj5gdf1biht0d8j.png&quot; alt=&quot;Picksy Website Screenshot&quot;&gt;&lt;/p&gt;
&lt;p&gt;This release also gave me a chance to revisit an older idea and clean it up into something I would actually use today: lightweight, straightforward, and easy to share.&lt;/p&gt;
&lt;p&gt;If you prefer working in the terminal, I also made a Python-based version here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/joiellantero/name-roulette&quot;&gt;name-roulette&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15146/open-source-spin-the-wheel-website-no-ads-no-trackers</guid>
<pubDate>Sun, 19 Apr 2026 05:19:14 +0000</pubDate>
</item>
<item>
<title>Recreating History: Building a Windows 98 Disk Defrag Simulator with Modern Web Tech</title>
<link>https://coderlegion.com/13057/recreating-history-building-a-windows-98-disk-defrag-simulator-with-modern-web-tech</link>
<description>&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ysehm1enkypm8d71gnxj.gif&quot; alt=&quot;Defragmenting a disk in Windows 98 Disk Defrag Simulator&quot;&gt;&lt;/p&gt;
&lt;p&gt;Hey fellow devs! I'm Dennis Morello, a Senior Frontend Engineer with a passion for both cutting-edge web technologies and retro computing. I'm excited to share my latest project that combines these interests: a faithful recreation of the Windows 98 Disk Defragmenter, built entirely for the web.&lt;/p&gt;
&lt;p&gt;Check it out: &lt;a rel=&quot;nofollow&quot; href=&quot;https://defrag98.com&quot;&gt;defrag98.com&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;update-19-jul-2024-we-re-on-product-hunt-&quot;&gt;Update 19 Jul 2024: We're on Product Hunt!&lt;/h3&gt;
&lt;p&gt;Great news, everyone! The Windows 98 Disk Defragmenter Simulator is now &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.producthunt.com/posts/windows-98-disk-defrag-simulator&quot;&gt;live on Product Hunt&lt;/a&gt;. This is an exciting milestone for the project, and I'm thrilled to share it with a wider audience of tech enthusiasts and nostalgia lovers.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.producthunt.com/posts/windows-98-disk-defrag-simulator?embed=true&amp;amp;utm_source=badge-featured&amp;amp;utm_medium=badge&amp;amp;utm_souce=badge-windows-98-disk-defrag-simulator&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=472182&amp;amp;theme=light&quot; alt=&quot;Windows 98 Disk Defrag Simulator - Relive the 90s: Windows 98 Defrag Simulator Nostalgia | Product Hunt&quot; style=&quot;width: 250px; height: 54px;&quot; width=&quot;250&quot; height=&quot;54&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;update-14-jul-2024-featured-on-the-verge-and-hacker-news-&quot;&gt;Update 14 Jul 2024: Featured on The Verge and Hacker News!&lt;/h3&gt;
&lt;p&gt;The project has been featured on &lt;a rel=&quot;nofollow&quot; href=&quot;https://news.ycombinator.com/item?id=40962195&quot;&gt;Hacker News&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.theverge.com/2024/7/14/24198206/take-a-moment-to-reflect&quot;&gt;The Verge&lt;/a&gt;! Thank you to everyone who has tried it out and shared their love for this blast from the past.&lt;/p&gt;
&lt;h3 id=&quot;the-tech-stack&quot;&gt;The Tech Stack&lt;/h3&gt;
&lt;p&gt;For this project, I leveraged some of the most powerful tools in modern web development:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;React&lt;/strong&gt;: For building the UI components&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Next.js&lt;/strong&gt;: To optimize performance and SEO&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zustand&lt;/strong&gt;: To manage the state of the app&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TailwindCSS&lt;/strong&gt;: To style the app, along with &lt;a rel=&quot;nofollow&quot; href=&quot;https://jdan.github.io/98.css&quot;&gt;98.css&lt;/a&gt; for bringing in the Windows 98 aesthetic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Radix UI Primitives&lt;/strong&gt;: For accessible interactive components like sliders and modals&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel&lt;/strong&gt;: The hosting platform for the app&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;challenges-and-solutions&quot;&gt;Challenges and Solutions&lt;/h3&gt;
&lt;h4 id=&quot;1-recreating-the-defragmentation-algorithm&quot;&gt;1. Recreating the Defragmentation Algorithm&lt;/h4&gt;
&lt;p&gt;One of the biggest challenges was implementing a defragmentation algorithm that felt authentic. I created a custom algorithm that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Randomly selects clusters to process&lt;/li&gt;
&lt;li&gt;Simulates file movements across the disk&lt;/li&gt;
&lt;li&gt;Adjusts processing speed based on the selected virtual drive&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-pixel-perfect-ui-recreation&quot;&gt;2. Pixel-Perfect UI Recreation&lt;/h4&gt;
&lt;p&gt;Achieving the exact look and feel of Windows 98 required meticulous attention to detail. I used a combination of 98.css and TailwindCSS to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Match colors precisely&lt;/li&gt;
&lt;li&gt;Recreate the characteristic 'chunky' borders&lt;/li&gt;
&lt;li&gt;Implement the classic Windows 98 typography&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-simulating-hard-drive-sounds&quot;&gt;3. Simulating Hard Drive Sounds&lt;/h4&gt;
&lt;p&gt;To add an extra layer of nostalgia, I implemented realistic hard drive sounds. This involved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recording and editing authentic HDD sounds&lt;/li&gt;
&lt;li&gt;Leveraging the Web Audio API for precise playback control&lt;/li&gt;
&lt;li&gt;Synchronizing sound effects with the visual defragmentation process&lt;/li&gt;
&lt;li&gt;Adapting the HDD sounds to the chosen drive speed&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;This project was a fantastic opportunity to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deep dive into the intricacies of writing a custom defrag algorithm, and find a balance between performance and simulation accuracy&lt;/li&gt;
&lt;li&gt;Explore the challenges of accurately simulating legacy software&lt;/li&gt;
&lt;li&gt;Push the boundaries of what's possible in browser-based applications&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h3&gt;
&lt;p&gt;This is a project I started just for fun, but I'm excited to see where it goes. I'm looking forward to continuing to improve the app, and adding more features as feedbacks from users come in.&lt;/p&gt;
&lt;p&gt;I'd love to hear your thoughts, suggestions, or questions about this project. Have you worked on similar retro tech simulations? What challenges did you face?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/13057/recreating-history-building-a-windows-98-disk-defrag-simulator-with-modern-web-tech</guid>
<pubDate>Tue, 17 Mar 2026 13:54:18 +0000</pubDate>
</item>
<item>
<title>From Zero to Hero: Create a Cyberpunk Snake Game with Real-Time Ranking using Supabase and Vanilla J</title>
<link>https://coderlegion.com/12990/from-zero-hero-create-cyberpunk-snake-game-with-real-time-ranking-using-supabase-vanilla</link>
<description>&lt;p&gt;Do you remember the classic Snake from the old Nokias? Now imagine it with neon lights, synthesized music, explosive particles, and most importantly: &lt;strong&gt;connected to the cloud&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Web game development has experienced a renaissance thanks to the power of HTML5 Canvas and &lt;strong&gt;Serverless&lt;/strong&gt; tools. In the past, if you wanted to save your players' scores to create a global leaderboard, you needed to set up a server, configure a REST API, manage databases, and pay for hosting. Today, thanks to Backend-as-a-Service (BaaS), we can do it in minutes.&lt;/p&gt;
&lt;p&gt;In this tutorial, we are going to build &lt;strong&gt;&quot;Neon Snake&quot;&lt;/strong&gt;, an aesthetic and functional web arcade game that uses &lt;strong&gt;Supabase&lt;/strong&gt; (an Open Source alternative to Firebase) to persist data in PostgreSQL.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/game_snake/imagen_snake_game.png&quot; alt=&quot;Conceptual image of Snake Game&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;-the-tech-stack&quot;&gt;️ The Tech Stack&lt;/h3&gt;
&lt;p&gt;We're going to keep it simple but powerful. No heavy frameworks, just the purity of the web:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; HTML5 Canvas + Vanilla JavaScript (ES6+).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Styles:&lt;/strong&gt; Tailwind CSS (via CDN) for the user interface.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backend:&lt;/strong&gt; Supabase (PostgreSQL + Realtime).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audio:&lt;/strong&gt; Web Audio API for code-generated sound effects.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-setting-up-the-backend-with-supabase&quot;&gt;Step 1: Setting up the Backend with Supabase&lt;/h3&gt;
&lt;p&gt;Before writing a single line of JavaScript, we need our brain in the cloud. Supabase offers us a real PostgreSQL database with an automatically generated API.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a free account at &lt;a rel=&quot;nofollow&quot; href=&quot;https://supabase.com&quot;&gt;supabase.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a new project called &lt;code&gt;NeonSnake&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Table Editor&lt;/strong&gt; section and create a new table called &lt;code&gt;snake_scores&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;the-magic-of-sql&quot;&gt;The Magic of SQL&lt;/h4&gt;
&lt;p&gt;To streamline the process, Supabase allows you to execute SQL commands directly. Go to the &lt;strong&gt;SQL Editor&lt;/strong&gt; and paste the following code. This will create the table structure and configure the security policies (RLS - Row Level Security) so your game can read and write data publicly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- 1. Create the scores table
create table snake_scores (
  id bigint generated by default as identity primary key,
  username text not null,
  score integer not null,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null
);

-- 2. Enable Row Level Security (RLS)
alter table snake_scores enable row level security;

-- 3. Policy to allow anyone to view the ranking (Public Read)
create policy &quot;Ver ranking publico&quot;
on snake_scores for select
using ( true );

-- 4. Policy to allow anyone to save their record (Public Write)
create policy &quot;Guardar puntuacion&quot;
on snake_scores for insert
with check ( true );
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Security Note:&lt;/strong&gt; In a commercial game, we would use full user authentication. For this arcade-style demo, we allow public (anonymous) access to reduce friction so anyone can play and log their record instantly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally, go to &lt;strong&gt;Settings &amp;gt; API&lt;/strong&gt; and copy your &lt;code&gt;Project URL&lt;/code&gt; and your &lt;code&gt;anon public key&lt;/code&gt;. You will need them shortly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-the-cyberpunk-frontend-html5-canvas-&quot;&gt;Step 2: The Cyberpunk Frontend (HTML5 + Canvas)&lt;/h3&gt;
&lt;p&gt;Our game will live in a single &lt;code&gt;index.html&lt;/code&gt; file. We will use &lt;strong&gt;Tailwind CSS&lt;/strong&gt; for the floating menus (Game Over, Start Screen) and the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element to render the game at 60 FPS.&lt;/p&gt;
&lt;p&gt;The basic structure is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Neon Snake 2077&amp;lt;/title&amp;gt;
    &amp;lt;script src=&quot;[https://cdn.tailwindcss.com](https://cdn.tailwindcss.com)&quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;style&amp;gt;
        @import url('[https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&amp;amp;display=swap](https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&amp;amp;display=swap)');
        body { font-family: 'Orbitron', sans-serif; background-color: #050505; }
        /* Neon glow effect for the canvas */
        canvas { box-shadow: 0 0 20px #0ff; border: 2px solid #0ff; }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body class=&quot;h-screen w-full flex items-center justify-center overflow-hidden bg-gray-900&quot;&amp;gt;

    &amp;lt;div class=&quot;relative&quot;&amp;gt;
        &amp;lt;canvas id=&quot;gameCanvas&quot; width=&quot;400&quot; height=&quot;400&quot;&amp;gt;&amp;lt;/canvas&amp;gt;
        
        &amp;lt;div id=&quot;gameOverMenu&quot; class=&quot;hidden absolute inset-0 bg-black/90 flex flex-col items-center justify-center z-10&quot;&amp;gt;
            &amp;lt;h2 class=&quot;text-4xl text-red-500 font-bold mb-4 drop-shadow-[0_0_10px_rgba(239,68,68,0.8)]&quot;&amp;gt;SYSTEM FAILURE&amp;lt;/h2&amp;gt;
            &amp;lt;input type=&quot;text&quot; id=&quot;usernameInput&quot; placeholder=&quot;Enter ID...&quot; class=&quot;bg-gray-800 text-cyan-400 p-2 rounded border border-cyan-500 mb-4 text-center outline-none&quot;&amp;gt;
            &amp;lt;button id=&quot;saveBtn&quot; class=&quot;bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-2 px-4 rounded shadow-[0_0_15px_#06b6d4] transition-all&quot;&amp;gt;
                UPLOAD DATA
            &amp;lt;/button&amp;gt;
            
            &amp;lt;div id=&quot;leaderboard&quot; class=&quot;mt-6 text-sm text-gray-300 w-64 text-left h-40 overflow-y-auto custom-scrollbar&quot;&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script type=&quot;module&quot;&amp;gt;
        import { createClient } from '[https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm](https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm)'
        
        // ... Game Logic here (See step 3) ...
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;game-logic-beyond-eating-apples&quot;&gt;Game Logic: Beyond Eating Apples&lt;/h4&gt;
&lt;p&gt;To give it that &quot;Pro&quot; touch and differentiate it from a basic tutorial, we implement several key mechanics in JavaScript:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Animation Loop:&lt;/strong&gt; Using &lt;code&gt;requestAnimationFrame&lt;/code&gt; to maintain fluidity at 60 FPS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Particle System:&lt;/strong&gt; An array of objects that spawn at the &quot;food&quot; coordinates when eaten and fade gradually, simulating a data explosion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic Firewalls:&lt;/strong&gt; From level 5 onwards, we generate static obstacles that the player must dodge, progressively increasing the difficulty.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hybrid Controls:&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PC:&lt;/strong&gt; We listen for &lt;code&gt;keydown&lt;/code&gt; events for directional arrows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile:&lt;/strong&gt; We capture &lt;code&gt;touchstart&lt;/code&gt; and &lt;code&gt;touchend&lt;/code&gt; to calculate the gesture direction (&lt;em&gt;swipe&lt;/em&gt;) and move the snake.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-connecting-the-dots-integration-with-supabase-&quot;&gt;Step 3: Connecting the Dots (Integration with Supabase)&lt;/h3&gt;
&lt;p&gt;Here is where the Backend-as-a-Service magic happens. We are going to connect our Canvas to the PostgreSQL database. Inside our &lt;code&gt;&amp;lt;script type=&quot;module&quot;&amp;gt;&lt;/code&gt; tag:&lt;/p&gt;
&lt;h4 id=&quot;1-client-initialization&quot;&gt;1. Client Initialization&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const supabaseUrl = 'YOUR_SUPABASE_PROJECT_URL';
const supabaseKey = 'YOUR_SUPABASE_ANON_KEY';
const supabase = createClient(supabaseUrl, supabaseKey);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-the-game-over-flow&quot;&gt;2. The &quot;Game Over&quot; Flow&lt;/h4&gt;
&lt;p&gt;When the player loses and hits &quot;UPLOAD DATA&quot;, we need to ensure the data is saved before showing the leaderboard. We will use &lt;code&gt;async/await&lt;/code&gt; to control this asynchronous flow.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function saveScore(username, score) {
    // 1. Save the score
    const { error } = await supabase
        .from('snake_scores')
        .insert([{ username: username, score: score }]);

    if (error) {
        console.error('Error uploading data:', error);
        alert('Connection error with the central server.');
        return;
    }
    
    // 2. Immediately after, update the leaderboard visually
    fetchLeaderboard();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-fetching-the-ranking-select-&quot;&gt;3. Fetching the Ranking (Select)&lt;/h4&gt;
&lt;p&gt;We retrieve the &lt;strong&gt;Top 20&lt;/strong&gt; sorted by score in descending order to show who rules the server.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function fetchLeaderboard() {
    const { data: scores, error } = await supabase
        .from('snake_scores')
        .select('username, score')
        .order('score', { ascending: false }) // Descending order
        .limit(20);                           // Only the top 20

    if (error) console.error('Error fetching leaderboard:', error);
    
    // Render in the DOM
    const list = document.getElementById('leaderboard');
    list.innerHTML = '&amp;lt;h3 class=&quot;text-cyan-400 border-b border-cyan-500 mb-2 font-bold&quot;&amp;gt;TOP HACKERS&amp;lt;/h3&amp;gt;';
    
    scores.forEach((entry, index) =&amp;gt; {
        // Highlight top 3 with different colors if you want
        const colorClass = index &amp;lt; 3 ? 'text-yellow-400' : 'text-gray-300';
        
        list.innerHTML += `
            &amp;lt;div class=&quot;flex justify-between py-1 border-b border-gray-800&quot;&amp;gt;
                &amp;lt;span class=&quot;${colorClass}&quot;&amp;gt;#${index + 1} ${entry.username}&amp;lt;/span&amp;gt;
                &amp;lt;span class=&quot;text-cyan-300 font-mono&quot;&amp;gt;${entry.score}&amp;lt;/span&amp;gt;
            &amp;lt;/div&amp;gt;`;
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;bonus-realtime-&quot;&gt;Bonus: Realtime ⚡&lt;/h4&gt;
&lt;p&gt;Do you want the ranking to update on &lt;strong&gt;everyone's&lt;/strong&gt; screen if someone breaks the record at that precise moment? Supabase makes this trivial with its subscriptions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Subscribe to changes in the 'snake_scores' table
supabase
  .channel('public:snake_scores')
  .on('postgres_changes', 
      { event: 'INSERT', schema: 'public', table: 'snake_scores' }, 
      (payload) =&amp;gt; {
        console.log('New record detected on the network!', payload);
        fetchLeaderboard(); // Automatically refresh the list for everyone
      }
  )
  .subscribe();
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-final-result&quot;&gt;The Final Result&lt;/h3&gt;
&lt;p&gt;By putting it all together, you get a fluid and highly competitive user experience:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The user plays frantically dodging neon &quot;Firewalls&quot;.&lt;/li&gt;
&lt;li&gt;Upon crashing, the game stops with a visual &quot;glitch&quot; effect.&lt;/li&gt;
&lt;li&gt;They enter their name (e.g., &quot;Neo&quot;).&lt;/li&gt;
&lt;li&gt;Upon clicking send, &lt;strong&gt;in milliseconds&lt;/strong&gt;, their name appears on the leaderboard.&lt;/li&gt;
&lt;li&gt;If their friend is playing on another mobile at the same time, they will see &quot;Neo's&quot; name appear on their screen without reloading the page.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And once created... all that remains is to enjoy and play &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/games/snake/&quot;&gt;Datalaria Snake Game&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;We have moved from a simple static canvas to a &lt;strong&gt;Fullstack&lt;/strong&gt; application in real-time without touching a traditional backend server (Node.js, PHP, Python) or managing infrastructure. The combination of creativity in game development with the power of BaaS tools like Supabase opens a world of possibilities for frontend developers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next step?&lt;/strong&gt; I invite you to clone the project and improve it: add GitHub authentication to avoid duplicate names, create snake skins that unlock with high scores, or implement a multiplayer mode using Supabase presence channels.&lt;/p&gt;
&lt;p&gt;The code is yours! &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Did you like this tutorial? Share it on your networks with your High Scores and don't forget to check the official Supabase documentation to dive deeper.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/12990/from-zero-hero-create-cyberpunk-snake-game-with-real-time-ranking-using-supabase-vanilla</guid>
<pubDate>Tue, 17 Mar 2026 06:55:03 +0000</pubDate>
</item>
<item>
<title>I spent a year chasing AI hype and buying courses. Here is why I went back to Vue.</title>
<link>https://coderlegion.com/10722/i-spent-a-year-chasing-ai-hype-and-buying-courses-here-is-why-i-went-back-to-vue</link>
<description>&lt;p&gt;I’ll be honest: I got lost.&lt;/p&gt;
&lt;p&gt;For the past year, I abandoned my comfort zone. As a veteran Vue 2 developer, I felt the &quot;Fear Of Missing Out&quot; (FOMO) hitting me hard. Everywhere I looked, people were shouting: &lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&quot;AI will replace you!&quot;&lt;/em&gt; &amp;gt; &lt;em&gt;&quot;Learn LLMs or become obsolete!&quot;&lt;/em&gt; &amp;gt; &lt;em&gt;&quot;Make passive income with AI-generated videos!&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have two kids to feed. The anxiety was real.&lt;/p&gt;
&lt;p&gt;So, I did what many terrified developers did. I stopped writing code and started chasing the wind. I bought the expensive courses on &quot;AI App Development&quot;. I tried to become a &quot;Content Creator&quot; using AI tools to churn out soulless videos. I tried to force myself to love the React ecosystem just because &quot;it has more AI libraries.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I became a &quot;Leek&quot; (a term we use in China for people who get harvested by hype).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After a year of burning cash and time, I looked at my portfolio. I had zero real users, a bunch of &quot;Hello World&quot; AI demos, and a lingering feeling of emptiness. The AI tools improved my efficiency, sure, but they didn't solve the core problem: I wasn't building anything valuable.&lt;/p&gt;
&lt;h3 id=&quot;the-return-to-reality&quot;&gt;The Return to Reality&lt;/h3&gt;
&lt;p&gt;I had a reality check one late night. I realized that while everyone is trying to build the next ChatGPT wrapper, regular people still struggle with basic things.&lt;/p&gt;
&lt;p&gt;I create content, and I work with images daily. I realized that simple tasks like bulk resizing, converting formats, or compressing images without uploading them to a shady server (privacy matters!) are still annoying. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Photoshop is too expensive and heavy for quick tasks.&lt;/li&gt;
&lt;li&gt;Most online tools are ad-ridden nightmares that steal your data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, I made a decision. &lt;strong&gt;Stop chasing the hype. Start shipping utility.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;from-vue-2-to-nuxt-4&quot;&gt;From Vue 2 to Nuxt 4&lt;/h3&gt;
&lt;p&gt;I went back to my roots, but I leveled up. I skipped the legacy Vue 2 patterns I was used to and dove straight into &lt;strong&gt;Vue 3 (Composition API)&lt;/strong&gt; and &lt;strong&gt;Nuxt&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It was refreshing. No more fighting with Python environments or prompt engineering. Just pure, clean JavaScript/TypeScript.&lt;/p&gt;
&lt;p&gt;I built &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://bulkpictools.com&quot;&gt;BulkPicTools&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It’s not an &quot;AI-powered, Blockchain-backed, Metaverse-ready&quot; platform. It’s a boring, useful tool.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⚡️ &lt;strong&gt;Client-side only&lt;/strong&gt;: It runs entirely in your browser using WebAssembly and modern JS APIs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Privacy-first&lt;/strong&gt;: Your photos never leave your device. No server uploads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solves a real problem&lt;/strong&gt;: Bulk processing for creators who value their time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-i-m-sharing-this&quot;&gt;Why I'm sharing this&lt;/h3&gt;
&lt;p&gt;I’m writing this for any other developer who feels inadequate because they aren't an &quot;AI Expert&quot; yet.&lt;/p&gt;
&lt;p&gt;You don't need to ride every wave. Your engineering skills—whether it's Vue, Rails, or PHP—are still valuable if you use them to solve real problems.&lt;/p&gt;
&lt;p&gt;I’m still learning the ropes with the latest Nuxt features, and the journey from &quot;Corporate Employee&quot; to &quot;Indie Hacker&quot; is terrifying. But at least now, I'm building something real again.&lt;/p&gt;
&lt;p&gt;If you have feedback on the tool or the Nuxt implementation, I’d love to hear it! &lt;/p&gt;
&lt;p&gt;Thanks for reading.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10722/i-spent-a-year-chasing-ai-hype-and-buying-courses-here-is-why-i-went-back-to-vue</guid>
<pubDate>Thu, 29 Jan 2026 23:35:34 +0000</pubDate>
</item>
<item>
<title>Tailwind CSS: Build Modern UIs Faster Without Writing Custom CSS</title>
<link>https://coderlegion.com/9983/tailwind-css-build-modern-uis-faster-without-writing-custom-css</link>
<description>&lt;p&gt;Tailwind CSS is a utility-first CSS framework that completely changes the way developers build user interfaces.&lt;/p&gt;
&lt;p&gt;Instead of writing long custom CSS files, Tailwind allows you to style elements directly in your HTML or JSX using pre-defined utility classes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Why Developers Love Tailwind CSS:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;⚡ Super fast development – no context switching&lt;/p&gt;
&lt;p&gt; Consistent design system – spacing, colors &amp;amp; typography stay aligned&lt;/p&gt;
&lt;p&gt; Highly customizable – extend themes easily&lt;/p&gt;
&lt;p&gt; Responsive by default – mobile-first utilities&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;♻ Clean &amp;amp; scalable UI – perfect for React, Next.js &amp;amp; Vue&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Whether you're building a startup landing page, dashboard, or full-scale SaaS product, Tailwind CSS helps you move faster without sacrificing design quality.&lt;/p&gt;
&lt;p&gt; Once you try Tailwind, going back to traditional CSS feels slow.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9983/tailwind-css-build-modern-uis-faster-without-writing-custom-css</guid>
<pubDate>Mon, 19 Jan 2026 07:58:07 +0000</pubDate>
</item>
<item>
<title>Tailwind’s hidden trade-off</title>
<link>https://coderlegion.com/9501/tailwinds-hidden-trade-off</link>
<description>&lt;p&gt;If you hang around modern frontend discourse (or pretty much any React codebase), Tailwind feels like the default.&lt;/p&gt;
&lt;p&gt;It’s fast. It’s polished. It has great docs. It makes UIs look decent by accident.&lt;/p&gt;
&lt;p&gt;And yet… I don’t use it.&lt;/p&gt;
&lt;p&gt;This post is my attempt to explain that without turning it into a holy war. I’m not here to dunk on Tailwind, and I’m definitely not here to cosplay as the last defender of “real CSS”. Tailwind is an impressive piece of work.&lt;/p&gt;
&lt;p&gt;But for the way I build things, it’s not my tool.&lt;/p&gt;
&lt;h2 id=&quot;tailwind-s-actual-superpower&quot;&gt;Tailwind’s actual superpower&lt;/h2&gt;
&lt;p&gt;Most arguments around Tailwind get stuck at the surface:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“Class soup is ugly”&lt;/li&gt;
&lt;li&gt;“It’s basically inline styles”&lt;/li&gt;
&lt;li&gt;“No, it’s faster”&lt;/li&gt;
&lt;li&gt;“No, it destroys semantics”&lt;/li&gt;
&lt;li&gt;“No, BEM is worse”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That debate is endless.&lt;/p&gt;
&lt;p&gt;The real Tailwind pitch is simpler: constraints + speed.&lt;/p&gt;
&lt;p&gt;Tailwind turns styling into selecting from a predefined design system (spacing scale, font scale, colors, shadows, radii). That’s why it feels so productive. You’re not inventing a new margin value every 5 minutes , you’re picking from a menu. Tailwind’s own docs explicitly frame it as “designing with constraints”, and pair that with variants for hover, focus, and responsive behaviour.&lt;/p&gt;
&lt;p&gt;When Tailwind works, it feels like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have a component in your head&lt;/li&gt;
&lt;li&gt;You type the HTML&lt;/li&gt;
&lt;li&gt;You sprinkle utilities until it looks right&lt;/li&gt;
&lt;li&gt;You move on with your life&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No naming. No context switching between files. No “where is this class defined”. No cascade surprises.&lt;/p&gt;
&lt;p&gt;And if you’re working in a team (or inheriting a codebase), the consistency is a big deal. You can open any component and recognise the spacing and typography patterns instantly, because everyone is pulling from the same scale.&lt;/p&gt;
&lt;p&gt;That’s a real benefit.&lt;/p&gt;
&lt;h2 id=&quot;-isn-t-it-just-inline-styles-not-really-&quot;&gt;“Isn’t it just inline styles?” Not really.&lt;/h2&gt;
&lt;p&gt;This is worth addressing because it’s the most common misunderstanding.&lt;/p&gt;
&lt;p&gt;Inline styles are limited. No pseudo-classes, no pseudo-elements, no media queries, no selector relationships, no tooling. Utility classes live in real stylesheets, so they can do everything CSS can do (including complex selectors, responsive behaviour, caching, processing, etc.).&lt;/p&gt;
&lt;p&gt;So no, Tailwind is not style=&quot;&quot;.&lt;/p&gt;
&lt;p&gt;It’s more like: “write your CSS once, pre-slice it into Lego bricks, then build UIs by snapping bricks together.”&lt;/p&gt;
&lt;h2 id=&quot;the-moment-tailwind-clicks-and-why-people-love-it-&quot;&gt;The moment Tailwind clicks (and why people love it)&lt;/h2&gt;
&lt;p&gt;I’ve worked on Tailwind projects. I get the appeal.&lt;/p&gt;
&lt;p&gt;There are three moments where Tailwind feels undeniably good:&lt;/p&gt;
&lt;h3 id=&quot;1-you-stop-naming-things&quot;&gt;1) You stop naming things&lt;/h3&gt;
&lt;p&gt;Naming is hard. Naming CSS classes is its own little tax.&lt;/p&gt;
&lt;p&gt;Tailwind sidesteps that by making your “naming” be the composition itself. You don’t create .pricingCardFeatured and then hunt for it later. You just describe the styles right where they apply.&lt;/p&gt;
&lt;p&gt;Even if you disagree with the conclusion, the problem is real: CSS and HTML are coupled whether you like it or not.&lt;/p&gt;
&lt;h3 id=&quot;2-it-makes-boring-layout-work-fast&quot;&gt;2) It makes boring layout work fast&lt;/h3&gt;
&lt;p&gt;Spacing, flex, grid, alignment, typography: Tailwind is extremely optimized for the 80 percent case.&lt;/p&gt;
&lt;p&gt;It’s especially good at “dashboard UI” work: cards, nav bars, modals, forms, tables, list items, and all the slightly boring stuff every product needs.&lt;/p&gt;
&lt;h3 id=&quot;3-it-gives-you-a-default-design-system&quot;&gt;3) It gives you a default design system&lt;/h3&gt;
&lt;p&gt;This is the underrated part.&lt;/p&gt;
&lt;p&gt;People don’t just like Tailwind because it’s utilities. They like it because the defaults are tasteful and cohesive. You can move quickly without designing a spacing scale from scratch.&lt;/p&gt;
&lt;p&gt;That “design tokens out of the box” feeling is the gateway drug.&lt;/p&gt;
&lt;h2 id=&quot;the-main-trade-off-tailwind-makes&quot;&gt;The main trade-off Tailwind makes&lt;/h2&gt;
&lt;p&gt;Tailwind’s trade-off is not “long class attributes”.&lt;/p&gt;
&lt;p&gt;That’s annoying, but it’s manageable.&lt;/p&gt;
&lt;p&gt;The real trade-off is: you move a big chunk of your styling layer into your markup layer.&lt;/p&gt;
&lt;p&gt;Sometimes that’s exactly what you want.&lt;/p&gt;
&lt;p&gt;But it changes how the codebase feels.&lt;/p&gt;
&lt;p&gt;And that’s where I start to drift away from Tailwind, because for me HTML readability is not optional. It’s where I think about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;structure&lt;/li&gt;
&lt;li&gt;accessibility&lt;/li&gt;
&lt;li&gt;content hierarchy&lt;/li&gt;
&lt;li&gt;interactive states&lt;/li&gt;
&lt;li&gt;layout relationships&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When every element is carrying 15 to 40 styling decisions, the document stops reading like a document.&lt;/p&gt;
&lt;p&gt;Yes, components help. Yes, you can extract patterns. Yes, you can use @apply (we’ll talk about that). But the default mode is still “style lives in the markup”.&lt;/p&gt;
&lt;p&gt;Some people love that. I don’t.&lt;/p&gt;
&lt;h2 id=&quot;another-trade-off&quot;&gt;Another trade-off&lt;/h2&gt;
&lt;p&gt;There’s one more Tailwind trade-off that doesn’t show up:&lt;/p&gt;
&lt;p&gt;you’re not just adopting a set of utilities, you’re adopting a whole ecosystem.&lt;/p&gt;
&lt;p&gt;Tailwind works because it’s a tightly designed package: a Domain-Specific Language, a compiler pipeline, a config-driven design system, a documentation culture, and a bunch of editor tooling that makes it pleasant.&lt;/p&gt;
&lt;p&gt;That’s great… until it isn’t.&lt;/p&gt;
&lt;p&gt;This week, a GitHub discussion around Tailwind’s documentation (about adding an llms.txt endpoint for LLM-friendly docs) unexpectedly highlighted the fragility of that setup. In that thread, Tailwind’s creator described docs traffic being down significantly since early 2023, revenue being down sharply even while the framework remains popular, and a major reduction in engineering headcount. (c.f. &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tailwindlabs/tailwindcss.com/pull/2388&quot;&gt;https://github.com/tailwindlabs/tailwindcss.com/pull/2388&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2894660900537886832&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I’m not bringing this up to predict doom. Tailwind is widely used and not “going away tomorrow.”&lt;/p&gt;
&lt;p&gt;I’m bringing it up because it clarifies the risk profile of adopting a framework-level styling layer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If Tailwind’s development slows, you can’t just “swap it out”.&lt;/li&gt;
&lt;li&gt;Your markup is full of Tailwind’s vocabulary.&lt;/li&gt;
&lt;li&gt;And if you leaned into @apply, even your CSS is now Tailwind-dependent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the exit cost isn’t “rewrite some styles”. It’s rewrite markup, retrain habits, rebuild constraints, and reestablish a design system.&lt;/p&gt;
&lt;p&gt;With native CSS, you can still make bad architecture choices , but at least you’re building on the platform’s most stable contract. If tomorrow you change approaches, your code doesn’t stop compiling because a tooling layer changed shape.&lt;/p&gt;
&lt;h2 id=&quot;the-practical-pain-points-from-actually-using-it-&quot;&gt;The practical pain points (from actually using it)&lt;/h2&gt;
&lt;p&gt;Here’s what bothered me in real projects, beyond vibes.&lt;/p&gt;
&lt;h3 id=&quot;1-content-gets-buried-&quot;&gt;1) “Content gets buried”&lt;/h3&gt;
&lt;p&gt;When class lists get long, the actual content becomes harder to scan. This is a real maintenance cost: editing copy, reviewing diffs, reading templates, doing quick refactors.&lt;/p&gt;
&lt;p&gt;If your workflow depends on “fold all classes” plugins, that’s a signal. Not necessarily a deal breaker, but a signal.&lt;/p&gt;
&lt;h3 id=&quot;2-you-start-needing-tooling-to-keep-it-readable&quot;&gt;2) You start needing tooling to keep it readable&lt;/h3&gt;
&lt;p&gt;Tailwind is amazing with good tooling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;class autocomplete&lt;/li&gt;
&lt;li&gt;sorting&lt;/li&gt;
&lt;li&gt;linting&lt;/li&gt;
&lt;li&gt;folding&lt;/li&gt;
&lt;li&gt;component extraction patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without that, it can feel like typing encrypted abbreviations.&lt;/p&gt;
&lt;p&gt;Again: not fatal, just friction.&lt;/p&gt;
&lt;h3 id=&quot;3-it-nudges-you-toward-breakpoint-heavy-design&quot;&gt;3) It nudges you toward breakpoint-heavy design&lt;/h3&gt;
&lt;p&gt;This one is subtle, and it depends on how you write CSS.&lt;/p&gt;
&lt;p&gt;A really common Tailwind style is: sm:, md:, lg: everywhere.&lt;/p&gt;
&lt;p&gt;That works, but it can also push you into “design at breakpoints” instead of building layouts that are fluid by default.&lt;/p&gt;
&lt;p&gt;To be clear: Tailwind can do fluid layouts. But the path of least resistance often becomes breakpoint choreography.&lt;/p&gt;
&lt;h3 id=&quot;4-the-escape-hatch-problem&quot;&gt;4) The “escape hatch” problem&lt;/h3&gt;
&lt;p&gt;Tailwind is great until you hit something that doesn’t map nicely to utilities.&lt;/p&gt;
&lt;p&gt;At that point you either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;write custom CSS anyway&lt;/li&gt;
&lt;li&gt;use arbitrary values&lt;/li&gt;
&lt;li&gt;create weird utility combos&lt;/li&gt;
&lt;li&gt;reach for plugins&lt;/li&gt;
&lt;li&gt;or accept something “close enough”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don’t even mind mixing. The web is messy. But if I’m going to write CSS anyway, I start asking: why am I also maintaining a second styling language?&lt;/p&gt;
&lt;h2 id=&quot;-but-you-can-just-use-apply-&quot;&gt;“But you can just use @apply”&lt;/h2&gt;
&lt;p&gt;A lot of Tailwind discussions end with:&lt;/p&gt;
&lt;p&gt;“Just extract components.” or “Just use @apply.”&lt;/p&gt;
&lt;p&gt;And you can. The issue is what you’re extracting into.&lt;/p&gt;
&lt;p&gt;@apply is not CSS. It’s Tailwind syntax that requires Tailwind in your build pipeline. If you remove Tailwind, those styles break.&lt;/p&gt;
&lt;p&gt;This is where Tailwind starts to feel less like “CSS helpers” and more like adopting a framework.&lt;/p&gt;
&lt;p&gt;That’s fine. Frameworks are normal. I just prefer when my styling layer stays as close as possible to the platform.&lt;/p&gt;
&lt;h2 id=&quot;my-honest-reason-i-like-css-as-a-language&quot;&gt;My honest reason: I like CSS as a language&lt;/h2&gt;
&lt;p&gt;This is the part that gets misread as elitism, so let me say it plainly:&lt;/p&gt;
&lt;p&gt;I don’t avoid Tailwind because I think Tailwind users are wrong.&lt;br&gt;
I avoid Tailwind because I enjoy writing CSS, and modern CSS has become extremely capable.&lt;/p&gt;
&lt;p&gt;I like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;custom properties as real design tokens&lt;/li&gt;
&lt;li&gt;cascade layers to keep priorities predictable&lt;/li&gt;
&lt;li&gt;container queries for component-level responsiveness&lt;/li&gt;
&lt;li&gt;newer selectors and pseudo-classes&lt;/li&gt;
&lt;li&gt;small utilities that I control&lt;/li&gt;
&lt;li&gt;debugging styles in DevTools as actual CSS rules, not a long list of class mappings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tailwind can coexist with modern CSS, but it’s still an abstraction layer. And when you already know the base language well, abstractions have to earn their place.&lt;/p&gt;
&lt;h2 id=&quot;the-part-people-really-want-okay-what-do-you-do-instead-&quot;&gt;The part people really want: “Okay, what do you do instead?”&lt;/h2&gt;
&lt;p&gt;I try to steal Tailwind’s best idea (constraints) without adopting Tailwind’s whole approach.&lt;/p&gt;
&lt;p&gt;Here’s the pattern:&lt;/p&gt;
&lt;h3 id=&quot;1-design-tokens-in-css-not-js&quot;&gt;1) Design tokens in CSS, not JS&lt;/h3&gt;
&lt;p&gt;Instead of a Tailwind config defining spacing and colors, I define tokens using custom properties:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:root {
  /* spacing scale */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-6: 1.5rem;
  --space-8: 2rem;

  /* colors */
  --bg: #f6f0e8;
  --text: #111;
  --accent: #1f6b55;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gets me the “designing with constraints” benefit, but it stays native.&lt;/p&gt;
&lt;h3 id=&quot;2-a-tiny-set-of-utilities-that-i-actually-use&quot;&gt;2) A tiny set of utilities that I actually use&lt;/h3&gt;
&lt;p&gt;I’m not against utilities. I’m against importing 10,000 of them by default.&lt;/p&gt;
&lt;p&gt;So I’ll define a few:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.stack {
  display: grid;
  gap: var(--space-4);
}

.card {
  border: 2px solid var(--text);
  border-radius: 12px;
  padding: var(--space-6);
  background: var(--bg);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now my HTML is readable again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;section class=&quot;stack&quot;&amp;gt;
  &amp;lt;article class=&quot;card&quot;&amp;gt;
    &amp;lt;h2&amp;gt;Title&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;Text&amp;lt;/p&amp;gt;
  &amp;lt;/article&amp;gt;
  ....
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-use-css-for-the-relationships-tailwind-struggles-with&quot;&gt;3) Use CSS for the relationships Tailwind struggles with&lt;/h3&gt;
&lt;p&gt;Tailwind is great at styling an element. CSS is great at styling relationships.&lt;/p&gt;
&lt;p&gt;Parent state, sibling relationships, component structure, “if this exists then style that”, fluid layouts, container-driven behaviour.&lt;/p&gt;
&lt;p&gt;This is why I keep coming back to CSS. The language keeps getting better at exactly the kinds of things that used to be Tailwind’s sweet spot.&lt;/p&gt;
&lt;p&gt;When Tailwind absolutely makes sense&lt;br&gt;
If you read this as “Tailwind bad”, you missed the point.&lt;/p&gt;
&lt;p&gt;Tailwind is a great choice when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you have a product team and need consistent UI fast&lt;/li&gt;
&lt;li&gt;you want onboarding to be easy (many devs know Tailwind now)&lt;/li&gt;
&lt;li&gt;you want a default design system with strong guardrails&lt;/li&gt;
&lt;li&gt;you build mostly inside component frameworks where markup readability matters less than component boundaries&lt;/li&gt;
&lt;li&gt;you don’t want to spend time building and maintaining a CSS architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It can also be a solid bridge for devs who feel lost in CSS, because its docs + constraints teach styling patterns indirectly.&lt;/p&gt;
&lt;p&gt;Why I still don’t use it&lt;br&gt;
If I had to summarise my position in one sentence:&lt;br&gt;
I want Tailwind’s constraints, but I want them in native CSS, with readable HTML, and without adopting a second styling Domain-Specific Language.&lt;/p&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;Tailwind solves real problems. It just solves them in a way that makes my code feel less like the web platform and more like a framework language.&lt;/p&gt;
&lt;p&gt;If that trade-off is worth it for you, use it. Seriously.&lt;/p&gt;
&lt;p&gt;If you’re on the fence, my suggestion is boring but honest: try both approaches on a real project, then pick the one that makes you move faster without hating your codebase three months later.&lt;/p&gt;
&lt;h2 id=&quot;support-my-work&quot;&gt;Support My Work&lt;/h2&gt;
&lt;p&gt;Feel free to leave your comments or questions.&lt;br&gt;
If you found this article helpful, please share it with your peers.&lt;/p&gt;
&lt;p&gt;If you want more content from me, you can go &lt;a rel=&quot;nofollow&quot; href=&quot;https://theosoti.com&quot;&gt;directly on my blog&lt;/a&gt;.&lt;br&gt;
I also created a &lt;a rel=&quot;nofollow&quot; href=&quot;https://theosoti.com/email-course&quot;&gt;7-day email course&lt;/a&gt; to learn more about modern CSS.&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9501/tailwinds-hidden-trade-off</guid>
<pubDate>Sat, 10 Jan 2026 12:23:14 +0000</pubDate>
</item>
<item>
<title>Diagonal Background Pattern with TailwindCSS</title>
<link>https://coderlegion.com/6654/diagonal-background-pattern-with-tailwindcss</link>
<description>&lt;p&gt;Background patterns are a recent trend in web design, and most can be replicated with only CSS. In this article, I will teach you how to replicate the diagonal background pattern on the Tailwind CSS website using only Tailwind CSS.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7992757022944960949&quot; alt=&quot;Tailwindcss website&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-we-will-be-building&quot;&gt;What we will be building&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16191346426907319301&quot; alt=&quot;profile card&quot;&gt;&lt;/p&gt;
&lt;p&gt;We will build this simple card component using the background pattern on the card header. The card is a custom shadcn card with extended styles. I will only focus on the background pattern on the card header, and not the card layout or content.&lt;/p&gt;
&lt;h4 id=&quot;tailwind-css&quot;&gt;Tailwind CSS&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;globals.css

:root {
  --border: oklch(0.9219 0 0);
}

.diagonal-bg {
  @apply bg-[image:repeating-linear-gradient(315deg,var(--border)_0,var(--border)_1px,transparent_1px,transparent_50%) bg-[size:8px_8px];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;card-component&quot;&gt;Card Component&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;/** Card.tsx */

function Card() {

  return (
    &amp;lt;Card&amp;gt;
      &amp;lt;CardHeader className=&quot;py-4 diagonal-bg&quot;&amp;gt;
        &amp;lt;CardTitle&amp;gt;User Details&amp;lt;/CardTitle&amp;gt;
      &amp;lt;/CardHeader&amp;gt;
      &amp;lt;CardContent&amp;gt;
       {/** card content */}
      &amp;lt;/CardContent&amp;gt;
    &amp;lt;/Card&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;explanation&quot;&gt;Explanation&lt;/h4&gt;
&lt;h5 id=&quot;gradient&quot;&gt;Gradient&lt;/h5&gt;
&lt;p&gt;315° points top-left. Using repeating-linear-gradient(315deg, …) lays stripes along that diagonal.&lt;br&gt;
The pattern then repeats infinitely along the gradient axis for a subtle diagonal texture.&lt;/p&gt;
&lt;h5 id=&quot;colored-stripe-var---border-&quot;&gt;Colored stripe (var(--border))&lt;/h5&gt;
&lt;p&gt;In this example, I am using a &lt;code&gt;--border&lt;/code&gt; variable.&lt;br&gt;
var(--border) 0 starts the colored stop at the very beginning of the pattern.&lt;br&gt;
var(--border) 1px holds that color until 1px, creating a crisp 1px line.&lt;br&gt;
var(--border) is a CSS variable for colour.&lt;/p&gt;
&lt;h5 id=&quot;transparent-gap&quot;&gt;Transparent gap&lt;/h5&gt;
&lt;p&gt;transparent 1px turns the colour off right after the 1px line.&lt;br&gt;
transparent 50% keeps it transparent until halfway through the repeat segment, so each unit is “1px line + clear space.”&lt;/p&gt;
&lt;h5 id=&quot;tile-size-amp-repeat&quot;&gt;Tile size &amp;amp; repeat&lt;/h5&gt;
&lt;p&gt;background-size: 8px 8px sets each repeating tile to 8×8px.&lt;br&gt;
With a 1px line in each tile, the repeats intersect to form a fine diagonal grid/checker texture.&lt;/p&gt;
&lt;h4 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;One thing to note is that more often than not, background patterns in Tailwind CSS can be very verbose. I encourage you to move the CSS code to an &lt;code&gt;@apply&lt;/code&gt; Tailwind block in an external file, or, as we will see in other examples in this series, you can keep the code within the component, depending on your preferred React pattern.&lt;/p&gt;
&lt;p&gt;What other background pattern do you want me to cover?&lt;/p&gt;
</description>
<category>Backgrounds</category>
<guid isPermaLink="true">https://coderlegion.com/6654/diagonal-background-pattern-with-tailwindcss</guid>
<pubDate>Mon, 20 Oct 2025 09:08:35 +0000</pubDate>
</item>
<item>
<title>Space vs. Gap — Tailwind CSS Explained</title>
<link>https://coderlegion.com/6038/space-vs-gap-tailwind-css-explained</link>
<description>&lt;p&gt;&lt;img src=&quot;http://&quot; alt=&quot;&quot;&gt;&lt;br&gt;
Tailwind is an awesome resource when styling web applications. It’s fast, lightweight, and easy to use once you get used to the class-first syntax. But what do you use when you need to add some spacing between items — &lt;strong&gt;gap&lt;/strong&gt; or &lt;strong&gt;space&lt;/strong&gt;?&lt;br&gt;
Let’s get into it.&lt;/p&gt;
&lt;h4 id=&quot;what-do-i-use-&quot;&gt;&lt;strong&gt;What do I use?&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;To tackle this question, let’s create a scenario. We’ll be using three boxes represented by &lt;code&gt;divs&lt;/code&gt; in an HTML document.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;section&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-300&quot;&amp;gt;Box 1&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-400&quot;&amp;gt;Box 2&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-500&quot;&amp;gt;Box 3&amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, there are three boxes we need to add some spacing to. Normally, the way to go about this would be to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use a flex layout on the &lt;code&gt;section&lt;/code&gt; parent element and add a &lt;code&gt;gap&lt;/code&gt; value to create space.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a margin on each box.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But what happens when there’s no need to use a flex layout or margin? What do we use?&lt;/p&gt;
&lt;p&gt;That’s where the space Tailwind class comes in. Picture it as what &lt;code&gt;gap&lt;/code&gt; is capable of in a grid or flex layout, but without the flex/grid. So let's see a side-by-side comparison.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;!-- with tailwind space --&amp;gt;
&amp;lt;section class=&quot;space-5&quot;&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-300&quot;&amp;gt;Box 1&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-400&quot;&amp;gt;Box 2&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-500&quot;&amp;gt;Box 3&amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;!-- with tailwind gap --&amp;gt;
&amp;lt;section class=&quot;flex gap-5&quot;&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-300&quot;&amp;gt;Box 1&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-400&quot;&amp;gt;Box 2&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;w-48 h-48 bg-slate-500&quot;&amp;gt;Box 3&amp;lt;/div&amp;gt;
&amp;lt;/section&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the first code block, using Tailwind &lt;code&gt;space&lt;/code&gt;creates spacing between the items in the section element. The main difference is that when using &lt;code&gt;gap&lt;/code&gt;, a flex or grid layout is required, whereas when using &lt;code&gt;space&lt;/code&gt;, it's not.&lt;/p&gt;
&lt;p&gt;This is something I discovered earlier when exploring more of what Tailwind CSS can do in my projects.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Hopefully, this article has helped to simplify the &lt;strong&gt;gap vs. space&lt;/strong&gt; concept in Tailwind. For more detailed documentation, please visit the official Tailwind docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://tailwindcss.com/docs/gap&quot;&gt;&lt;em&gt;Tailwind gap&lt;/em&gt;&lt;/a&gt; &lt;em&gt;vs.&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://tailwindcss.com/docs/margin#adding-space-between-children&quot;&gt;&lt;em&gt;Tailwind space&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading, and have a great day.&lt;br&gt;
Ciao &lt;/p&gt;
</description>
<category>Web Dev</category>
<guid isPermaLink="true">https://coderlegion.com/6038/space-vs-gap-tailwind-css-explained</guid>
<pubDate>Sat, 04 Oct 2025 07:32:09 +0000</pubDate>
</item>
<item>
<title>Why My First React App Was Ugly (and What I Did to Fix It)</title>
<link>https://coderlegion.com/3981/why-my-first-react-app-was-ugly-and-what-i-did-to-fix-it</link>
<description>&lt;p&gt;&lt;strong&gt;W&lt;/strong&gt;hen I built my first React app, I didn’t expect to be embarrassed by it.&lt;/p&gt;
&lt;p&gt;I had the logic working. Components rendered. Data flowed. But when I stepped back and looked at the whole thing… it just felt off. No one had to tell me I knew it was ugly.&lt;/p&gt;
&lt;p&gt;Looking back now, I realize that this “ugly” version was one of the best things that could have happened. It forced me to think deeply about design, user flow, and what I was really trying to build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; The App: A Minimal Portfolio&lt;/strong&gt;&lt;br&gt;
You can check it out here: modello-oro-jb.netlify.app&lt;/p&gt;
&lt;p&gt;It’s still minimalistic and could definitely use more polish. But I’m proud of it not because it’s perfect, but because it shows progress. Every section is a reminder of the small (but crucial) lessons I picked up while learning React and frontend design at the same time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Where I Went Wrong&lt;/strong&gt;&lt;br&gt;
Here are a few things I didn’t get right at first:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Design Without Spacing Logic&lt;/strong&gt;&lt;br&gt;
I underestimated the power of padding and margins. Everything felt crammed or too spaced out. I learned quickly that design isn’t just about color and fonts it’s also about breathing room and visual hierarchy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Responsiveness? What’s That?&lt;/strong&gt;&lt;br&gt;
I focused so much on getting it to look good on desktop that I forgot people would visit on mobile too. The first time I opened it on my phone, I was horrified. Buttons overflowed, text broke, layouts collapsed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. State Management Overkill&lt;/strong&gt;&lt;br&gt;
In one section, I used useState and useEffect when a simple conditional would’ve done the job. I was so eager to use React tools that I forgot about clarity and simplicity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Animations Were… Missing&lt;/strong&gt;&lt;br&gt;
I didn’t think much about transitions or feedback animations. So every interaction felt a bit lifeless. Later on, I discovered how even subtle motion can change the user’s perception of quality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; What I Fixed (and Still Improving)&lt;/strong&gt;&lt;br&gt;
Switched to a component-first approach to keep code dry and reusable&lt;/p&gt;
&lt;p&gt;Adopted Tailwind CSS for more consistent styling and responsive utility classes&lt;/p&gt;
&lt;p&gt;Focused more on typography and spacing to guide the eye better&lt;/p&gt;
&lt;p&gt;Added some subtle transitions and hover effects for interactivity&lt;/p&gt;
&lt;p&gt;Started thinking of mobile-first instead of retrofitting for small screens later&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; What I Learned&lt;/strong&gt;&lt;br&gt;
Here’s what I’d tell my past self (or anyone just starting out with React and frontend):&lt;/p&gt;
&lt;p&gt;Ugly is part of the process and it’s a great teacher.&lt;/p&gt;
&lt;p&gt;Don’t obsess over frameworks; obsess over the user experience.&lt;/p&gt;
&lt;p&gt;Write your code like someone else has to read it because one day, that someone will be you.&lt;/p&gt;
&lt;p&gt;Design and development aren’t separate they go hand in hand, especially for solo devs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Final Thoughts&lt;/strong&gt;&lt;br&gt;
The app still isn’t perfect, but I see it as a snapshot of my learning curve and that’s something to be proud of.&lt;/p&gt;
&lt;p&gt;If you're building your first project and it feels a bit ugly, don’t give up. Instead, ask why, fix what you can, and learn from the rest.&lt;/p&gt;
&lt;p&gt;I’d love to hear from others who’ve gone through the same phase drop your link or story below &lt;/p&gt;
</description>
<category>Java</category>
<guid isPermaLink="true">https://coderlegion.com/3981/why-my-first-react-app-was-ugly-and-what-i-did-to-fix-it</guid>
<pubDate>Mon, 14 Jul 2025 11:01:12 +0000</pubDate>
</item>
<item>
<title>LockedIn: Visualize Your Life in Weeks and Plan for Success</title>
<link>https://coderlegion.com/470/lockedin-visualize-your-life-in-weeks-and-plan-for-success</link>
<description>&lt;p&gt;It was just another lazy evening. I was mindlessly scrolling through Instagram,&lt;br&gt;
And then, one reel stopped me in my tracks.&lt;/p&gt;
&lt;p&gt;It was a sarcastic but brutally honest saying,&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Yes, you're going to become a billionaire, trillionaire... by scrolling all day. Someone out there is keeping their head down, working tirelessly. And that's definitely not you.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I seriously Uninstalled Instagram and started thinking this scrolling would not reach me where I wanted and even it would never make me reach if I continued doing so.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rewiring My Mindset&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Uninstalling Instagram wasn’t enough. I still had to figure out how to keep myself focused, disciplined, and working towards my bigger vision.&lt;/p&gt;
&lt;p&gt;It wasn't just about getting rid of distractions; it was about setting my mind to operate in emergency mode—to treat time like the precious commodity it is.&lt;/p&gt;
&lt;p&gt;But how?&lt;/p&gt;
&lt;p&gt;How to make my mind in Emergency mode,&lt;/p&gt;
&lt;p&gt;People around the globe are developing tools to improve productivity, focus, and motivation.&lt;/p&gt;
&lt;p&gt;That’s when something from the web world hit me—I realized I could build something of my own…&lt;/p&gt;
&lt;p&gt;A tool that could remind me every single day of how important time is.&lt;/p&gt;
&lt;p&gt;What is LockedIn?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=3612261741975125555&quot; alt=&quot;https://locked--in.vercel.app/&quot;&gt;&lt;/p&gt;
&lt;p&gt;LockedIn is more than just a productivity tool; it's a visual representation of your life in weeks.&lt;/p&gt;
&lt;p&gt;It shows you, in the most straightforward way possible, how much time you’ve spent and how much time you have left to achieve your goals.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Countdown Grid:&lt;/em&gt; You enter your birthdate and name, and LockedIn generates a grid where each block represents one week of your life.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Progress Bar&lt;/em&gt;: LockedIn includes a progress bar that tracks how much of your life you've lived and how much remains.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10470790832183511500&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Turning Vision Into Action: The To-Do List&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While LockedIn helps you visualize your life at a macro level, I knew that wouldn’t be enough.&lt;/p&gt;
&lt;p&gt;The next step was to bridge the gap between the big picture and daily actions. That’s where the To-Do List feature came in.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=4360279520799753705&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Check off accomplishments as you make progress, feeling that rush of dopamine with each task you complete.&lt;/p&gt;
&lt;p&gt;With this, Plan your daily tasks and make sure they align with your long-term goals.&lt;/p&gt;
&lt;p&gt;Final Thoughts: Lock in Your Time, Build Your Legacy&lt;br&gt;
Initially, LockedIn was something I built just for myself. I wanted a tool that would help me stay in “emergency mode” and never waste another week.&lt;/p&gt;
&lt;p&gt;But then something interesting happened—it was a Tuesday, and I remembered Peerlist - a platform where people submit their projects to gain feedback and connect with like-minded peers.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I figured, “Why not?” I decided to share LockedIn with the world. I submitted my project, expecting maybe a few eyeballs, a couple of comments, and that would be it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But the response blew me away.&lt;/p&gt;
&lt;p&gt;Suddenly, LockedIn was getting attention. People were commenting, upvoting, and praising the idea.&lt;/p&gt;
&lt;p&gt;Why I Need Your Support on Peerlist&lt;br&gt;
Now, here I am, sharing this with you because today is the last day to upvote LockedIn on Peerlist. Your support means a lot.&lt;/p&gt;
&lt;p&gt;If you've ever felt like time is slipping through your fingers, I invite you to upvote LockedIn on Peerlist and join this movement of people who are taking control of their lives, one week at a time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Final Thoughts:&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;Lock in Your Time, Build Your Legacy&lt;br&gt;
The day I uninstalled Instagram was the day I stopped letting my time be controlled by algorithms. I decided to lock in and take charge of how I spend my days.&lt;/p&gt;
&lt;p&gt;LockedIn has transformed the way I approach time, turning it from an abstract concept into something I can see, plan, and act on.&lt;/p&gt;
&lt;p&gt;And now, I want to share this with you.&lt;/p&gt;
&lt;p&gt;Join me on this journey. Let’s make every week count.&lt;/p&gt;
&lt;p&gt;Upvote LockedIn on &lt;a rel=&quot;nofollow&quot; href=&quot;https://peerlist.io/i_aryansharma17/project/lockedin&quot;&gt;Peerlist&lt;/a&gt; before time runs out!&lt;/p&gt;
&lt;p&gt;I hope you loved this writing, I have used my creativity and AI to write this article.&lt;/p&gt;
&lt;p&gt;Do not forget to Upvote. for the time being, visit my other Blogs.&lt;/p&gt;
&lt;p&gt;Thanks for reading.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/470/lockedin-visualize-your-life-in-weeks-and-plan-for-success</guid>
<pubDate>Sun, 03 Nov 2024 21:30:23 +0000</pubDate>
</item>
</channel>
</rss>