TailwindCSS vs. UnoCSS
Tags: tailwindcss, css, unocss, web
Published: 2023-05-01 06:36:04

---
title: TailwindCSS vs. UnoCSS
published: true
description: A deep, nitpicky comparison of two awesome utility CSS frameworks
tags: tailwind, css, unocss, web
cover_image: https://mapleleaf.dev/images/tailwind-v-uno.png?1
# Use a ratio of 100:42 for best results.
# published_at: 2023-05-01 05:39 +0000
---

I’m a long time [TailwindCSS](https://tailwindcss.com) user and a huge fan of utility CSS in general. I’ve rarely felt a strong need to switch, but [UnoCSS](https://unocss.dev/) has been on my mind for a bit. I got around to using it properly, so I thought it’d be fun to write up my 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

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

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

Uno also has an opt-in ["attributify" transform](https://unocss.dev/presets/attributify), but I personally prefer my 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:

- Every style is a kebab-cased name, e.g. `bg-blue-500`, `p-4`
- Classes can be prefixed with a variant, like `md:`, `hover:`
- Arbitrary stuff is in `[brackets]` , with [well-documented differences depending on the context](https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values)

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.](https://unocss.dev/guide/why) 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:

```jsx
// 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:

```jsx
// `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
  - It'll autocomplete classes in CSS with `@apply`, but [it won't autocomplete custom classes from CSS in HTML](https://github.com/tailwindlabs/tailwindcss-intellisense/issues/227)
- 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. I highly appreciate Tailwind’s constraints and clearer authoring language, but if you value flexibility and extra features, you’ll probably like Uno. Uno also has an overall nicer editor experience as of writing, but maybe that’ll change! I’ll be watching both of them closely. 👀

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](https://github.com/tw-in-js/vscode-twind-intellisense/issues/23)
- 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 😅