Why We Love Tailwind and You Should Too
We've been moving our apps over to Tailwind CSS over the last few months. Tailwind is controversial, and normally we don't use CSS tooling; but we think Tailwind is worth it. Here's why.
We got bit by the Tailwind bug in late 2023, and we’re more excited about Tailwind than any other CSS-related technology we’ve seen over the last 15 years. Tailwind, along with Tailwind UI and Figma to Tailwind, has easily doubled the rate at which we can ship new layouts for our clients.
But Tailwind Isn’t Maintainable, WET not DRY!
If you’ve heard of Tailwind, you’ve likely run into blog posts like this one which say that Tailwind is effectively inline styles and unmaintainable unreadable spaghetti code.
Surprisingly, I agree with that assessment, with caveats. Every tool comes with tradeoffs. Tailwind’s downside is HTML readability. For example, below is some Tailwind I wrote with some Vue code recently. The following blob of classes is a challenge to read. The code aesthetics of conditional Tailwind classes in React are even more abysmal.
So hold up, if Tailwind makes your HTML less readable then why is it good? The number one benefit we’ve seen is that copy/pasting layout is trivial. Without Tailwind, trying to copy/paste some layout that you like is hard due to styling conflicts. One class name conflict and you risk breaking your entire layout. I once copied some CSS that had `.container { background-color: white }` and made the client’s entire site into a white screen; not good.
With Tailwind, styles are scoped to the individual elements. So if you find an element you like on Tailwind UI, or you’re looking at HTML generated by Figma to Tailwind, you can copy/paste into your layout without breaking anything else. Changes are also easy: if you just want to add some left margin to a button, you just make the changes to that button, without impacting anything else.
So, yes, Tailwind code is less readable locally. But Tailwind code is more portable, and often easier to understand due to colocation. Plus, let’s face it, we web developers typically don’t read CSS, we look at it through “Inspect Element”. With Tailwind, your workflow is less about reading and understanding CSS. It is more about copy/pasting some elements, inspecting the result in Chrome, and making minor tweaks.
Cascading Is Usually What You Don’t Want
We advise developers to not get too attached to any one pattern or practice; reactive vs imperative programming patterns, dependency injection vs singleton, etc. Because you will run into situations where one pattern is the clear right choice, and other situations where the other is the right choice.
CSS only offers cascading styles: a `.container {}` selector applies to all elements on the page with the “container” class regardless of page structure. So JavaScript developers have started using patterns like scoped CSS to make CSS that just applies to some elements, but not all.
Easy copy/paste is part of the benefit of Tailwind and scoped CSS, but there’s another benefit: code colocation. Developers love talking about separation of concerns, but excessive separation of concerns can make code hard to read. Think about trying to follow the chain of events when clicking a button in Redux and having to go through 6 files of boilerplate to understand what the button click is doing.
When all relevant code is in one place, the code becomes easier to understand. With CSS, when you’re looking at an HTML element, the element’s appearance can be affected by any CSS file. With Tailwind, you know that the HTML element is styled just by the code you’re looking at.
Don’t get me wrong, CSS is great, and I make use of global CSS alongside Tailwind for basic rules like “buttons should be green by default” and “headers should be in Lato font by default”. But the global styles are limited to a handful of reasonable defaults, most of our styling is inline Tailwind.
Not Just Inline Styles
Yes, Tailwind is a lot like inline styles. Indeed, many of the most common CSS classes, like `w-full` and `bg-white` are easily replaced by inline styles `width: 100%;` and `background-color: white`. Tailwind’s class names are more concise, but can also be harder to read.
However, there are a few capabilities that Tailwind adds. First, inline styles can’t specify media queries, so there’s no way to apply mobile-only or desktop-only styles using inline styles. Tailwind supports prefixes like `md:` or `sm:`, so `sm:w-full` means “width: 100% if the screen width is at least 640px”.
Second, inline styles can’t use pseudo-selectors like `:hover` or `:disabled`. With Tailwind, `hover:bg-white` makes the background white on hover.
Finally, Tailwind also includes numerous utility classes that don’t line up exactly with CSS rules. Our favorite is `truncate`, but we also like Tailwind’s default `shadow` and `ring` classes.
And, Yes, AI
It is inevitable: we will use more AI generated code in the future, not less. AI generated code lines up nicely with Tailwind. You’re not going to regenerate a completely new layout from scratch every time, you’ll use code generation tools to generate a layout for a small piece of your app, and then copy that layout into your existing code. And because Tailwind makes copy/paste easy, Tailwind also makes it easy to integrate generated layouts into your code.
C/P FTW
Tailwind has shown us a future where we don’t have to write CSS, we just copy existing layouts or auto-generated layouts we like and give them some minor tweaks. People joke online that web developers don’t write code, they just copy code from StackOverflow answers. And while that isn’t entirely true (they also copy code from Mastering JS articles), using code snippets that you find online and adjusting them is a core part of JavaScript developers’ workflow. Tailwind makes that same pattern easier for layout.
Our Most Recent Tutorials
Check if a Date is Valid in JavaScript
What We’re Reading
Neon docs on branching: Branching is an interesting idea where you effectively “fork” your production database by creating a materialized view. This is neat for development deployments: you can start with a clean fork of production data and then read/write to the database, giving you a more realistic test of how the feature will perform in production.
HyperFormula: headless spreadsheet evaluator. Build your own Google Sheets clone plugged into whatever data source you want. Spreadsheets are the duct tape that holds the business world together. I don’t have an exact use case in mind yet, but there are a lot of possibilities.