Using a utility first CSS framework: First impressions of Tailwind.

I recently speculated that the more abstract CSS frameworks would probably offer a better framework experience, than their moderately abstract counterparts. Tailwind is the standout option when it comes to abstract frameworks; I recently had a chance to give it a try.

I was apprehensive about the idea of creating a mess of classes in my HTML. I suspect most people feel the same. I tried to keep this in perspective however, as I know this is in part an aesthetic concern.. It did feel pretty horrible though.

So why consider the utility first approach, what was the appeal of Tailwind? The biggest part of that question is “why use a framework?”. For this project it was desirable to be able to extend the product, without writing CSS. I would say this is essentially the appeal of any CSS framework - to be able to make stuff with minimal understanding of CSS. The problem with traditional CSS frameworks is that they don’t get you very far before that dream starts to fade away. The advantage Tailwind has here, is that the atomic nature of the classes gives you much more power to create something bespoke. In my experience, Tailwind was pretty successful in this regard. This was the first time I felt that I was able to realise a design, while working more or less in the spirit of a framework.

One of the practical concerns I had about the Tailwind approach, was having to change classes in multiple places when making a global change. I found that even before getting to that point, a related issue arose. Just applying those classes consistently in the first place, and keeping track of them is a considerable burden. Tailwind gives you classes for various amounts of spacing, for example. This is integral to the power to create bespoke designs, but of course with that power comes the responsibility to create consistency yourself. To make this process easier, I ended up creating my own less abstract spacing classes. In addition to (or potentially in place of) m-1 m-2 etc, I added m-gutter (’m’ standing for margin here). Tailwind makes this easy to do, via its config file.

There’s a balance to be found here. These classes that control things like spacing and font size, need to encompass everything you need for your project, while not being so many in number that the framework dissolves away. Despite the granular approach of Tailwind, I think its most valuable aspect is these variable-like classes, which offer some degree of commonality. Without this element of opinion, the framework would just be a proxy for CSS. This is intact the case for a large chunk of Tailwind. For example, you use the block class, as a proxy for writing display: block in CSS, and the absolute class for position: absolute. This is required to serve that CSS averse user I previously identified. To someone without an aversion to CSS however, writing this pseudo CSS can feel perverse.

You could make an argument that learning a new (pseudo) language, to do what you can do with the standard language of CSS, is not the best use of time. If you know CSS, the Tailwind classes will be quite intuitive; if you don’t know CSS, you will spend a lot of time learning Tailwind. That time might be better spent learning CSS?

While Tailwind acts as a proxy for CSS, the full language is not fully represented. There is no CSS grid, no concept of siblings, no pseudo-elements, no transitions.. So you are probably going to find yourself back in a stylesheet at some point. Perhaps this is inevitable with any CSS framework. The question then is of how the relationship between utility classes, and custom CSS is managed. Tailwind has a useful feature which allows you to use its class names inside your CSS, like so:

.my-selector {
  @apply font-bold py-2 px-4 rounded;
}

This is a nice way to make use of those variable-like classes; it mitigates the feeling of having abandoned the framework. This does raise questions though. Because having a mixture of component and utility classes could be confusing, the temptation might be to extract everything into components. Having done so, you might want to exclude the Tailwind classes (to reduce file size). At which point, you might as well have not used the framework to begin with. This would probably not be in the spirit of Tailwind though. You are more likely going to end up with a mix of utility and component classes. For me, this is not ideal, it adds some doubt about where styling is coming from. When you do need to leverage the power of real CSS, reasoning about what to extract and what to keep inline I found not to be a trivial task. Once you start writing real CSS for a component, do you then put all the styles for that component in the CSS, or keep as much as you can in the template? The @apply feature in a sense allows you to customise the level of abstraction. This could be considered problematic, as once you start using it, you are then wrestling with question of “how abstract do I want this to be?”.

My biggest concerns going into this was the inability to style a component differently in different contexts, and the complexity involved in redesigning. Nothing has made me feel any better about this while using Tailwind.

Despite the issues I have pointed out here, Tailwind felt like a framework of more value than others I have used. As someone happy to write CSS, I am never going to be the primary beneficiary of a CSS framework. The real test will be how the CSS averse find it.