https://colton.dev/blog/tailwind-is-the-worst-of-all-worlds/ Skip to main content Colton Voege Top level navigation menu * Home * Archive * About * Feed Tailwind is the Worst of All Worlds * 21 July 2025 * React, * CSS, * Tailwind, * HTML Tailwind is the worst of all worlds. It is a regrettable step backwards that takes everything bad about CSS and modern web development and brings it all together in one library. CSS's Successes and Failures Of all the web technologies that underlie the modern web, the one that has received the fewest fundamental changes is CSS. We've gotten amazing things like flexbox, grid, container queries, and more which have supercharged the ability to quickly build responsive styles. While we're far from the days of using tables for layout and googling "how to vertically center css" once a week, the language semantics themselves hasn't had to change much. This is because CSS offers two well rounded ways of applying styles that work pretty well: inline styles and stylesheets. Inline styles let you quickly add targeted rules to a single element. Much maligned historically due to the fact that many abused them and thus failed to keep their code DRY, there's really nothing wrong with inline styles. If there is something wrong with them, it's simply that an HTML attribute is a kind of clunky place to write out styles. They'll wander off screen on your editor once you have enough rules and it's harder to get nice formatting or static analysis. But if you're a good developer you won't use inline styles this way very often. (This is what we call foreshadowing). Stylesheets are the much more powerful feature. They let you assign a set of rules to zero, one, or many elements at once with the minimum amount of code. They also include all the fancy features like media queries, container queries, and pseudo-elements, which are not available inline. Flaws that existed in the early days of stylesheets have largely been remedied, albeit not always in the most desirable way. SCSS variables had broader support (since they simply compiled to basic CSS) and were prettier than CSS variables, but eventually SCSS started losing steam in the face of an improving CSS. CSS definitely isn't perfect. Like JavaScript's == operator, some ideas were bad then and are still bad now. CSS chooses which styles overwrite others based on selector specificity, leading some style debugging to be a bit of a rat's nest where the developer ends up bailing out to !important tags, which just kick the can down the road. Worse yet, when two rules have the same specificity, the default functionality is to pick whichever is defined last in the stylesheet. So, in this example, the styles of two win out over one, because the .two rule is declared after the .one rule, even though the one class comes second on the div.
With modern multi-sheet websites and bundlers, this can get confusing fast as it can be hard to keep track of which sheet was loaded first on every page. Additionally, CSS's status as a separate language from JavaScript is a blessing and a curse. It's a blessing in that CSS can be downloaded, parsed, and rendered in parallel extremely quickly by modern browsers, sidestepping the single-threadedness of JavaScript. But a curse in that modern apps simply require some amount of JavaScript-CSS interop since CSS is not a general purpose language. Sharing variables to keep styles consistent betweent he two means duplicating code between the two languages. Much of these problems are solved with modern tooling, albeit often with their own caveats. Tools like styled-components brought JS and CSS together fairly seamlessly, giving you all the power of JavaScript (functions, etc) in your CSS. The caveat is that styles are compiled into Javascript (slow, single threaded) rather than CSS (fast, parallel). Tools like vanilla-extract have done an admirable job giving you fully typed CSS-in-JS-in-CSS, compiling JS created styles into CSS files. Lastly, tools like React have made inline styling much better with objects that are linter, type system, and formatter friendly. Okay, now can we dunk on Tailwind? For reasons we'll get into later, Tailwind decided to burst on to the scene and become the default styling solution for all modern web development despite taking each and every one of these problems, exacerbating them, and applying them to places that didn't have them before. Lets see how Tailwind replicates CSS's main paradigms. Remember, inline styles let you control single element styles simply and fairly explicitly: style="background: red; color: blue;". Classes let you control multi-element styles simply, with the slight annoyance that it's easy to misspell your class name and spend way too long debugging before realizing it: class="todo-item". Tailwind exclusively offers inline styles via classes. Classes are single strings separated by spaces, not key-value pairs. So the classes must be something like class="bg-red txt-blue". Key and value must be obscured into a plain string, lowering readability and writability. If you misspell one of these plain strings your editor is not going to tell you. You'll just have to find out once you get into the browser. Did you notice that txt-blue is wrong, and it should actually be text-blue? No? Get used to it, you'll make that mistake daily because Tailwind is inconsistent in how it names things. Rule sets What about applying a set of rules to multiple elements, as you do in style sheets with class rule sets, what does Tailwind offer for that? Nothing. The answer is nothing. Tailwind's official recommendation for this is to ignore everything you've ever learned about constants and duplicate your class names. Then simply remember to always use multi-cursor editing any time you touch them: When duplication is localized to a group of elements in a single file, the easiest way to deal with it is to use multi-cursor editing to quickly select and edit the class list for each element at once. You can, of course, disobey the Tailwind overlords and create constants or components, but those solutions end up clunky because you are dealing purely with strings instead of objects. An element with a mere 10 CSS rules, will inevitably wrap in your editor and be more difficult to read and extend than a simple object would be: const sharedClass = `mx-auto max-w-md overflow-hidden rounded-xl bg-white shadow-md text-black border border-black border-solid`; return vs. const sharedInline = { marginLeft: 'auto', marginRight: 'auto', maxWidth: MEDIUM_WIDTH, overflow: 'hidden', borderRadius: EXTRA_LARGE_ROUNDED, background: 'white', boxShadow: MEDIUM_BOX_SHADOW, color: 'black', border: '1px solid black', }; return CSS's Problems are Tailwind's Problems Tailwind offers no solution to the built in problems of CSS. Tailwind classes are generated dynamically outside of your purview by the bundler and thus make it even harder to understand who will win a specificity conflict. For example: This seems straightforward. Red when inactive, blue when active, right? Nope. Which class comes first in the generated stylesheet is not predictable. Tailwind's recommendation is to, once again, ignore basic coding principles and recommend you duplicate your business logic: As a result, tailwind developers fall back on the same trick as the CSS developers of yore: spamming important tags (in the form of ! suffixes on their class names) every time things don't work out nicely. Did you really say bundler? Yes. Despite offering no real solutions to CSS's problems and instead making them worse, Tailwind requires a plugin and a bundler to function. It can not be simply dropped into a plain web page. This bundler is doing code processing so basic it's almost admirable. It simply searches all your files and looks for things that look like Tailwind classes, and then it makes a CSS class rule set for each one. The oft-touted benefit of this system is that Tailwind will minimize your bundle sizes by only including what you need. Unfortunately this benefit does not hold up to basic scrutiny on medium-to-large codebases. By the time your codebase gets big enough to actually worry about how much CSS you have, you will have one class for every individual possible CSS rule you'll ever use. Still, this isn't much of a problem since CSS loads in parallel and it loads fast. The problem is what it does to your JavaScript bundle sizes. Tailwind expects you to define styles via several classes on basically every element in your codebase. Meaning every time you use it you are writing out long strings which increase your slow, single-threaded, fully blocking JavaScript bundle size. Consider the following simple example with plain CSS: .todo-item { color: var(--warning-color); background: blue; }