Why Does Shadcn use cn() Helper Functions?

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:

  1. Adding props that toggle internal styles🔗
  2. Using Tailwind’s important modifier🔗

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'
// Objects
clsx({ foo:true, bar:false, baz:isTrue() });
//=> 'foo baz'
// Objects (variadic)
clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });
//=> 'foo --foobar'
// Arrays
clsx(['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