From Tailwind to Vanilla CSS: Structuring Styles Without a Framework

After years of relying on Tailwind CSS for rapid prototyping and small sites, I recently migrated a couple of projects back to semantic HTML and vanilla CSS. The process was surprisingly rewarding—it taught me how much structure Tailwind had quietly provided, and how I could replicate those systems without the utility-first overhead. Below, I answer common questions about this transition, covering the key lessons I learned about CSS architecture, including resets, component organization, color palettes, font scales, and responsive design.

Why did you decide to move away from Tailwind CSS?

I started using Tailwind eight years ago because I had no idea how to structure CSS—my code was chaotic, and Tailwind gave me a consistent system. Over time, however, I became curious about writing cleaner, more semantic HTML with vanilla CSS. The decision wasn’t about disliking Tailwind; it was about wanting deeper control over my stylesheets and reducing build complexity for smaller projects. The migration felt fun and interesting, not painful, because Tailwind had already taught me many structural concepts—like resets, color palettes, and spacing scales—that I could replicate on my own.

From Tailwind to Vanilla CSS: Structuring Styles Without a Framework

What did Tailwind teach you about structuring CSS?

Tailwind inadvertently taught me several organizational principles. For example, every CSS codebase has multiple layers: resets, typography, color systems, layouts, and common components. Tailwind provides built-in systems for these: a reset stylesheet (Preflight), a predefined color palette, a font-size scale, and a spacing system. While using Tailwind, I absorbed these patterns subconsciously. When I moved to vanilla CSS, I realized I could imitate the systems I liked—like maintaining a consistent color palette and a base set of utility classes—while keeping the markup cleaner and more maintainable. In essence, Tailwind gave me a mental model for CSS structure that I could apply without the framework itself.

How did you handle the CSS reset?

I simply copied Tailwind's Preflight reset—about the first 200 lines from the Tailwind source. This reset includes fundamental rules like * { box-sizing: border-box; } and html { line-height: 1.5; }. I’ve grown so accustomed to these defaults that writing CSS without them would feel like a major adjustment. The reset also standardizes margins, padding, and list styles across browsers. By keeping this reset, I maintained the same baseline behavior I was used to, without needing Tailwind’s build tools.

How do you organize components in vanilla CSS?

I structure CSS by components, similar to how you’d write Vue or React components—even if the site has no JavaScript. Each component gets a unique class, and its CSS lives in its own file. The golden rule: CSS for one component never overrides another. This isolation means editing a button’s styles won’t break a card’s layout. About 80% of the CSS I change daily lives in these component files. For example, I have a .card component with its own set of rules for padding, background, and border-radius. This approach keeps the codebase predictable and scalable.

What systems do you use for colors and fonts?

I borrowed Tailwind’s approach: define a limited color palette as CSS custom properties and a font-size scale. For colors, I set variables like --color-primary: #3b82f6 and use them throughout components. For fonts, I create a scale (e.g., --text-sm: 0.875rem, --text-base: 1rem, --text-lg: 1.125rem) to maintain consistent typography. I also define utility classes for common sizes (like .text-sm) but keep them minimal—only for truly reusable one-off adjustments. The goal is to avoid a gigantic utility-first explosion while still having quick access to the palette and scale I need.

How do you manage spacing and responsive design?

Spacing I centralize into a scale using CSS custom properties, e.g., --space-1: 0.25rem, --space-2: 0.5rem, up to --space-8: 2rem. Components reference these variables instead of arbitrary values. For responsive design, I use a mix of container queries (where supported) and media queries, often defined within component files. I keep breakpoints as variables, like --bp-tablet: 768px. This approach mirrors Tailwind’s responsive prefixes but without the verbosity—I can write @media (min-width: var(--bp-tablet)) { ... } inside a component’s stylesheet.

What about utility classes and the build system?

I still use a small set of utility classes for truly atomic needs—like .hidden or .text-center—but I keep them in a single utilities.css file. They are generated manually, not via a JIT engine. For the build system, I switched from Tailwind’s CLI to a simpler PostCSS setup with Autoprefixer and a CSS minifier. The build step is now faster, and I have full control over what gets generated. I also use a CSS cascade layer (@layer reset, base, utilities, components) to enforce order without worrying about specificity wars.

Recommended

Discover More

Lexus Set to Debut Its First Three-Row Electric SUV: A Luxury Counterpart to Toyota's Highlander EVHow to Honor a Loved One and Sustain the Communities That Matter5 Key Updates on Netflix's Narnia Prequel: The Magician's Nephew Delayed to 2027Linux Misreports Intel Bartlett Lake CPU Frequency: A 7GHz PhantomKubernetes 1.36 Delivers Crash-Consistent Volume Group Snapshots for Production