Tailwind CSS defines itself on its official site as:
A utility-first CSS framework packed with classes.
In this post I'll explain to you what does utility-first mean and how Tailwind CSS works under the hood.
Utility-first
Utility classes are easily understood, single purpose classes. For example, Tailwind provides utility classes such as:
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
So when you want to horizontally center a paragraph with small font size, you can just use mx-auto text-sm
as the class name of the element.
In this way, we can build complex components by combining different utility classes together.
3 main benefits of utility-first
Now we understand what is utility-first and let’s see the benefits of it:
1. Design with constraints
Using inline styles, every value is an arbitrary number. With utilities, you’re choosing styles from a predefined design system such as the default spacing scale:
Take setting width for an example, you just use utility class such as w-48
or w-96
, instead of specify the exact pixel you want. This makes it much easier to build visually consistent UIs.
2. CSS stops growing
Using a traditional approach, your CSS files get bigger every time you add a new UI component. With utilities, everything is reusable. This means the size of your CSS file stops growing after a certain point because the same utility classes are used again and again.
3. No need to invent class names
With traditional CSS, you might be adding class names like sidebar-inner-wrapper
just to be able to style a <div>
. With utilities, you just composing them to build a complex style you want.
How Tailwind CSS works under the hood
Tailwind CSS works by scanning all of files for class names at build time, then generating all of the corresponding CSS for those styles.
The paths to all of your content files is specified by content
section of your tailwind.config.js
:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./pages/**/*.{html,js}", "./components/**/*.{html,js}"],
// ...
};
Tailwind use regular expression to detect class names. Therefore when you want to use utility class conditionally like the following syntax doesn’t work:
<div className={`text-${error ? "red" : "green"}-600`}>Lorem Ipsum</div>
This is because the regular expression doesn’t match any class name.
Instead, you should use:
<div className={error ? "text-red-600" : "text-green-600"}>Lorem Ipsum</div>
In the second approach, Tailwind can match the string 'text-red-600'
and 'text-green-600'
and then generates the corresponding CSS.
This also explains why you can't use props to build class names. For example, this won't work:
function Button({ color, children }) {
return (
<button className={`bg-${color}-600 hover:bg-${color}-500 ...`}>
{children}
</button>
);
}
Instead, map props to complete class names that are statically detectable at build-time:
function Button({ color, children }) {
const colorVariants = {
blue: "bg-blue-600 hover:bg-blue-500",
red: "bg-red-600 hover:bg-red-500",
};
return <button className={`${colorVariants[color]} ...`}>{children}</button>;
}
Wrap up
To sum up, we've learned that Tailwind CSS makes it easy to design neat and consistent websites. Knowing how it works also helps us understand why some things work well and others don't, guiding us to use Tailwind more effectively!
Top comments (22)
Thank you for your excellent article 👍. I stopped using it at some point due to the poor code readability. In reality, in large projects, it often looks like this:
<a class="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"></a>
There's a good reason why solutions like this now exist, offering readable classes for use: daisyui.com
I agree that it becomes a bit cluttered looking as a big long string, but I stopped inlining classnames in JSX a while back and it pretty much resolved the problem. My usual component structure is something like:
so that the component file is something like:
and the utils file has:
This is exactly what PandaCSS already does.
I think it's a pretty common pattern. I made a package (tailwind-classlist) back in 2018ish that did something similar, although it's no longer maintained. Handled merging conflicting classes and everything as well.
Nice!
NYC bro
If you do not like how all classes are smashed together maybe spend some time grouping them together ?
Here is an example from one of the more complex styled component taken from shadcn Dialog:
I find the code above pretty readable, easy to summarize and skim through.
Yes, its a little bit of extra work to write it like this.
Yes, you have to create separate components for each used div -> a good practice in React/Svelte/Solid/Vue anyway.
I find something intresting. I'm not sure about the date, but it was 2022 I think that TailwindCSS and Prettier (a VScode add-on) featured with a very good updates. While you're doing refactoring the structure of your code with this add-on, it will sort all of your classes. Check it out in the end of the first page of tailwindcss.com
Enjoy it!
When I spend more time making something disorganized organized, it's often better to do it myself. I'm taking a more pragmatic approach: native CSS is now really good. To make something clear and maintainable, I don't need a framework, just a good method to do it.
I know of it, I tried it.
I did not like the auto ordering of the tailwind classes.
If they are components that I use everywhere (like buttons) I want to make them as readable as possible.
The approach in my code example solved that goal in the best possible way known to me.
Like it a log. classMerge is amazing. Thanks for sharing :)
I like Tailwind because there are so many libs that build up on it, so you can use a solid set of components that were built with Tailwind, e.g. HeadlessUI or TailwindUI, so you can get the groundwork done fast and then customize them the way you want, while still keeping the option to fully change components with the ease of use of the Tailwind framework.
Headless UI doesn't even have 1% of the components that other component libs offer and tailwind ui is a paid solution.
That's true, and I don't mind paying for components if they are well-made and maintained. TailwindUI is a great team with nice support and I can use their components to bring my projects up to speed in almost no time :)
I'm not a fan of paying for things that should be open source.
daysyui -> is horrible in accessability. Its okay to use it for purely ui/graphical/design stuff but not for interactive components.
The web is pretty much all about accessibility, so ignoring it defeats the purpose to do web development in the first place.
If you want to do accessibility right in React then one of the 2 libraries is the way to go
Svelte/Sollid/Vue have their own similar specialized component libraries
I cannot assess the quality of DaisyUI in terms of accessibility, but my point was that Tailwind is very cluttered in the DOM for my taste. Perhaps it is also important to say from which perspective I decide whether Tailwind is suitable for me. I build less in the way of applications and more websites. For me, it was a nightmare to write pseudo-elements, states, and simple appearances in Tailwind. I find this comparison here by DaisyUI very apt 😂.
I would disagree on the "websites are more complex than web app in terms of layout" claim
Proof
twitter.com/masumparvej_/status/17...
-> Try doing something like this to look good and responsive on all screen devices
True!
I personally love NextUI. It works well with Tailwind and also has good accessibility (built on top of React Aria).
I like to use tw fare better than using any other CSS solution because the layout related solution so simple like
grid gap-2
for vertical list. Also can solve dark/light skin change and responsive design at zero cost.Tailwind likes to pretend it's the only option with those benefits, but that's not really (really not) true. PandaCSS for example has the same benefits, but none of the downsides of having to learn a new utility class language.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.