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.

More Posts

DRY Principle in Software Design photo

DRY Principle in Software Design

Don’t Repeat Yourself: Writing reusable and maintainable code

KISS Principle in Software Design photo

KISS Principle in Software Design

Keep It Simple, Stupid: How simplicity leads to better code

Basic principles of OOP photo

Basic principles of OOP

An introduction to the fundamental concepts of Object-Oriented Programming (OOP), including encapsulation, inheritance, polymorphism, and abstraction.