I’m a long time TailwindCSS user and a huge fan of utility CSS in general. I’ve rarely felt a strong need to switch, but UnoCSS 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, 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
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
- It'll autocomplete classes in CSS with
@apply
, but it won't autocomplete custom classes from CSS in HTML
- It'll autocomplete classes in CSS with
- 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, likehover:(| )
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
- 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 😅
Top comments (14)
Great stuff
Thanks for article
Love the article Maple, great work!
I've been looking the key differences between UnoCSS and Tailwind for some time and reading this helped me get it. Thanks for sharing this article Maple.
I like the tailwind for the nice colour choices and fan of the Refactoring UI book. But not very comfortable with using the utility classes as it doesn't bring advantage when we have to change style if we change the utility class name in all html. I prefer component style classes as we could change the styles in single place instead of going and changing the utility class in all html to apply a different style. It is just my opinion.
Learnt new stuff in this article, thank you for writing this!
On a side note though, my company project has had no issues trying to autocomplete custom classes that we add into the tailwind config within Vue's HTML template though. Perhaps it's powered by VSCode TailwindCSS extension?
I just add a link here to Stylify CSS for those that may be looking for an alternative to both of these with smaller learning curve and CSS-like selectors 😅🤟
In the same genre as the 2, there is modcss. unlike tailwind and unoCss, it only uses arbitrary values on its properties which is very convenient.
it also has a fully parametric grid system with n columns.
Le site
Le github
Good article 👍
Also from my side, thank you for that effort
So the main difference between Tailwind and Uno is extensibility. Hmm?
Could you shed some more light on internal working engines of both?
Thanks for sharing, learned something new.. first time knowing about UnoCss
Thanks
Thanks a lot for opening us to UnoCSS. Will surely give this a try now over tailwindcss.