EntriesAbout

Tailwind “Versus” CSS

or, Yet Another False Dichotomy

When I first came across the idea of “utility-class” CSS “frameworks” (the first one I recall hearing about was called “Tachyon”), I didn’t think much of them.

“I already know how to write CSS”, I thought. “Why would I bother with this ‘Mickey-Mouse’ version?” and then dismissed the notion.

I’ve since come around on the idea though, to the extent that I’m now the author of a Prolog Tailwind library and (as of this writing) the second most prolific contributor1 to Girouette, a Clojure library implementing the same. Whence this change in attitude? I shall endeavour to explain.


Much of my work over the past several years has involved building fairly large Clojure(Script) web applications, mostly using reagent. Previously we’d use something based on Clojure’s garden library that outputs plain old CSS in order to style them. This worked fine – both my co-founder and I are very comfortable writing lots of CSS – but we started to run into issues as things got larger and more complex.

Essentially, in order to avoid unexpected styling issues when nesting lots of little components, we’d have to be quite strict about writing our CSS to pretty much exclusively use direct-child selectors (thereby removing the “Cascading” part of CSS). It wasn’t too bad to actually write with garden’s syntax2, but finding the correct place to make changes could get fairly annoying. We’d have to be very disciplined about breaking up our style definitions in order to have any hope of finding the correct spot to update and even then, as components get larger, the CSS would get increasingly nested.

This particular idiom, while necessary to avoid styles “leaking” to other components, also means that the styles are extremely tightly coupled to the corresponding components. With all these direct-child selectors, adding so much as a wrapper div makes styles no longer apply; plus, it forces you to either keep coming up with names for these interstitial div​s or have these fairly gross bare elements in the chain of selectors.

So, what to do? One approach is to move to inline styles – i.e. just setting the style attribute of the relevant elements in each component. While that would be pretty gross in straight HTML (since you’re writing it all in a string), with reagent it’s not too bad. You can pass a proper map in for the style and thus you can factor things out to variables for common constructs if needed3.

The big deficiency of inline styles is that you lose the ability to target pseudo selectors like :before, :after, :hover and the like. How to address that? Well, it turns out that this is exactly what the Tailwind style is good for. You can think of the classes the library provides as a convenient short-hand for inline styles, but can actually target pseudo-selectors (e.g. the class hover:text-red applies color: red in the :hover state).

We now have the ability to style our components independently of their surroundings or their children, so we don’t worry about cascade breaking things anymore. Things can get verbose, if you have a lot of classes that you commonly reuse, but since the components are written in ClojureScript (i.e. not HTML), it’s easy enough to define some constants to hold “standard” classes for particular cases. Since Girouette and my own library generate the CSS on demand based on the referenced classes, the stylesheet doesn’t get unreasonably giant, plus you have the ability to set arbitrary properties (e.g. bg-rgb-XXXXXX to set the background colour to some arbitrary hex colour, left-13.5px to set some random exact left value)4.

There are, of course, some downsides.

For one, looking at the DOM in the browser, it can be hard to tell at a glance which component you’re looking at, since you don’t necessarily put semantic class names on things anymore. That is, of course, easily fixed by…adding semantic class names to components, making sure they don’t conflict with some other meaningful class name. One can run into CSS attributes that aren’t supported by the particular library; sometimes I might add support to the library, but most often I’d just manually define my own CSS style with a similar naming convention that does what I need. In some cases I might even supplement with “normal” CSS, if for some reason it’s easier than having the logic in dynamically created components to add their own styles5.

All in all though, I’m pretty happy with this solution – when I’m writing websites with content generated in this particular way. If I’m making a simple, mostly static site, I probably wouldn’t bother (although I might sometimes, just because I have nice workflows set up for making sites that already have the requisite tools wired up).

What I don’t understand is the attitude of “Tailwind versus CSS” or “don’t use Tailwind, just learn CSS” that I see sometimes. It simply doesn’t make sense – to me, Tailwind (-style libraries (since again, I don’t actually use the eponymous tool)) is just a way of writing CSS. True, it’s a way that mostly ignores the “cascading” part, but in practice many ways of writing vanilla CSS do as well. Maybe it’s because I wrote my own implementation, but I’ve never felt like writing these classes is different in any way from writing CSS – they seem “transparent” to me.

I don’t think that Tailwind is “necessary” to make websites – certainly not. But, if you have these particular pain-points of keeping markup in sync with style, deep component trees, are already avoiding cascading, then maybe give it a try!

Footnotes:

1
girouette_contributors.png
2

e.g. we can write

(def some-style
   [:.foo
     {:background-color "red"}
    [:>.baz
      {:color "blue"}
     [:>.quux
      {:font-size "2rem"}]]])

instead of the more laborious equivalent CSS:

.foo {
    background-color: red;
}
.foo > .baz {
    color: blue;
}

.foo > .baz > .quux {
    font-size: 2rem;
}
3

e.g.

(def theme-colour "red")

(defn some-view
  []
  [:div {:style {:background-color theme-colour
                 :font-size (str js/Math.PI "em")}}
   "Hello world!"])
4

At this point I have to confess I’ve never actually used Tailwind itself, since I don’t really write Javascript, so I can’t speak to the issues of that particular implementation, but I believe it also does something equivalent now.

5

e.g. if I want to add some moderately complex style to all the children of a particular component, it might be easier to define that in CSS like .whatever-parent > * { ... } rather than adding whatever that ... is to every one of the potentially heterogeneous children.