You Must Use twMerge + clsx as a Tailwind developer
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
andclsx
functions, inside theclsx
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
Subscribe to my newsletter
Read articles from Rahim directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by