So you have heard about the Tailwind CSS and want to incorporate into your new project. But those inline styles look so polluted - they look similar to the old "style="font-size: 12px; color: green; font-style: italics" stuff we are told to avoid. What is going on here? Are we going back??
The Problem with Plain CSS Classes
Before you run the other way, I think we have all encounter this problem maintaining a large codebase:
/* Developer A wrote this 2 years ago */
.card-variant-3 {
background-color: #f3f4f6;
padding: 1rem;
border-radius: 0.5rem;
}
After 2 years, the stylesheet is 5,000 lines long. And this class could be used anywhere in the codebase of millions of lines.
Developer B is too scared to modify .card-variant-3 because they don't know if it's used on some obscure marketing page... So they just append a new class to the bottom of the file.
.card-variant-3-updated {
background-color: #f3f4f6;
padding: 1.5rem; /* <--- Only changed the padding! */
border-radius: 0.5rem;
}
The Problem: Bundle sizes grow linearly with the app. You have to spend your time naming things (.main-content-inner, .card-body-flex) and jumping between HTML and CSS files.
What Tailwind Gives Us
Tailwind solves this problem by creating "utility classes" such as "bg-mint-500", "text-mint-500", and "border-mint-500" from CSS variables which one can use in the HTML like so
<h1 className="text-mint-500"> Introduction </h1>
This restructure gives us two advantages:
The Reduced CSS Bundle Size: Because you reuse bg-mint-500 or p-4 over and over, you stop writing new CSS lines. Whether you have 10 pages or 10,000 pages, your production CSS stays virtually the same size (often a fraction of traditional stylesheets).
Dramatically Lower Risk in Refactoring: Everything that makes this element look the way it does lives exclusively inside this component block. If you delete the component, the styles are deleted instantly alongside it. There is absolutely zero risk of breaking a sidebar on the other side of your application.
What about Readability??
The biggest reason this is okay is that you only have to look at it occasionally.
In modern web development, we rarely write raw, static HTML pages anymore. We use component-based architectures (React, Vue, Svelte, Blade, Astro, etc.).
When you style an elements block like this, you immediately encapsulate it inside a reusable component. E.g.,
<Card variant="premium">
Once that component is saved, the "ugly" implementation details disappear behind a clean, semantic custom tag. Your main layout code remains pristine, while the styling mess is safely quarantined inside a single file.
Conclusion
We traded pretty markup for bulletproof maintainability. We allowed our HTML to look messy in development so that our production architecture could remain perfectly clean, predictable, and isolated.
Next
I will discuss more about Tailwind CSS in real projects. Follow me for more such articles!
Please comment below - how do you manage your CSS and do you think utility classes is a good idea??
Top comments (11)
Thanks Cathy for the great article!
The most valuable point for me was the concept of style isolation within components - this changed my perspective on styling in large projects. Well explained!
Wishing you all the best
🌊🧊🗻
I used to dislike Tailwind for the same reason: it looked like we were putting styles back into markup.
But after working on larger component-based projects, I started seeing the trade-off differently. The real problem was not “ugly HTML”, it was CSS that nobody felt safe changing anymore.
Utility classes make the styling local, visible, and easier to delete with the component. That alone solves a huge maintenance problem.
I still think teams need discipline, though. Tailwind can become messy too if every component turns into a giant wall of classes. For me, the sweet spot is Tailwind for layout and spacing, design tokens for consistency, and reusable components for anything repeated often.
I agree! The utility classes still doesn’t give a centralised theme for all the components. So, things can be messy if not disciplined.
Thanks for sharing your experience about the increased in maintain ability 😀 it certainly is a relief from the old pure CSS way…
I have come to the conclusion that both
class=andstyle=have taken us down a poor path with regards to styling and CSS. We've been off the rails with frameworks for about 15 years or more now. HTML, CSS and JS are very capable these days and there really isn't a reason to wrap additional frameworks around them as they tend to replace one set of problems with another.NueJS planted the seed in me (unfortunately I think it is now being abandoned) but the Kitchen Sink Problem summarizes it. Classless components added to this but I don't think it takes the concept far enough.
So I've started work on something (for my own use) that I open-sourced which is to just use CSS variables to customize a UI, and the occasional
data-directive to describe intent or behavior, but generally the goal is to never useclass=orstyle=in HTML. (Or JSX if you use that.)See the docs here and note that there are no classes or style attributes anywhere for that site. If you scroll down (or menu) to the Colors section, there is a light/dark mode toggle button. There's also a "kitchen sink" (the good kind) page of examples here.
I don't know what will become of this project. It is early. But I feel like I have taken a refreshing shower when I use this, washing off a lot of gunk. Customization is just creating a site.css or simimilar overrides file full of CSS variables.
vitre-jsis the JS code to handle interactive behaviors but barely started with only a couple of behaviors supported so far.I'm not suggesting any one use this, yet, just that it serves as an example of rethinking this whole approach with classes and CSS frameworks. I see them, especially styling mixed with the HTML, as software development gone off the rails. It is a lot easier to implement an actual consistent design system if you never specify styles on HTML elements (or anything, other than behaviors).
Same here. NueJS has armotized my mindset about styling. One of the most underrated things it gifted me is to realize how the component boundary and layering should semantically be.
Unfortunately, there are a huge confusions of the component style boundary in the frontend folks nowadays, and it is always taking people to endless arguments without giving any helpful idea about how to make their projects' design system easy to scale and safe to change.
The problem I have with utility-class-oriented libraries such as Tailwind or Bootstrap is that, they tend to over-using the utilities.
As my experiences in setting up complex design systems (on multiple landing pages + multiple themes in one codebase, or one theme for deep-layered components, etc.), the utility class is designed to be worked with semantic naming systems like a charm and it obviously gives us endless choices of writing reusable components, as long as we understand the reusable component boundary + the CSS selectors & nesting.
For example, I feel very comfortable to work with styling like this
Rather than this
Because it gives me an image of design system composition, how the childs being affected by the parents, not a component sitting alone.
Yes. That's exactly what's going on, actually.
If your stylesheet is 5000 lines long, your problem is letting it get that big, and no amount of fancy new tech is going to fix that attitude problem.
This is blatantly untrue. Reality is the exact opposite: Where proper CSS does not grow with the app, inline styling does. Every new HTML page adds its own styling on top of the others. This is precisely one of the reasons why inline styling is bad.
This is very hyperbolic. Inline styling, whether it be using
style=orclass=makes refactoring of HTML structures a bit easier. The difference is only "dramatic" when compared to dramatically bad CSS. But the advantages are offset by the disadvantages: updating the styling now happens all over the app rather than in one stylesheet.Incorrectly assuming every website is rendered entirely using components does shift the balance a bit, but if that's your assumption, you can also leverage that to structure classic CSS: One file per component, strictly following the same hierarchy. This can easily be integrated into tooling and makes refactoring even easier: any CSS rule in
component.cssthat doesn't get used incomponent.jscan get removed.I, too, like to hide my abominations in the basement. Good thing the stuff us devs build aren't the kind that can just grow legs and walk away. But when working on a more real world project, I'd still rather write code that I don't need to hide.
Again, this is completely upside down. Production HTML is what gets mangled, while on the development side it's hidden behind abstractions like components.
Yeah that sums it up very nicely, great write-up - I've rarely seen it explained this clearly and simply!
I do come across code where people do NOT encapsulate things in React (or other) components, and where they've appended 12 or more Tailwind classes to a
buttonor other element, and then I wonder how on earth we're supposed to read and maintain that - but okay, I'll just assume that that's an exception ...It's amazing how many articles about Tailwind begin by comparing it to poorly written CSS. Of course, anything poorly written will compare badly with alternatives.
card-variant-3is a terrible name for a class, at least without there being a separate style guide explaining why there are multiple variants of card, and how to choose between them.card-variant-3-updatedis worse, since it doesn't explain anything about the update.Use proper semantics of the document contents for your class names, see how that improves maintainability, and then you can do a fair comparison with alternatives.
Thanks for your comment..
My experience has been that many teams don’t maintain the ideal semantic CSS architecture over time, especially in large React codebases with many contributors. In those situations, I’ve found utility classes and component-local styling easier to reason about because the styling context stays close to the component.
I don’t think Tailwind replaces good design systems or good naming. Rather, I see it as a tradeoff between centralized semantic abstractions and local component readability.
I appreciate you sharing the CSS-first perspective.
If a dev is too scared to modify because they can't figure out where the class is used, the dev needs to learn how to use AI or something isn't right about the styling at component level or style encapsulation.
Also, if the style sheet is one big 5,000 line file, then the team need to hire a new senior/lead dev from outside who can refactor the whole front end.
Maybe I'm missing something, but you should always make utility classes. If you don't want anticipate for every use case and make a whole set, you can add more as you go.
The reasons "why you should use TailWind CSS" listed here are indication of something really wrong about the whole frontend architecture to me, and these plausible issues aren't fixable by slapping on Tailwind CSS.
Dramatically Lower Risk in Refactoring compared really bad use of CSS, but not from in-line styling.
Readability? The example has nothing to do with Tailwind. If you hide everything in a component, it will look clean regardless of Tailwind or not.