From Tailwind to Vanilla CSS: Lessons in Structuring Stylesheets

After years of relying on Tailwind CSS for rapid prototyping and small sites, I recently decided to migrate a couple of projects to semantic HTML and vanilla CSS. To my surprise, the process was not only fun but also revealed how much Tailwind had taught me about CSS structure. Here's a Q&A breakdown of what I learned, including how I borrowed and adapted Tailwind's internal systems to create a maintainable, component-based stylesheet.

Why did you decide to move away from Tailwind?

Initially, Tailwind was a lifesaver because I had no clue how to organize my CSS. Its utility-first approach kept my code from descending into chaos. But over time, I realized that for larger or more semantically-focused sites, the HTML became cluttered with dozens of utility classes, making it harder to read and maintain. I also wanted more control over naming conventions and cascade behavior. The migration gave me a chance to apply the structural concepts I'd unconsciously absorbed from Tailwind—like color palettes, spacing scales, and reset styles—into a vanilla CSS setup that felt both familiar and cleaner.

From Tailwind to Vanilla CSS: Lessons in Structuring Stylesheets

What did Tailwind teach you about CSS structure?

Tailwind isn't just a utility library; it's a collection of well-thought-out systems. By using it for years, I internalized patterns for resets, color palettes, font scales, spacing, and responsive breakpoints. When I started reading modern CSS architecture articles, I realized I already knew what good structure looked like—I just needed to replicate it without the framework. For example, Tailwind's preflight reset taught me the importance of consistent box-sizing, line-height, and margin removal. Its color and font utilities showed me how to define design tokens as CSS custom properties. The biggest lesson: a maintainable CSS codebase is really just a set of small, predictable systems layered together.

How did you handle the CSS reset without Tailwind?

I simply copied Tailwind's preflight styles—the first 200 lines of their CSS—and adapted them slightly. This gave me the same baseline: * { box-sizing: border-box; }, html { line-height: 1.5; }, and removal of default margins. These small defaults had become second nature to me; I realized I would have struggled without them. Keeping that reset as a foundation meant I didn't have to relearn basic spacing and layout assumptions. It's a perfect example of how Tailwind's opinions became my own, and migrating just meant preserving that knowledge in vanilla form.

How do you organize CSS by components now?

I adopted a component-first approach inspired by Vue and React conventions. Each visual component—like a card, button, or navigation bar—gets a unique class name. All its styles live in a dedicated CSS file (e.g., card.css). The golden rule: no component's styles override another's. This prevents mysterious cascade bugs and makes it easy to find and edit styles. About 80% of my CSS now sits in these component files. For shared concerns like layout or typography, I use separate global files. This structure gives me the modularity of Tailwind's utility classes but with the readability of semantic CSS.

How do you handle colors and typography without Tailwind?

I defined design tokens using CSS custom properties. For colors: I created a palette similar to Tailwind's (e.g., --color-primary: #3b82f6, --color-gray-100: #f3f4f6, etc.). For typography, I set up a font-size scale using clamp() and custom properties for line heights and font families. These tokens are placed in a :root block and referenced throughout the component files. This keeps the design system explicit and easy to update. I also borrowed Tailwind's spacing scale (multiples of 4px) and made it available as --space-* variables.

Do you still use utility classes?

Yes, but sparingly. I created a small set of custom utility classes for truly one-off tweaks—like .text-center, .margin-bottom-sm, or .flex—rather than inline styles. The key is that these utilities don't conflict with component styles because they are deliberately generic and scoped at a low specificity. I also use the @layer directive (or cascade layer) to ensure utilities override components when needed, mimicking Tailwind's layering. This hybrid approach gives me the convenience of utilities without polluting the HTML with dozens of classes per element.

How did you structure responsive design without Tailwind's breakpoints?

I defined a set of custom media query aliases using custom properties and @custom-media (or just plain @media). For example: @custom-media --sm (min-width: 640px). These sit in a responsive.css file and are referenced in component files when needed. I kept the same breakpoints as Tailwind (sm, md, lg, xl) because I was used to them. Each component file includes its own responsive overrides, keeping all related styles together. This avoids the scattered breakpoints that often plague vanilla CSS projects.

Tags:

Recommended

Discover More

Harnessing Hamster Wheel Energy for Phone Charging: A DIY Guide10 Critical Facts About the Google Family Link Call Blocking BugGrafana Cloud Unleashes Custom Cloud Dashboards: Users Now Control AWS, Azure, and GCP ViewsWhy Lido Chose Chainlink CCIP: Security Principles Behind Cross-Chain Staking Expansion10 Exciting Revelations About Samsung’s July 22 Event: Galaxy Glasses, Watch 9, and Fold 8