Introduction
If you browse through Shadcn component set, you’ll find extensive use of a helper function cn
as follows:
import { type ClassValue, clsx } from 'clsx'import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs))}
It is a combination of two libraries (tailwind-merge, clsx) that make it easier to build Tailwind styles in components. Why is there a need for additional libraries and methods to construct component styles?
Tailwind Merge
When using Tailwind with component-based UI renderers (like React or Vue), you may encounter the problem of styles not overriding each other. Regardless of how the final style strings are combined, Tailwind outputs all possible classes as a static CSS during the utility class generation process, where the order of definitions matters for style specificity.
function MyGenericInput(props) { const className = `px-2 py-1 ${props.className || ''}` return <input {...props} className={className} />}
function MyOneOffInput(props) { return ( <MyGenericInput {...props} className="p-3" // ← Trying to modify padding style but not taking effect /> )}
There are two possible ways to solve this problem:
Both methods have their own issues, which is why Tailwind Merge is needed:
twMerge('p-5 p-2 p-4') // → 'p-4'
tailwind-merge relies on a ~7 kB
configuration file to determine how to merge Tailwind-related style strings; specific merging rules can be found in the merging rules documentation.
clsx
When using Tailwind with component-based UI renderers (like React or Vue), you often need to dynamically switch CSS classes based on conditions, and the native way of doing it can quickly become long and hard to read:
<div className={ {/* Note that the spaces between classes need to be combined correctly */} "p-2 " + (isActive ? "bg-blue-500" : "bg-gray-300") + (isDisabled ? " opacity-50" : "") }/>
clsx not only simplifies the style development process but also prevents erroneous falsy
states from being combined:
<div className={clsx("p-2", { "bg-blue-500": isActive, "opacity-50": isDisabled, })}/>
The rules can be found in the official Usage documentation:
// Strings (variadic)clsx('foo', true && 'bar', 'baz');//=> 'foo bar baz'
// Objectsclsx({ foo:true, bar:false, baz:isTrue() });//=> 'foo baz'
// Objects (variadic)clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });//=> 'foo --foobar'
// Arraysclsx(['foo', 0, false, 'bar']);//=> 'foo bar'
// Arrays (variadic)clsx(['foo'], ['', 0, false, 'bar'], [['baz', [['hello'], 'there']]]);//=> 'foo bar baz hello there'
// Kitchen sink (with nesting)clsx('foo', [1 && 'bar', { baz:false, bat:null }, ['hello', ['world']]], 'cya');//=> 'foo bar hello world cya'
Conclusion
Although these packages focus on very simple issues, they significantly enhance the development experience in large UI projects. Finally, I have many questions regarding these abbreviations, and I roughly guessed their meanings after writing this article:
- twMerge = Tailwind Merge
- clsx = Class Mix
- cn = Class Name
Further Reading
- cn() - Every Tailwind Coder Needs It (clsx + twMerge) - Byte Grad
- What is it for - tailwind-merge GitHub
- 01__clsx__function__main__.pdf - clsx-Documentation