Photo by Jhunelle Francis Sardido
Tailwind CSS Utilities and Best Practices
Using Tailwind CSS is great, but understanding it deeply goes beyond knowing that flex equals display: flex;. It’s about knowing how class utilities interact, how to prevent conflicts, and how to structure conditional styling cleanly and effectively depending on the context of your project.
Assuming you’re already familiar with CSS specificity, let’s go straight to the point.
1. clsx
clsx is a utility that helps you apply conditional classes based on logic in your components. This makes your class management much cleaner, especially when working with multiple states like isActive or isError.
Example
import clsx from "clsx";
const Button = ({ isActive, isError }) => {
return (
<button
className={clsx(
"px-4 py-2 font-bold",
isActive && "bg-blue-500 text-white",
isError && "border border-red-500"
)}
>
Click me
</button>
);
};
Here, we’re using clsx to add Tailwind classes based on props like isActive or isError. Instead of messing with string concatenation or ternaries inside the className, clsx lets us write cleaner code that only adds the classes we actually need.
2. tailwind-merge
When you’re using Tailwind, it’s easy to accidentally apply conflicting classes, like multiple bg-* values on the same element. By default, that can lead to unexpected styles depending on the order.
tailwind-merge solves this by automatically resolving those conflicts for you. It works kind of like CSS’s cascade: if two classes clash, it keeps the last one.
Example
import { twMerge } from "tailwind-merge";
const className = twMerge("bg-red-500 bg-blue-500 p-4");
// Result: "bg-blue-500 p-4"
In this example, twMerge removes the earlier bg-red-500 and keeps bg-blue-500, giving you the expected result without having to manually manage class order. It’s especially useful when you’re composing class names dynamically and want predictable styling.
3. cn
A common pattern in Tailwind projects is to combine clsx and tailwind-merge into a single utility function called cn. This way, you get both conditional logic and automatic class conflict resolution in one place.
It’s super handy when building components that need dynamic styles, and it keeps your className logic clean, even as things get more complex.
Installation
yarn add clsx tailwind-merge --exact
Utility function (TypeScript)
// utils/cn.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
/**
* Combines Tailwind CSS classes using clsx and resolves conflicts with tailwind-merge.
*
* @param inputs - Any number of class names or conditional class expressions
* @returns A single string of valid Tailwind classes
*/
export const cn = (...inputs: ClassValue[]) => {
return twMerge(clsx(...inputs));
};
Understanding how things work under the hood is satisfying, knowing there are tools that can take that to the next level makes the experience even better.