Design·Design·August 1, 2024

The Buzz Around cn() Function and Why Do We Use It

Why the cn() utility has become the standard for handling Tailwind class merging and conditional classes in React component libraries.

I've noticed a lot of developers are using the cn() utility function when working with Tailwind CSS. In this post, I'll explain why this is becoming so popular and how it can help streamline your development process.

Let's start from scratch. Imagine you have a button component in your project, and you want to pass a class name to it just like you would with a native HTML element. For instance, your button has a default background color of bg-blue-500, but you want to override it with bg-green-500.

Typically, you would do something like this:

const Button = ({ className, ...props }) => {
  return <button className={`bg-blue-500 ${className}`} {...props} />;
};

However, this approach has a problem: conflicts between Tailwind classes are not predictable. For example, you don't know if bg-blue-500 or bg-green-500 will win when both are present.

This is where twMerge comes in. By using the twMerge function from the tailwind-merge package, you can ensure that the classes you pass as props override the default ones correctly. Here's how you can use it:

import { twMerge } from 'tailwind-merge';

const Button = ({ className, ...props }) => {
  return <button className={twMerge('bg-blue-500', className)} {...props} />;
};

With twMerge, you can be confident that bg-green-500 will override bg-blue-500.

Next, let's talk about conditional classes. If you have a pending state that changes the background color, you might want to use the object syntax for conditional classes. Unfortunately, twMerge doesn't support this out of the box. But there's another library called clsx that does.

clsx allows you to use object syntax for conditional classes. For instance, you can define classes conditionally based on the state like this:

import clsx from 'clsx';

const Button = ({ className, pending, ...props }) => {
  return (
    <button
      className={clsx('bg-blue-500', className, { 'bg-gray-500': pending })}
      {...props}
    />
  );
};

Here, if pending is true, bg-gray-500 will be applied. This object syntax is very convenient for handling conditional classes.

However, clsx alone does not handle conflicts between Tailwind classes. So, the solution is to combine clsx with twMerge, resulting in the cn() utility function. This function gives you the best of both worlds: intelligent merging of conflicting classes and support for object syntax.

Here's how to create and use the cn() function:

import clsx from 'clsx';
import { twMerge } from 'tailwind-merge';

const cn = (...inputs) => twMerge(clsx(inputs));

const Button = ({ className, pending, ...props }) => {
  return (
    <button
      className={cn('bg-blue-500', className, pending && 'bg-gray-500')}
      {...props}
    />
  );
};

With cn(), you can handle both conditional classes and conflicting classes easily. This utility function has become increasingly popular, especially in UI libraries like Shadcn UI.


I originally posted this on Reddit — you can find the discussion here: r/tailwindcss

I'm a design engineer and GenAI engineer focused on building polished component systems and production AI products. If you're working on a design system, a component library, or an LLM-powered product and want to talk — feel free to reach out.