TailwindCSS vs. UnoCSS

It’s great to hear that you are a long-time TailwindCS user and a huge fan of utility CSS in general. UnoCSS is another utility CSS framework that you have been considering. I’m glad you got around to using it properly and wrote up your thoughts on both in detail.

This article includes a lot of small nitpicky stuff that may not matter to others, but for me, the more I can reduce microfriction, the better.


Features

TailwindCSS has class names for pretty much every CSS feature you could think of, including some useful ones you may not know about, like isolation. With arbitrary values, variants, and properties, most apps can be styled head-to-toe without a custom CSS file or plugins.

UnoCSS supports all of TailwindCSS, plus some extras of the box that you really appreciate, like variant groups, fluid columns with CSS grid, and a lot more animations.

UnoCSS also has an opt-in “attributify” transform, but you personally prefer your class names to stay in a single attribute separate from props. It’s a neat idea though.

The Language

Tailwind has a reasonably well-defined language for class names:

There's no spec per se, but I've found it's a very "guessable" system. Based on how other class names work, you try stuff like grid-cols-[4rem,1fr,auto] and it Just Works™️.

By contrast, Uno's default preset is regex all the way down. There’s no real “language” per-se, and there’s no standardization, e.g. m4 and m-4 both do the same thing.

This is intentional on Uno's part; Uno is all about flexibility. But I still prefer Tailwind's guardrails and opinionated methodology.

This point is somewhat moot since one would really just use Tailwind's language with Uno. That, and Uno probably works better as a framework to implement your own language on top of it. Regardless, this is my evaluation if I'm looking at it and its default preset as a userland tool for styling apps.

Documentation

Both docs sites are beautiful, well-written, and highly usable. But I want to give a special shoutout to Uno's interactive docs, and the accent color shifting is brilliant. Might steal that 🤭

Custom Styles

Here’s an example of a custom plugin in Tailwind:

// adds s-* utilities to apply both width and height
plugin(function size(api) {
  api.matchUtilities(
    { s: (value) => ({ width: value, height: value }) },
    { values: api.theme("width") },
  )
}),

Here’s (almost) the same in Uno:

// `s-*` classes to set width and height
[
  /^s-(\d+)$/,
  ([, size]) => ({
    width: `${Number(size) / 4}rem`,
    height: `${Number(size) / 4}rem`,
  }),
  { autocomplete: "s-<num>" },
],

For both of these, the class s-4 will give the styles width: 1rem; height: 1rem.

Tailwind’s plugin API has gotten a lot nicer over the years. Simple utilities are easy to add, and even more complex ones like in this example aren’t that bad either.

Uno makes heavy use of regex for dynamic utilities, which feels error-prone. Uno encourages defining utilities that can take whatever value you give it, which reduces the need for the [] arbitrary value syntax. But I still prefer how Tailwind limits you to a specific set of values.

Aside: Tailwind’s arbitrary value syntax also lets you use any value. But the docs, plus the syntax required to use it, discourages them from being used. I like the clear, enforced sentinel of “this does not exist in the design system”.
By contrast, Uno more or less encourages users to use whatever value they want. Although you technically could build a more constrained design system within Uno, that's more leaps than what you get with Tailwind.

Editor Support

Tailwind’s editor support works pretty well, but has some gaps:

  • No awareness of custom classes in CSS files
  • Does not autocomplete class names when using @apply in plugins
  • You need to configure an “experimental” option with custom regex to get completion contexts elsewhere, and you need to dig through issues and discussions to find the recipe you want. Which still may not work in all cases, because... y'know, Regex 🫠

These issues make it tedious to reuse styles, via @apply or with class name strings in JS.

Tailwind authors recommend against using @apply altogether, but I still find it useful for small atomic elements, like buttons, inputs, and links.

Uno highlights class names and gives color hints everywhere, which is nice if you’re sharing class names in standalone strings, but it is funny to see it highlight “transition” in const transition = useTransition().

By extension, Uno's editor support also works in uno.config.ts, which is very nice for adding custom reused class names.

Caveat: you have to add // @unocss-include at the top of the config file to get it to complete class names in it. This works fine with Remix and PostCSS, but it may break in other setups. TBH the plugin should support this OOTB

However, Uno’s autocomplete is finicky in some ways:

  • Often you don’t get options for autocomplete until you type a full utility and -
  • Shortcuts don’t always get autocompleted, and it’s not really clear why
  • Autocomplete inside a variant group e.g. hover:(|) doesn’t work unless you put a space in it, like hover:(| )

That aside, I've found Uno's overall editor experience more frictionless than with tailwind.


Tailwind and Uno have their strengths and weaknesses. If you value flexibility and extra features, you’ll probably like UnoCSS. UnoCSS also has an overall nicer editor experience as of writing, but maybe that’ll change! It’s always good to keep an eye on both of them. 👀

Honorable mentions:

  • MasterCSS: Didn't really draw me in. Doesn't seem to have a big focus on constraints at all, and its language doesn't feel very ergonomic
  • Twind: The editor plugin still isn't updated to work with the latest version
  • TypeWind (and similar): I greatly appreciate the effort to have good DX without an extra editor plugin, but TS-based class names are very unergonomic 😅

No comments:

Post a Comment