You Must Use twMerge + clsx as a Tailwind developer

RahimRahim
5 min read

What problem do twMerge and clsx solve?

As a tailwind user, you may want to add classes conditionally, or you may want to merge some classes without running into conflicts, Let's see the following example:

import { ButtonHTMLAttributes } from "react"

export const PrimaryBtn : React.FC<ButtonHTMLAttributes<HTMLButtonElement>> = ({children , className  , ...props })=>{
    return  <button className={"h-fit   bg-white border-2 border-primary hover:text-white hover:bg-primary      hover:outline-primary   text-primary  shadow-md text-lg rounded-3xl font-bold px-4 py-2"  + className } {...props} >{children}</button>
}

In the code above, we wanted to build a button, and we wanted to receive a className for this button and merge it with pre-defined classes , The code above represents how you would typically do this process without using twMerge and clsx,

The problem with the code above is that we may run into conflicts, for example, in our button element we have a pre-defined class of bg-white what is we pass a className in the PrimaryBtn props of <PrimaryBtn className="bg-red-400" /> In this case, we will have two classes of bg-white and bg-red-400 and we don't know which class will be applied to our btn

We want to tell our app to apply the last class if we have a conflict, in our example if we pass a bg-red-400 as a prop, we want this class to apply because the className prop comes after the pre-defined classes , to do that we have to use twMerge

How to use twMerge

Installing tailwind-merge dependency:

Run the following command to install tailwind-merge :

npm i tailwind-merge

After installing the dependency, you just have to import the twMerge function and invoke that function with all the classes that you want to merge:

import { twMerge } from 'tailwind-merge'
import { ButtonHTMLAttributes } from "react"

export const PrimaryBtn : React.FC<ButtonHTMLAttributes<HTMLButtonElement>> = ({children , className  , ...props })=>{
    return  <button className={twMerge("h-fit   bg-white border-2 border-primary hover:text-white hover:bg-primary      hover:outline-primary   text-primary  shadow-md text-lg rounded-3xl font-bold px-4 py-2"  + className ) } {...props} >{children}</button>
}

Now we are using the twMerge function to merge the pre-defined classes with the classes that we are getting from the props, and the twMerge will prioritize the classes that come after when there is a conflict, so now if we got the class of bg-red-400 from the props, twMerge will prioritize it because it came after the bg-whie and it will delete the bg-whtie class, so now we can easily merge classes without running into conflicts and this is the power of twMerge

Rendering Conditional Classes

If we want to use conditional classes we have to use the && operator, here is how we can do that with twMerge :

export default function Post (){
    const [pending, setpending] = useState(true)
    return <button className={twMerge(" border-2 px-4  border-black rounded-sm text-lg py-1 " , pending && "bg-white" , !pending && "bg-black"  )}  >Submit </button>
}

we are using pending && "bg-white" and !pending && "bg-black" to put a white background if the pending is true, and a black background if the pending is false, We are using twMerge to merge these classes, although you can do that, It is not recommended to render conditional classes that way since the code will be messier and you will need to type the && operator every time you want to render a conditional class

Using clsx to render conditional classes :

clsx gives you the ability to render conditional classes in a clean way,

  • Installing clsx : Run the following command :

      npm i clsx
    
  • After installing the dependency, you can render conditional classes in an object like this :

      export default function Post (){
          const [pending, setpending] = useState(true)
          return <button className={twMerge(clsx(" border-2 px-4  border-black rounded-sm text-lg py-1 " , {"bg-white" : pending , "bg-black": !pending  } ) )}  >Submit </button>
      }
    

    First, we are wrapping all the classes inside the twMerge and clsx functions, inside the clsx function we are rendering classes conditionally in an object: {"bg-white" : pending , "bg-black": !pending } , the keys of the object are classes and the values of the object are booleans, If the value of a class is true, the clsx function will return that class so the class will be applied, if the value of the class is false, clsx will not return that class,

    We are using the twMerge to ensure that we don't run into conflicts

Building the cn() function

It is a bad idea to wrap your classes in twMerge(clsx()) each time you want to use a class or you want to merge classes because it makes your code messier and you have to import the clsx and the the tailwind-merge dependencies every time you want to merge or render conditional classes, you can overcome this by building a unit function called cn() :

import  {ClassValue, clsx} from 'clsx'
import { twMerge } from 'tailwind-merge'



export function cn(...inputs : ClassValue[]) {
    return twMerge(clsx(inputs))
}

The above cn() function accepts a bunch of inputs, We use the Rest parameters to accept all the classes in an array, and then we pass this array to the twMerge and clsx , in this way, we can invoke the cn() function whenever we want to render conditional classes or we want to merge classes,

export default function Post (){
    const [pending, setpending] = useState(true)
    return <button className={cn(" border-2 px-4  border-black rounded-sm text-lg py-1 " , {"bg-white" : pending , "bg-black": !pending  } )}  >Submit </button>
}

The above code shows how the cn() function made our code cleaner

Conclusion :

It is always a good idea to use tools such as tailwind-merge and clsx , they will enhance your development experience , clean up your code and save you so much time and effort

0
Subscribe to my newsletter

Read articles from Rahim directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rahim
Rahim