TWMerge + ClassNames

Are you creating a new website with Tailwind as your styling framework? Then you should start using the following code snippet instead of classnames when merging classes in Tailwind. ‍



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

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

Merging class names in a good way

Many people who create new website use Tailwind as a framework for styling. One of the challenges everyone who has written Tailwind has experienced is having to dynamically merge classes. Por ejemplo:



export function MyComponent{}{
   const [isOpen, setIsOpen] = useState(false)

	return (
		
{/* ... */}
) }

Het. O quando i classe per usare condizionali più complexe:


export function MyComponent(){
   const [isOpen, setIsOpen] = useState(false)
   const [highlightText, setHighlightText] = useState(false)

	return (
		
{/* ... */}
) }

E pode una desidera recuperar nombres de classe da un componento overlying?



export function MyComponent(classNames){
   const [isOpen, setIsOpen] = useState(false)
   const [highlightText, setHighlightText] = useState(false)

	return (
		
{/* ... */}
) }

As you can see, blank spaces have been added after “p-4", “block” duck “hidden”. This is to make sure that the resulting class is something like: bg-white p-4 block text-yellow-500 leading-5 and not bg-white p-4blocktext-yellow-500.

As the usage becomes more complex, this can be difficult to keep track of. Especially if you have very long class names, which is common with Tailwind.

One solution to this is classnames libreria. Or its lightweight little brother CLSX. Let's start with clsx for the rest of the article.

We can rewrite the above code in two different ways with our new tool:



import clsx from 'clsx'

export function MyComponent(classNames){
   const [isOpen, setIsOpen] = useState(false)
   const [highlightText, setHighlightText] = useState(false)

	return (
		
	)
}



import clsx from 'clsx'

export function MyComponent(classNames){
   const [isOpen, setIsOpen] = useState(false)
   const [highlightText, setHighlightText] = useState(false)

	return (
		
	)
}

clsx (and classnames) automáticamente fixe espaciones para os, y es muy flexibel no entrada em enviar. For example, there is no problem if “ClassNames” is undefined.

So then it's all good, right?

Problema con conflating classe nombres

A challenge arises when we try to merge classes that give similar instructions:



import clsx from 'clsx'

export function MyComponent(classNames){
   const [isOpen, setIsOpen] = useState(false)
   const [highlightText, setHighlightText] = useState(false)

	return (
		
	)
}

export function ParentComponent(){
  return (
    
  )
}

What do you expect the padding on MyComponents its divs? If you're like me, you think that, well, the resulting class name in MyComponents Staying bg-white p-4 hidden text-black p-6, and then the padding gets in P-6 (padding: 1.5rem;) applied, yes?

The No. The way HTML and CSS will work P-4 be the dominant padding, and our overwriting of the default value won't work.

Obvious solutions to this problem quickly fall short when one has to deal with more complicated cases such as p-4 px-2 pb-3 p-5 py-3 .

Fortunately, there is a library for this: tailwind-go. Tailwind-merge is a bit similar to ClassNames and clsx in the way it takes input:



twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'

Het med tailwind-merge, på andra håndtak, er det er ikke på vad det fra input, og for example kan ikke handle javascript objecten som vi:



twMerge("bg-white p-4 hidden text-black", 
	{
	  "block": isOpen,
    "text-yellow-500 leading-5": highlightText
  },
	classNames
)
// → 💥

Soluzione

So what is the solution to both of our problems? Yes, to merge TWMerge duck CLSX:



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

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

Now we change our component to:



import cn from '~/utils/cn'

export function MyComponent(classNames){
   const [isOpen, setIsOpen] = useState(false)
   const [highlightText, setHighlightText] = useState(false)

	return (
		
	)
}

export function ParentComponent(){
  return (
    
  )
}

Are we on target. The component is displayed with P-6 some leading padding, and all class names are merged appropriately.

Inspiration retrieved aqui duck aqui.

Skrevet av
Tormod Haugland

Andre artikler