After eight years with Tailwind, rebuilding sites with semantic HTML and vanilla CSS revealed how constraint-driven frameworks teach foundational skills while ultimately limiting creative growth—a journey worth taking for any frontend developer seeking true CSS fluency.
Eight years ago, Tailwind CSS felt like a lifeline. Faced with the prospect of unstructured CSS chaos, its utility-first approach offered immediate productivity—I could build functional sites without wrestling with specificity or cascading inheritance. The trade-off seemed worthwhile: surrender some design flexibility for rapid iteration and consistency. What I didn’t anticipate then was how deeply those constraints would shape my mental model of styling, creating dependencies I’d only recognize when attempting to work outside them.
My recent migration away from Tailwind wasn’t driven by frustration but by curiosity. Having used v2 for years due to its growing reliance on build systems (making the 2.8MB tailwind.min.css feel increasingly archaic), I found myself wanting to engage with CSS as a language rather than a set of predefined classes. The process revealed nine structural insights that transformed my approach:
First, I acknowledged Tailwind’s hidden curriculum. Its preflight reset—particularly universal box-sizing: border-box and baseline line-height—had become invisible scaffolding in my workflow. Copying those 200 lines into a dedicated reset.css file wasn’t just about replication; it was conscious recognition of what I’d internalized. This reset now lives as an explicit foundation, not magic.
Second, component organization proved revelatory. By treating UI elements as isolated concerns—each with a unique root class, contained styles, and dedicated file—I eliminated the anxiety of accidental overrides. Editing a .zine component’s hover state no longer risks breaking the site header. This mirrors framework-based thinking without requiring JavaScript, proving that encapsulation principles transcend specific technologies.
Third, I systematized what Tailwind abstracted. Defining CSS custom properties for colors (--pink: #fea0c2) and font scales (--size-lg: 1.125rem) created a predictable vocabulary. While more verbose than text-lg, this approach forced intentionality: choosing --size-xl requires considering the actual pixel value and its relationship to the baseline, deepening my understanding of typographic hierarchy.
Fourth, utilities like .sr-only found a home in a minimal utilities.css—but with stricter governance. Unlike Tailwind’s prolific utility classes, I reserve this file for truly cross-cutting concerns, treating additions as architectural decisions rather than conveniences.
Fifth, base styles became a deliberate experiment. Starting near-empty (section containers and link colors only), I’m migrating patterns upward only when they demonstrably appear across multiple components. This bottom-up approach prevents over-abstracting early—a pitfall I’d encountered when trying to anticipate needs.
Sixth, spacing philosophy shifted from reactive to systemic. Instead of padding/margin whack-a-mole, I now task layout parents with child spacing (e.g., section > *+* { margin-top: 1rem; }). Inspired by the ‘lobotomized owl’ selector, this establishes rhythm at the source rather than patching symptoms.
Seventh, responsive design evolved beyond breakpoint chasing. Replacing media queries with CSS Grid’s auto-fit and minmax() (grid-template-columns: repeat(auto-fit, minmax(min(100%, 400px), max-content));) created fluid layouts that adapt organically. Using grid-template-areas for complex arrangements revealed possibilities Tailwind’s class-based system obscures—proving that constraints can sometimes hinder rather than help.
Eighth, the build system question resolved pragmatically. While native CSS imports and nesting suffice for development, I use esbuild for production bundling—not because it’s required, but because its static binary and standards alignment feel trustworthy. This mirrors my broader goal: adopting tools only when they solve actual problems, not because they’re fashionable.
The migration succeeded because I carried forward Tailwind’s lessons while discarding its crutch. I now see utility classes as training wheels: invaluable for early productivity but ultimately limiting when seeking mastery. My sites aren’t just lighter (no more multi-megabyte CSS files); they’re more intentional. Every styling decision now requires engaging with the cascade, specificity, and inheritance—the very mechanics Tailwind abstracted away. This isn’t a rejection of utility-first CSS; it’s an acknowledgment that true fluency comes from understanding what those utilities implement beneath the surface. For developers at any stage, periodically rebuilding without frameworks isn’t about rejecting progress—it’s about ensuring your foundational skills keep pace with the tools you choose to use.
Comments
Please log in or register to join the discussion