Working with Tailwind CSS every day for 2 years
themosaad.com
themosaad.com
First of all, it's important to understand what Tailwind is good for, and what it's not good for.
Tailwind is NOT a design framework. It doesn't dictate the "theme" of your app. I like to think of tailwind as a tool for writing "clean" css that encourages good UI/UX practices. We need to stop comparing it to Bootstrap.
Tailwind is a tool that makes using "good practice" design simple and easy. If you haven't read Adam Wathan's "Refactoring UI", I'd recommend it. It's a book that helps developers make better UI/UX design decisions, and Tailwind is the framework that Adam developed that encourages a lot of those practices.
On that note, my recommendation is, "Tailwind CSS is AMAZING if you're a small team of developers and you don't have a dedicated designer". Tailwind probably is NOT a great choice if you work with a team of designers and need everything to be super-custom and pixel-perfect.
It's also important to note that tailwind is based on utility-classes. It can easily be used alongside "regular" css. I love that it gives a sane convention for writing that stupid "padding: 4px" class you're going to need. My old apps had crap like this that all did the same thing:
.p-4 .pad .padding-4 .padding-button-4
After several developers got their hands on the CSS, and didn't realize there were already classes defined for what they needed.
Bootstrap has actually a lot of utility classes (since v4 IIRC).
As an alternative, use a scss mixin (or even just a class) called standard-box and then use it everywhere. Do that for all the major parts of your design and you’ve created a language with which to build individual chunks of UI. Modifying the design system itself isn’t free but it’s a lot easier than mapping the change to every use of every tailwind class.
See https://gist.github.com/axeldouglas/7f45b2a862401c7b515c138e...
Somebody writes a React component to abstract a button and its standard styles, and all seems simple enough. Then we need marketing styled buttons, and buttons that are anchor elements, and ones that are small and large, and inverted and with and without shadows because the design people insist, with icons, block width, pills, etc, etc. And you end up with a monstrosity component with all sorts of bespoke interface that everyone has to learn, when some cascading classes and plain HTML would have entirely sufficed.
I would much rather dig into a component that has been built to be flexible for all of those needs to keep it up to date, restyle of add new behaviors and designs requirements vs going through hundreds of html files looking for all of the classes that were created to do those variations you list above (including effectively duplicate (or worse yet cascaded with different behaviors to accommodate the different html construct they were placed on when created).
I really did dislike tailwind from first learning about it, but using it has turned me on it 100%.
On the rare occasions where I need to duplicate groups of Tailwind classes, I’ll extract those into ‘semantic’ CSS classes using @apply.
I think I get it. For years there have been lots of popular CSS authoring conventions for JavaScript apps that are clever but complicated, fragile, and have a lot of churn. Most of these either have large JavaScript runtimes that generate and inject styles on the fly as components render, or deep integration into the JavaScript build system like Webpack/Babel/etc. plugins, or both.
I believe that there was intense fatigue from supporting these systems, keeping up with the newest versions and trends in the high-churn JavaScript ecosystem, etc. Tailwind essentially offers a ridiculously simple way out of this fatigue. You just say screw it, let's just author all our JS component styles using string literals that from JavaScript's perspective are just totally arbitrary HTML classes. No more importing things, or using plugins that transform the AST of our JS source code, or extracting chunks during SSR, etc. We just run this totally separate Tailwind compiler that looks for string literals that seem like Tailwind utility classes and generates a stylesheet that we just import in our HTML.
Tailwind is pretty great on its own as a utility CSS framework. There's a lot of thought put into the "API" of their utility classes, and because it's so popular there is a ton of support for almost any use case you'll come across. But at the end of the day, I think the reason for its ubiquity (particularly among respected veterans and "influencers" in the frontend community) is that is offers a way out of the "JavaScript tooling rat race" that was causing so much fatigue.
However I think I'm starting to come around because it finally clicked that modern web programming already abstracts styles on another level, as components.
So instead of repeating yourself, you create something like a StandardBox component and it brings all of its styles.
It still doesn't look or feel very elegant to me, but this seems to be the compromise most web developers are willing to make. Changing the theme seems to be mostly done through variables.
const headerStyles = ["bg-slate-100", "rounded-xl", "p-8" ...]
...
<div className={...headerStyles} ... >...</div>
and so on. So at that point, why use Tailwind anymore?Currently I use Vanilla-Extract, it's like SCSS but uses TypeScript instead of the SCSS language. It also compiles down into native CSS, so there's no runtime overhead like styled-components, emotion, etc.
In my case, tailwind was useful for providing a handy set of vocabularies for simple and common stylings. But once customizations start to pile on, we're back into SCSS. Using 2 systems at once meant additionally gluing them with the postcss toolchain, so effectively we have 3 preprocessors running for every style refresh.
Looking in at TypeScript from the clojurescript ecosystem though, I'm still yet to see an equal to https://github.com/noprompt/garden or https://github.com/Jarzka/stylefy: single language, excellent composability, compile-time anonymous class names, inline styles... almost like they solved CSS (except for typing)
that said, tailwind's and SCSS's VS code integration is pretty amazing.
So then everything goes through a single system.
https://github.com/homebound-team/truss
> tailwind's and SCSS's VS code integration is pretty amazing
We get that too, by being just vanilla TypeScript, no editor-specific integration necessary. :-D
(I've linked to Truss in another response, so will stop now. :-))
I like Vanilla-Extract! Didn't wanna add to the article by talking about it and other alternatives I tried.
It solves the main two problems I encountered with Tailwind CSS by offering type-safety and low learning curve since you're mostly writing vanilla CSS.
However, some of its issues are:
- Isn't as widely supported as Tailwind CSS since it's exclusive to TypeScript (cannot use it in Laravel)
- Requires a special integration with each framework (couldn't use it with Remix.run)
- There's a runtime overhead when you use it inline via its Sprinkles package. Without it, you're back to thinking about class names and having your CSS in a separate file which I personally found not to be neither the most productive nor the most maintainable approach.
That being said, Vanilla-Extract author has recently Joined Remix.run and I hope they address some of these points soon.
<div
class="display-flex align-items-center gap-8px border-radius-6px padding-4px"
>
<!-- ... -->
</div>
> Instead of: <div class="flex items-center gap-2 rounded-full p-1">
<!-- ... -->
</div>
But... why ?I just don't get tailwind.
I'll try to write another article about the WHY soon.
Whatever way that’s refactored, I only want to see that when I’m working with the styles and I don’t wanna do anything special for it either.
The problem with writing a lot of CSS is that you will eventually run in circles and repeat yourself on the same elements with only small differences. A design system lets you adjust it all at once through Variables, and as of recently - some of the more powerful Selectors and Pseudo-classes.
It also makes it easier to switch a design so you don't inherently lose all your work.
I don't have a strong opinion on Tailwind CSS because I don't use it but I am familiar with it. I know it hasn't been adopted inside CMS's all that much, but it is very popular when it comes to things like individual elements (cards, headers, etc.) because those individual elements will work universally across any Tailwind project.
Saw the folks behind a CSS-in-JS library (Stitches) move to it recently[1]
However, they still saw the need for utility classes for cases where devs want to hack together something custom[2]
[1]: https://twitter.com/colmtuite/status/1572918908637650944 [2]: https://twitter.com/colmtuite/status/1572918911301218305
I know there are some obvious things, but like you said CSS is used for both. So there is a lot "grey area". Especially when "style" can affect the "layout".
In Tailwind, I'll use a `p-3` or a `.flex.items-center` all day, but making a nice button is such a slog, since I'm coming from a Vuetify background.
Vuetify also has the same layout classes as Tailwind, like `pa-3` and `.d-flex.align-center`
I use Vuetify + Pug (instead of raw HTML templates), so I get to make buttons like this:
v-btn(outlined color="accent" @click="handleClick") Do Stuff
or a Data Table with, say, a custom-formatted UPC column, like this: v-data-table(:loading="loading", :items="items", :headers="headers", :items-per-page="50" dense).my-3
template(v-slot:item.upc="{ item }")
span {{formatUpc(item.upc)}}
(I like to separate attributes with commas, which is valid pug but invalid HTML)Sure, I could get something like TailwindUI, but their components are walls of divs and classes that each take half an hour to load into my head to visualize what's creating all the combos of states (regular, hover, focus).
So my Vuetify + Pug approach works great for greenfield projects that I create.
But if I'm downloading a nice template for a side project, say from Cruip.com, it's not like they're going to use the same build chain as me, and they do have to settle on a standard CSS library, so they default to Tailwind (just like 5~10 years ago they would have defaulted to Bootstrap).
I'd basically have to re-implement all of Vuetify in Tailwind, from buttons to chips to toggles to alerts, etc?
The v-data-table is like 10 sub-components stuck together, each of which I'd have to re-implement with walls of Tailwind classes.
It does however add some extra time to build your app - but consider that they have already built 90% for you - in terms of the design etc.
The best arguments against it:
1. Spamming utility classes causes horrible git commits, git history, git difference checks;
2. Conflicting utility classes aren't clear, and sometimes the order cannot be trusted;
3. Replacing `p-4` with `p-4` will require you to replace its occurrence all over your app, sometimes affecting thousands of matches. And since there is no context to it, it'll be hard to JUST replace them where you would want them to.
Vanilla CSS using `:root` declaration, a fixed base size (using `rem` and `em`), CSS variables and `calc()` are SO powerful.
In almost every single use case, using vanilla CSS or SCSS is far superior. For React projects (and Vue.js, and Svelte, and Angular), I'd recommend anyone to just use (S)CSS modules. It's so elegant and doesn't come with any of the disadvantages.
Except maybe a slightly larger package size. Minimally so. Your framework of choice should be (or allow) code splitting to take place. And a few bytes more or less aren't going to make or break most websites.
I've been a frontend dev for 25 years and I quite like Tailwind. I'm not sure which category I fit into. I think it might be both.
Replacing `p-4` with `p-4` will require you to replace its occurrence all over your app, sometimes affecting thousands of matches.
This is a really good example of where Tailwind is actually quite nice. Imagine if you didn't use a utility class, and instead you'd written your styles in plain old CSS or SCSS with "padding: 8px;" everywhere on your years-old-built-up-to-thousands-of-styles design. Replacing those is easy enough, but what about your SCSS mixins? Your CSS calc()s? The places where someone thought an element needed a bit more padding and used "padding: 9px" instead? Manually managing styles is hard. Tailwind makes it a bit easier, mainly because it encourages consistency and removes variability. Finding and replacing a "p-4" everywhere is trivial, and if you've used a library well rather than rolling out an ad hoc mix of things you can be quite confident your change will work everywhere. It's far from perfect but it's quite a well-thought out approach.
If you're applying `p-4` to all your component classes you're only marginally improving on applying `padding: 8px` on all of your component styles. Both are terrible solutions even if one is slightly better than the other.
I generally agree with OP. Tailwind is horrible for larger projects and I have no idea why it's so well liked. The utility class approach is bad, and naming of their utility classes is even worse.
For smaller projects or for prototyping it's okay, although even then it's only slightly better than inlining styles.
That solves the exact problem you and OP complain about, in a neat, configurable, safely replaceable way. Tailwind allows you to separate design intent from implementation values.
Variables are very cool and super powerful, but they fall down if you need to support old browsers and it's really easy to make things that are hard to reason about (defined in several places, used in calc()s, overridden in the cascade, etc). On a team that doesn't communicate or test well vars can be a source of pain.
I have around 10 years experience, tailwind is a godsend for maintainability and speed.
I can't say I've really had many issues with git diffs or conflicting classes. I'm not entirely sure why you'd replace blanket `p-4` all over your app. Are you making some changes to the spacing in your design system? Why not change it globally and let it propagate?
I'd tend to agree with you that (S)CSS modules are a great improvement over the completely detached styling of the past, but I still think Tailwind has a lot to offer with its prebaked system and dead code elimination.
Like, I thought we were supposed to keep our content and styles separate, not mismatch them all together with a thousand utility class imports.
Basically it's a component-mindset; create some file with everything in it so that in the rest of the app you can just use `<MyButton>...</MyButton>`.
The `<MyButton />` file takes responsibility for everything it does (logic, handlers, markup, style).
However; this does not mean these files should grow large. Instead when things become too big (say over 250 LoC) you split up responsibilities by sub-components.
It's basically dividing problems in ever-smaller problems, but NOT by separating by technology (html/css/js).
Separation of concerns is horizontal slicing of the app architecture, components however slice the architecture vertically.
How much passion does your standard sun contain?
This is an incredibly silly take. As a senior frontend dev with almost 20 years of experience and a solid resume, I will take Tailwind over anything else in most cases. Obviously there is always exceptions based on the project needs, but to have such a hard take like this just cracks me up at how narrow minded it is.
It doesn't enable you to do anything that CSS didn't already do. It's just a different way of writing it. Knowing CSS is a prerequisite to being able to use Tailwind.
"Tailwind is for beginners" just sounds to me like "I don't like this thing because I'm too smart for it"
More like "I don't like this thing because I don't understand it, so I blame it on juniors because I feel superior to them, so I must be right"
Tailwind does not require knowing CSS. It'll get you around tailwind faster if you do know it, but you do not have to care about what Tailwind is really up to when adding "outline outline-2 p-4 outline-offset-2". This is a major reason why people pick it up in the first place, to ignore CSS as much as possible.
Where are do you so frequently see that being suggested? I would never suggest that and I've never seen anyone say something like that before.
Tailwind is not an alternative to CSS, it's a way of writing CSS.
If you haven’t read and understood the spec then like most people you don’t know CSS, which is fine. It’s not a question of knowing what say conic-gradient does, but how browsers use CSS in depth.
Your HTML will be uglier and it will be more difficult to look at the elements panel to find the element you're looking for. Both of those things are super minor for me, and have almost no impact on my day-to-day.
Or, if you're using containers/writing components you can literally do something like this: https://pastebin.com/qkdzGWNT
But that a { could just as easily be "tag"
Personally, I've always found BEM to be tedious and the sort of "roll your own" class names just doesn't scale. This approach scales. This is what we should be moving towards as an industry.
If it's being asked repeatedly, it should probably be addressed somewhere. I see no mention of it in the tutorial or any of the amazing starter videos I've seen. I also have never used PostCSS directly before, so that's not an obvious solution. I appreciate the example you shared. I've asked (the OP in particular) in good faith, so I'm sorry that the question frustrated you.
> Personally, I've always found BEM to be tedious and the sort of "roll your own" class names just doesn't scale. This approach scales. This is what we should be moving towards as an industry.
Can you please elaborate on this? We've had well-functioning applications that predate Tailwind or CSS in JSS, so I'm not sure what doesn't scale. Descriptive names might get messy if you jam everything into in a single stylesheet, but using the cascading part works pretty well. If we're going to say "no, actually we were completely wrong when we said class names based on styles like 'red', 'big', and '2spaces' were bad", I'd like to understand what fundamentally changed. If it's just a case of convenience, that's fine. But, what about this scales better? How does this approach improve maintenance?
Your PostCSS example where you compose rules looks like a good approach, but I don't see that widely in use. Instead, I see the same rules repeated across elements in a document. That's the style the Tailwind docs use, so I'm operating under the notion that this is the prevailing way to write frontend code with Tailwind. While I can see that as an improvement over inline styles by ensuring consistent spacing definitions and such, that was never the recommended approach anyway. The style used in the Tailwind docs strikes me as being more problematic for ensuring consistency across pages or an app, which I see as hampering scalability.
Anyway, I'm trying to better understand why and how this shift is better. The only clear answer I've had thus far is that it's faster to get started because you get nice styles out of the box without having to adopt something like Bootstrap. That makes sense to me for small projects. At least I can see why someone would make that trade-off. I'm less clear on the long-term maintenance aspect and how utility classes help that.
Also, I think I am wrong. It's possible to use Tailwind without implementing a build step like PostCSS, which would necessitate using it much like the documentation describes.
>We've had well-functioning applications that predate Tailwind or CSS in JSS, so I'm not sure what doesn't scale.
CSS has fallen victim to the same "cult of semantics" that HTML has, and it's disingenuous bullshit just like it is in HTML (mostly).
Yes, if you have someone who really cares and writes great CSS they will have everything broken out into custom variables and well designed and named classes. You still have the problem of how you scale that, right? After all, if you have a .card that is formatted one way on one page but laid out differently on another, you have to think up a different class name or refactor.
Enter BEM. Block, element, modifier. A consistent naming system for your large application. That is both subjective, requires a lot of extra mental bandwidth in a time when you're lucky if half the "front-end developers" you're working with know more than the basics of CSS to begin with.
I have seen some truly tortuous HTML that uses BEM classnames. It was awful.
So when I say it doesn't scale, it's that most large codebases end up with !important everywhere and z-index:9999999 etc...
So throw it out completely. Utility classes make sense when you accept that you are probably styling a component and that markup will be inserted everywhere the component is. You never have to think about what your class name should be, and if it makes sense, or if it conflicts with other scopes.
Yes, an HTML element with 15 classes is not ideal.
The example I posted would hopefully be the logical end (and is if you're using Svelte with PostCSS configured) - where we're no longer writing html documents full of elements and then a stylesheet styling them. Each discrete UI element has one document, markup, state and styling are tightly coupled, and it can be reused many times.
It gets rid of nesting being very important, it gets rid of having to write verbose media queries for responsive. It makes changing things really fast.
The part about Tailwind providing sensible defaults in terms of sizing, spacing etc... is just icing on the cake. The problem that Bootstrap had is that it tried to do too much. It came about in an era when doing layout in CSS was really hard. So it rolls its own stuff like modals, dropdown menus, grid etc... it also takes it upon itself to style actual HTML elements, which to me is a very poor choice. Including bootstrap will get you default styles for headings etc... but I believe in Tailwind it's all done through utility classes.
Tailwind doesn't do that. The focus is on removing semantics from styling. Look at the API surface: https://tailwindcss.com/docs/
There's a "Components" link, but that's to a paid resource showing common UI patterns built with Tailwind.
https://tailwindcss.com/docs/reusing-styles#extracting-class...
"Whatever you do, don’t use @apply just to make things look “cleaner”. Yes, HTML templates littered with Tailwind classes are kind of ugly. Making changes in a project that has tons of custom CSS is worse."
Then it lists a bunch of reasons that seem to just be the author's preferences on things like naming and workflow productivity. I guess I don't think naming things is that hard. I generally need a name of some sort anyway so I can interact with the thing from JS. I haven't seen anyone argue not to use React or Web Components because naming a component is hard. I wonder if it's just a granularity thing. I tend to give a class name to a logical entity and then use ancestry selectors for nested tags, with SCSS mixins to avoid duplication.
With that said, I think it's reasonable to roll some things up into general classes. They remain very easy to change.
>I haven't seen anyone argue not to use React or Web Components because naming a component is hard.
I think the whole point is that that UI element, or "interface", or "component" is the thing that should have a name. That entity doesn't need to have a unique CSS classname.
Thanks for taking the time to clarify that for me. It does indeed change my perception on utility classes.
The point is that HTML elements don't need to have semantic class names at all. So even the top most element in the component would still use utility classes.
Of course, the trade-off is if you completely change the structure of that "component" you'll need to update any ancestry rules. But on the naming front, I'd need to come up with an identifier or logical class name to find the element in JS anyway, absent something like React that sort of controls the world.
I started with Tailwind and as I learned CSS along the way. Obviously people will look up stuff on MDN when they are stumbling on terms they didn't know about.
The tailwind docs + tw-intellisense plugin [1] are pretty good, too. Especially since the extension shows the raw CSS behind the abstraction when you hover over a class name.
[1] https://marketplace.visualstudio.com/items?itemName=bradlc.v...
I considered using it for a few projects but once I start I feel it too much work with no payoff.
You shouldn't use Tailwind if you don't already know CSS.
There are plenty of people bumbling through with both.
But understanding the CSS is needed to use it reasonably well.
Take the example `block`. Ok so a junior FE dev is supposed to use this class without understanding CSS? How?! It just maps to `display: block;`. It hides literally none of the complexity.
If it didn't hide any of complexity you'd be writing CSS.
That said -- you need to know the average amount of CSS to be successful using tailwind.
Adam Wathan, the creator of Tailwind
Unlike the yet-another umpteenth bespoke `.card-header__buttons .card-header__button--secondary` that no one can remember and create an umpteenth+1
Tailwind works great when your site/app is a collection of components
So why bother with a DSL like Tailwind over Component CSS/SCSS for example?
Everywhere I've seen CSS-in-JS used, everywhere it's people busy writing bespoke styles in every file.
I prefer a global set of styles and more specific selectors to override styles where needed. It may not work well on projects with many teams but for the projects I typically work on it's fine.
The thing with Tailwinf is that it already has a decent design syste encoded in its utility classes. It's very easy to slap a p-2 for a standardized padding than trying to remember what it was when you write the next component with `{ padding: 2 2 2 2 }`
> I prefer a global set of styles and more specific selectors to override styles where needed.
That's kinda what I mean. In all projects I've seen you have a global stylesheet with a bunch of classes that no one remembers, and hundreds of one-ofs in every component either reimplementing stuff from scratch or serving as an `!important` of sorts
I may run into the same issues as the other CSS-in-JS solutions (I only encountered stitch once, and not for a long period of time)
Doubly so when using TypeScript, as with Vanilla-Extract: https://vanilla-extract.style
The link literally has this:
``` export const className = style({ display: 'flex', flexDirection: 'column', selectors: { '&:nth-child(2n)': { background: 'aliceblue' } }, '@media': { 'screen and (min-width: 768px)': { flexDirection: 'row' } } }); ```
Which is indistinguishable from any other CSS-in-JS which has hundreds of these one-off things scattered everywhere. With a theme somewhere exposing another hundred or so variables of varing quality. And all these solutions basically converge on Tailwind (or other utility-first CSS approaches):
``` export const hero = style({ backgroundColor: vars.color.brandd, color: vars.color.white, padding: vars.space.large }); ```
is nothing more than `bg-brand color-white p-4`
But TypeScript support is definitely nice.
Re-typing/re-quoting:
--------
The link literally has this:
export const className = style({
display: 'flex',
flexDirection: 'column',
selectors: {
'&:nth-child(2n)': {
background: 'aliceblue'
}
},
'@media': {
'screen and (min-width: 768px)': {
flexDirection: 'row'
}
}
});
Which is indistinguishable from any other CSS-in-JS which has hundreds of these one-off things scattered everywhere. With a theme somewhere exposing another hundred or so variables of varing quality. And all these solutions basically converge on Tailwind (or other utility-first CSS approaches): export const hero = style({
backgroundColor: vars.color.brandd,
color: vars.color.white,
padding: vars.space.large
});
is nothing more than `bg-brand color-white p-4`But TypeScript support is definitely nice.
----
/// Define variants for a specific component, such as a button
export const button = recipe({
base: {
borderRadius: 6
},
variants: {
color: {
neutral: { background: 'whitesmoke' },
brand: { background: 'blueviolet' },
accent: { background: 'slateblue' }
},
size: {
small: { padding: 12 },
medium: { padding: 16 },
large: { padding: 24 }
},
rounded: {
true: { borderRadius: 999 }
}
},
// Applied when multiple variants are set at once
compoundVariants: [
{
variants: {
color: 'neutral',
size: 'large'
},
style: {
background: 'ghostwhite'
}
}
],
defaultVariants: {
color: 'accent',
size: 'medium'
}
});
/// Use in code
<button className={
button({
color: 'accent',
size: 'large',
rounded: true
})
}>
Hello world
</button>
Basically, unlike other CSS-in-JS libraries, you can encode your design system into something called variants, then you use them by calling the function `button()` to generate a class name.Notice how `button()` takes in certain properties and only those properties with their corresponding values (enforced by TypeScript). If you try to put in `button({ color2: 'testColor', ...})`, TS won't compile.
In this way, you can take the design team's ButtonSmall, ButtonLarge, NeutralButtonLarge etc and codify them into TypeScript such that you literally can't make a mistake. You simply can't do that in Tailwind, unless you use some outside plugin I assume.
(Note that VE also has a Tailwind-like atomic CSS API called Sprinkles, but since I don't like Tailwind I don't use this either).
Before Tailwind came along I very often found myself just wanting to put some inline styling in various places. It's so much easier to just add some CSS to an element than having to think of a class name, think of where the best place would be to put the class, etc.
*EDIT* for the above.
What are some typical defaults that I should start with for things like margin, padding, font size? What sort of items should I be initializing in ::root then overriding later?
https://necolas.github.io/normalize.css/
Design decisions, though, are ultimately up to your taste and judgement.
https://gist.github.com/bdougherty/404b4ca33dfdbff48614b454f...
The problem is that I think we are outnumbered. Most engineers want to avoid CSS, pretend it doesn't exist, and if all else fails, build a complicated mech suit they can climb into to manipulate it without coming into contact with it directly. That's why things like Tailwind and Bootstrap and CSS-in-JS solutions took off.
My solution on the teams I worked with was to say "hello, I will do all of the CSS in the entire application if it means we don't have to add another dependency just to indulge the team's distaste for CSS."
Near the end of my career I was pretty much just doing CSS all day. I liked it, but probably what I did was create a terrible situation for whoever came after me. They still hated CSS, were bad at using it, but now had to maintain a lot of it. We probably should just have consented to the convenience of the majority even if (as I still maintain) they are wrong.
I don't find this to be the case with styled-components. To me styled components feels just like writing CSS (which I like), but with a slightly obscure variable syntax (if I need a prop or theme value).
Things like CSS Zen garden made it seem like they were, but that was only a separation of control. If the HTML structure changes, then the CSS likely needs to change. That's a tight coupling.
CSS-in-js embraces the coupling and makes it explicit instead of implicit, which makes refactoring safer and speeds up development.
edit for typo
I've done a lot of thinking about this - like "How would we adopt what we have done in frameworks into web standards"? And the closest I have come up with is Svelte.
In Svelte your file is a component. It can have a <script> tag which defines state that can be passed to it or any other arbitrary javascript. You can have a <style> tag which scopes specifically to that component. And the rest is just regular HTML markup with the ability to do light interpolation for dynamic values.
That's how it should be, in my opinion. That's what we should be moving towards as an industry. I think CSS-in-JS was a side effect of JSX being in JavaScript. Hopefully we are moving away from that trend now.
I agree both with your interpretation, and with the sentiment. I think, in many contexts, using the Cascading part of CSS is a mistake. It's great for low-level design system kinds of work (e.g. setting matching text and BG color, then override font size or decoration later). But, it's pretty problematic when you start styling specific components in a web app. (This is less of an issue with static documents).
If we're using SASS instead of a css-in-js, we end up doing things like:
``` .my-custom-hero { h1 { // override necessary styles } } ```
That's essentially the same encapsulation. It's just hidden in a one-off class in the CSS rather than embedded with the HTML - which makes it even less legible. It also has a dependency on the root-level `h1` styles. If someone change those, they inadvertently change the hero's styles.
We could get around that by having a `.my-custom-hero .my-custom-hero-title`. But, that's essentially a worse version of css-in-js encapsulation: I have to come up with names for everything, and my component's internals are split across multiple files.
I wouldn't touch SCSS with a ten foot pole in 2022, especially not for React where literally any CSS-in-JS library is better, and I used to love SCSS.
I use the word 'components' because that's familiar to people, but there is a related concept in most modern frontend frameworks. In this world, tailwind makes so much sense. If you do it right, there is very much less in the way of shared stuff that needs find-and-replacing and you're keeping all the information you need to see how something is going to render all in one place.
But I will also say – you need to get over that. It's probably going to be ubiquitous, whether you like it or not. There is some justification for it, it offers some advantages for particular users and use-cases, and it's likely that those advantages are enough to outweigh the downsides in general use. It's not worse enough in the ways that matter. So I've embraced it in the past couple of projects and it's fine – at worst vaguely annoying, and I'm much happier not having to bother hating it any more. Life's too short.
I'm not necessarily a big fan of Tailwind, but this is a total strawman. Nobody would advocate having one big file with thousands of repeated classes or something. Just like it would be a strawman against the use of css variables to say that you'll end up with a bunch of global variables used in thousands of places that become impossible to change because you can't be sure you won't break something unintentionally. And in the extreme cases where it does happen, the latter scenario is often much worse of a problem IMO than having too much repetition, you can always get clever with find and replace to get through repetition.
Tailwind works when you use it with a component framework, you're not getting rid of all abstractions completely you're just moving them all to a single layer within the component class.
But both approaches still make it possible to shoot yourself in the foot and end up with a mess of unmaintainable code if you don't abstract things well.
As opposed to what? This is still an issue with any kind of utility class, where the alternative is inline styles or CSS-in-JS, both of which are substantially worse.
> Conflicting utility classes aren't clear, and sometimes the order cannot be trusted;
This is fixed in the IDE with tools like ESLint. I have VSCode setup to automatically reorder Tailwind classes, and it works great. (It even moves broken classes to the front of the list, so you can immediately identify if they're working or not)
> Replacing `p-4` with `p-4` will require you to replace its occurrence all over your app, sometimes affecting thousands of matches. And since there is no context to it, it'll be hard to JUST replace them where you would want them to.
This is an issue with CSS in general, especially CSS modules. Plus, if you use tailwind correctly (with @apply) you only have to update it once.
> Vanilla CSS using `:root` declaration, a fixed base size (using `rem` and `em`), CSS variables and `calc()` are SO powerful.
With tailwind, you still have access to `calc()` and variables with their bracket `[]` syntax. It's just much easier to do everything else.
"Whatever you do, don’t use @apply just to make things look “cleaner”. Yes, HTML templates littered with Tailwind classes are kind of ugly."
That is straight from the Tailwind docs: https://tailwindcss.com/docs/reusing-styles
Using @apply a lot just reinvents CSS; that's not the point of Tailwind.
I can write similar comments about the rest of your statements, but I think you're too narrow minded to accept any input so I won't bother.
I would say the documentation adequately responds to your complaints about re-usability then? Use a component, use the IDE, or use @apply. The notion about needing to change classes 1000 times for a small edit just isn't true (I've never had to do this w/ 2+ years of tailwind).
> I think you're too narrow minded to accept any input so I won't bother.
Making some sort of ad hominem attack about how I'm narrow minded is useless. I'd love to hear your input.
Plenty of very experienced developers like it just fine.
As a long term front-end dev who likes tailwind, I find this fairly offensive. Your second and third points sound like problems with CSS in general.
> Vanilla CSS using `:root` declaration, a fixed base size (using `rem` and `em`), CSS variables and `calc()` are SO powerful.
If I'm using tailwind on a project, I don't want more power. I want less. I want simple constraints that give me solid basic styling without constant adjustment. The adjustments I _do_ want to make should be easy. The options available should be good options, not all options. I don't feel like fussing with box-shadow for the umpteenth time to get something that looks nice, or poking at my margins or padding or gap until it works with the rest of the page. I don't want to come up with a grid system again, or a color set, or a bunch of variables to extract these from the custom classes so they see proper reuse and are easily available; I want them already done.
I've been writing CSS since IE 5.5 and I was tired of the whole horrible mess. Tailwind goes against everything I learned about the reasoning and benefits of CSS' styled web page design. Tailwind is right. Tailwind is how CSS should have been.
I (and more importantly my customers) do no care about minor differences in padding, or that the markup looks ugly, or that it takes a few extra minutes to parse the git commit (made up for by massive time savings elsewhere). Tailwind gives you a quick way to get a design that looks good enough while maintaining control over the layout of the page.
Need something custom? No one is stopping you from writing your own classes to compliment the utility classes or from moving common combinations of utility classes into their own class.
It lets me design things in browser in a way that nothing else I've ever used has. Precisely because of my CSS knowledge, I know how to do everything, it's just a matter of finding the class name for it (referenced post talks about having to look up docs a lot, I relate.)
There's also a lot of established UI patterns, so I don't have to reinvent the wheel. And I often do roll something up into a semantic class name when I know I'm going to reuse it a lot. For example, I have a .container that is a list of @apply'd tailwind classes. PostCSS gives a lot of flexibility.
But I mixed things. So I have media queries that affect that :root font size and that decreases the amount of element specific responsive work I have to do.
I should mention I'm also working in Svelte, which has the ability to scope local CSS at the component level. It's a delight. So I mix and match between utility classes and scoped (dynamic classnames get generated) styling.
Yes, you can do this sort of thing in CSS and SCSS, but you're already using a templating language that can do this job, so why are you pulling in yet another tool (SCSS), or why are you requiring the mental context switch to yet another language and another file (CSS) when you can stay directly in template you're actually working on?
In other words, by removing CSS from the full stack of template language+programming language+HTML+CSS+JS, you reduce the cognitive overhead and context switching needed to actually get stuff done. If you add htmx you can also remove most of the JS too, thus reducing cognitive load even further.
Everyone as usual saying "long class lists" even though the literal home page tells you not to do this and to make components or partial views or whatever your tool of choice calls it.
At least be inventive and follow the instructions before you start blaming something.
You simply can't look at people not reading the docs (or even the home page) who then moan about how the thing they are misusing isn't working - and then conclude it must be the fault of the thing instead of the people misusing the thing.
I do like Tailwind, for the record. It would be nice if its allies and enemies would both admit it makes tradeoffs.
What is the alternative?
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
Becomes: "[javascript][typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
} "[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript][typescript]": {
"editor.defaultFormatter": "something.else"
}
If they're going to allow conflicts anyways, just invert the structure so you at least get to use normal arrays: "editor.defaultFormatter": {
"esbenp.prettier-vscode": ["typescript", "javascript"]
}
Though, I guess the logic is that the other format allows you to override ANY setting based on language.I thought it wouldn't be a big deal but after seeing unnecessary long walls of class names I'm really tired. Anything other than basic styling is an absolute pain with tailwind and I don't understand why /my company specifically/ can't just use typestyle/styled-components/css modules to separate walls of text from components. With time, I degraded to the point I where I use classnames library just to split longer class string into 3-4 smaller ones so I can split the component into multiple rows to make it look better
I wanted to like Tailwind, but it seems a bit disingenuous. It's like using inline style tags, just with shorter names.
From time to time I've made an effort to learn how CSS works, but after a while I forget the details. It's more productive if I can browse a catalogue of visual examples, with concise markup that is easy to copy and paste.
Bulma seems like the more "modern" take on Bootstrap: https://bulma.io/
The goal of tailwind is the DX, and it seems to only acheive that with people that enjoy it. I know the statement seems obvious, but there are instances where you don't like a language or a framework, but it doesn't fight against you hence why I consider that a good DX.
`<div css={Css.mx2.black.ifSm.mx1.$}>...</div>;`
That must be one of the most horrid things I've ever seen done to CSS.
Definitely use whatever you like, but just like vim bindings, which look "omg wtf" to newbies but then become "cannot live without" once you learn them, once you learn the Tachyons-style abbreviations, I/we find that they fade into the background/become 2nd nature, and the succinctness ROI is worth it.
But happy to have you disagree/stick with TW.
I like that it helps reduce the walls of class names since the classes are on their own lines in the tailwind styled component.
Wrote about Vanilla-Extract shortcomings here https://news.ycombinator.com/item?id=33793944
> Lightning-fast build times since March 15, 2021
Just put some numbers in there. Average code base size, average build time improvement since version x, etc. I can be the judge of whether that is "lighting fast" to me
Fair enough. The video I linked to has some numbers Though. It used to take 19.44s to produce a 12MB CSS file in development vs a 1.9s to produce 11KB CSS file since this feature was released.
I believe it's even faster now (v3.1.8) as it only takes 260ms to produce a 29KB CSS file in one of my projects with dark mode and lots of variants.
I desperately needed a _language_ instead of a framework back when Tailwind was still a framework. As I could not get any response from them, I ended up rolling my own called Turbo CSS - this was like 2 years before their compiled version came out. Take a look if you want to consider alternatives.
https://developer.boomla.com/turbo-css
I'd compare the two as Tailwind being primarily a framework, a language second, while Turbo is a language first. For example, in Turbo you write libraries and reference class names in libraries via `my.btn` instead of polluting the global namespace. It also has first class support for classes, like you can do `hover:my.btn`, which Tailwind doesn't support (to my best of knowledge, it definitely didn't back then). Also, Turbo has zero global side-effects, which makes it great if you don't control the entire codebase (think CSS reset).
I backed out from maintaining a big Tailwind CSS plugin let alone create my own solution.
Similar to React, I don't think Tailwind CSS is easy to replace regardless of the slightly better alternatives that exist or might popup up due to its established community.
I pretty much always have the tailwinds docs open and thankfully they are very good and searchable.
It does get a bit wordy when you try to do too much with it though. The nice part is you can always just plop a class in instead of doing everything with tailwinds.
`h-8` utility is height of 8 * 4 = 32px or 8 / 4 = 2 rem.
Since design systems are supposed to be consistent, 4px is typically considered 1 unit of distance.
However, there are times when I might encounter values in Figma (or get inputs from designer) that won't allow me to use this easily.
Say, designer wants a max-width of 252px on an element. I usually use Alfred app on my work MBP to divide it quickly by 16 (since 1 rem is 16px under normal font-size settings), but you can use any calculator, even the one in Google search or DDG search.
It turns out to be a fraction, and in this case, it's 15.75 rem.
I use utility like `max-w-[16rem]`, closest consistent dimension that's a multiple of 1rem, and ship a pull request preview to the designer, asking for design feedback.
Chances are, designer agrees to stick to 16rem, and we ship it as is. If this width of 16rem, or closer values within the 250px vicinity, are used in other places in the design system components or our app; I'd typically add that to the Tailwind config as well.
Most of the time question of rem <-> px conversion comes into picture because we look at design dimensions in Figma / Sketch / Indesign etc. tools, and try to implement the same in our UI code.
But this would only slow down a developer, switching back-and-forth between design and implementation.
What I find more productive while prototyping a UI (or a smaller component), is to just "eye it", instead of getting actual pixel-values right at the first go.
From just eye-ing it, I can make a guess if it should be h-3 or h-4 (you can also guess the right value using a binary search style heuristic), and if my implementation looks bigger (or smaller) than the design, I'd adjust accordingly.
Only after I've implemented a basic prototype of the UI component, I'd cross-check with the design tool, and edit some utilities if necessary to get as close to the design as possible.
The first 10 minutes or so of this video is also a decent introduction it seems which will help put this project into context for you so you can see specifically what kinds of problems it solves in a way that Tailwind doesn’t and vice versa. https://youtu.be/O53MwmolKP4
`.author-bio > div > h2 { color: red } is NOT good CSS. The div part has no reason being there (why would the div change the color of the title ?). After presenting this auto-created problem, he sets out to solve it with BEM, where you need untold numbers of classes for everything. So now, CSS has a naming problem, created to solve a non-existing auto-created CSS problem. And Tailwind now solves this pseudo-problem.
Apart from this, the thing that really bugs me is that every advantage claimed (apart from one) is also non-existing because if you do your CSS right, the same claimed advantages arise in pure CSS. Design tokens ? No need for json, custom properties are all you need. Specificity ? Cascade layers, pseudo-selectors are here for you.
The only "advantage" I see is the possibility of working in one file only rather than a couple of files. And that, for me at least, is not a real advantage but only a justification for being lazy. There's no way the price of all these processes and dependencies complexity are worth the laziness-added value.
It is like the next evolution of Tailwind. See this blog post for full explanation: https://antfu.me/posts/reimagine-atomic-css
As someone who's been writing HTML since Netscape days, I find Tailwind/UnoCSS plus a modern web framework (React/Vue/Solid JS) the most productive way to develop websites ever.
The main thing is in a single React or Solid JS component, you can have all the code, html and styling for a component in one file or most of the time in one function even.
No more having to deal with jumping around between template and css files, having to search for where a class or a styling is defined, or trying to figure out how to structure or even name the css classes. No more having to make decisions like "Should I put this styling in this class or that class or in a classless selector."
For me, it just saves too much time to not use.
I was skeptical at first, but once I started using it, I could never go back.
I think one of the primary reasons Tailwind is so popular is how poorly React deals with classes and styles. In other frameworks/libraries like Vue/Svelte CSS is treated as a first-class citizen with a lot of nice quality-of-life improvements that React teams are forced to implement using awful CSS-in-JS solutions or with Tailwind. You get single-file-components, scoped styles, etc, all out of the box and without having to learn a new abstraction.
If the dev world wasn't a React monoculture (and more devs appreciated CSS deeply) I think you would see significantly less interest in Tailwind.
It is not perfect by any means, and in larger components/pages it can lead to some pretty long, difficult to reason about files. But its the best workflow I've had the opportunity to work with so far.
Tailwind is a leaky abstraction - https://news.ycombinator.com/item?id=33787218 - Nov 2022 (272 comments)
I just discovered last night there’s an aspect ratio property…
And this shocked up in Google. Rocked my world. I wanna go back across a bunch of products I’ve worked on over the years to fix stuff using this!
One of my favourite features now!
I feel like I’m taking crazy pills
You're not. If you look hard enough, long enough, you'll see this same pattern in every corner of programming. Round and round, never forward.
(External quote from the maintainer)
Bit of a tangent, but what's the play here? The library is worse than it could be because of this reason. And of course you have adoption and don't want to break people's shit.
Seems like we're missing out on "what could be."
If you do it this way it becomes harder for anyone to go wild with every variant of padding under 8/12.