Tailwind, CSS and complexity in Computer Science
We treat Tailwind and CSS Modules like they "fix" CSS, but all they really do is shift the complexity and hide it behind new abstractions. In this episode I unpack why tooling isn't a silver bullet, how misuse just reintroduces the same old problems in a new costume, and why understanding composition and semantics in plain CSS still matters if you actually care about engineering.
Hello friends.
A short disclaimer from my side - this will be a more technical narrative about complexity in computer science looking from the CSS perspective.
So - You’ve been warned, and now - let’s dive in.
Every few years in frontend, we collectively discover that everything we did with CSS before was obviously wrong.
First it was "just write some CSS". Then it was BEM and naming conventions. Then CSS-in-JS. Now Tailwind, utility-first, CSS Modules, and whatever comes next.
And every time, the pitch is the same: this new thing finally solves the mess. No more global overwrites, no more tangled styles, no more cascading chaos. Just drop this library in, add some magical classes, and the pain goes away.
Except it doesn’t. It just moves.
What I want to talk about here isn’t Tailwind vs pure CSS as a framework war. I’m more interested in the pattern behind it: the way mature tools get wrapped in layers of abstraction, not because they were fundamentally incapable, but because we collectively stopped using them with discipline.
Let’s start from the boring, unfashionable thing: plain CSS and Sass.
You can structure CSS in a perfectly sane, composable way. You can use semantic, BEM-like class names. You can split files by domain, by component, by feature. In Sass, you can generate unique identifiers, compose rules, use variables, mixins, and build very strict local structures.
If I really want to, I can generate distinct, non-overlapping class identifiers for each component from Sass alone. I can get almost exactly what CSS Modules gives me: a mapping from some local name in my file to a unique, never-colliding class name in the output. That’s not magic. It’s just convention plus tooling.
CSS Modules take this and industrialize it: instead of trusting humans to be careful, they enforce local scoping by default. Every component gets its own little CSS island. It’s not that CSS got better. It’s that we stopped trusting humans with the global namespace.
Same story with Tailwind, just with a different aesthetic.
For the people in the audience who don’t live in CSS land all day: Tailwind is basically a big predefined dictionary of tiny CSS rules. Instead of writing a class like .button-primary in a stylesheet, you write things like px-4 py-2 bg-blue-500 text-white rounded directly in your markup. Under the hood, it’s still CSS. The difference is that you’re composing your styles out of little Lego bricks of utility classes instead of naming and grouping them yourself.
Does Tailwind introduce a new capability to CSS? Not really.
It gives you guardrails, a design token system, some constraints, and a very opinionated way to compose things. In return, you give up a lot of the raw flexibility of plain Sass or CSS. You agree not to use the full expressive power of the language, in exchange for something that is harder to screw up quickly.
And this is where the interesting part starts.
Because this isn’t about CSS at all. This is the same pattern we see everywhere in engineering.
A tool matures. It becomes powerful and flexible. With that flexibility comes complexity. To use it well, you need some discipline and some time to learn it. At the same moment, culturally, we push more and more people onto that tool who don’t have the time, the background, or frankly the interest to understand it deeply.
So what do we do? We wrap it.
We encapsulate it. We hide the parts that are hard to reason about behind friendlier APIs and stricter rules. We promote the abstraction as the new best practice. And because the abstraction saves people from their own mistakes, it spreads.
But complexity doesn’t disappear. It just changes shape.
You can absolutely misuse CSS Modules. You can absolutely misuse Tailwind.
If your mental model for composition is fuzzy, the abstraction will not save you. You’ll just end up with a different kind of mess: thousands of slightly different utility chains, copy-pasted all over the place, or a jungle of component-local styles that still leak assumptions everywhere.
In big projects, something funny happens.
Even in Tailwind-heavy codebases, people quietly start doing the thing Tailwind was supposed to replace. They create files that group several utility classes into one named construct: essentially, a pseudo-component style. They extract reusable bundles: "here are the card classes", "here are the button classes". They build their own micro-abstraction layer on top of Tailwind because the project is complex enough that humans want names and structure again.
At that point, you’ve almost reinvented CSS classes. Just with more indirection, more build tooling, and less of the original flexibility.
The same compositional problems that existed in plain CSS reappear, just delayed.
That doesn’t mean Tailwind or CSS Modules are bad. They absolutely solve real, practical issues for teams where CSS is everyone’s third or fourth language. In environments where a "content developer" is also a "full-stack developer" and a "React developer" and occasionally a "Python developer", it’s naive to expect deep mastery of CSS architecture.
In those contexts, these tools are guardrails. They prevent the most dangerous footguns: global collisions, leaky selectors, unbounded cascade. They standardize things so a rotating team of developers can ship something without first studying CSS architecture.
The problem is when we start telling ourselves that these tools are inherently superior, as if they expand what’s possible. They don’t. They restrict what’s possible in a controlled way, and that restriction is the feature.
And this restriction isn’t free.
You pay for it with: extra abstraction layers, more build tooling, lock-in to specific conventions and, often, a loss of performance or flexibility that you could have had with well-structured plain CSS.
So the point I want to make, especially to younger engineers, is this:
Reaching for a new technology because it looks like a golden solution is tempting. Tailwind, CSS Modules, the next framework – they all come with a nice story: "your pain comes from the old way; this new way fixes it".
Most of the time, the real problem isn’t the technology, but our usage patterns.
If you don’t think about composition, no abstraction will do it for you. If you don’t understand the underlying model, the tool will hide your mistakes, not remove them.
By all means, use Tailwind. Use CSS Modules. They can be the right tool in the right context.
But be honest about what they are.
They are scaffolding for teams that either cannot or will not invest in deep CSS engineering. They are guardrails around a powerful system, not an upgrade to it. They make some things safer by constraining what you are allowed to do.
And if you do care about engineering, about long-term complexity, about understanding the systems you work with, then at some point you have to go back to the underlying thing: the language itself, the composition, the semantics.
Because the abstractions will keep changing. Tailwind will have a successor, and that successor will have another.
But the underlying complexity is always there, waiting for whoever decides to actually look at it instead of just wrapping it one more time.
These are weird times and I fully reserve my right to be wrong, but everything I said here was wholehearted.
God speed and thank you for listening.
