
Info
In this article, I will use tailwindcss to style a ReactJS application.
# The Problem
When writing CSS class names in React's className, we often find that we want to merge the names of two identical CSS classes. For instance, consider a component like this:
type Props = React.HTMLAttributes<HTMLDivElement> export function Maxwidthdiv({ className, children, ...props }: Props) { return ( <div className={`mx-auto w-full max-w-[1440px] px-8 sm:px-10 md:px-12 ${className}`} {...props} > {children} </div> ); }tsx
We want to use Maxwidthdiv flexibly, so we need to use the className prop. However, if we write className as above, there's a chance the classes we write won't merge well. For example, if the Maxwidthdiv component is called like this:
export default function App() { return ( <Maxwidthdiv className="text-primary font-bold px-4"> <p>Something</p> </Maxwidthdiv> ) }tsx
The App component uses Maxwidthdiv with the class px-4, while inside Maxwidthdiv, there's also px-8. This can cause inconsistent styling results, sometimes resulting in px-4, and other times px-8, depending on how Tailwind processes the sorting.
# Solution: twMerge

Github tailwind-merge
To merge classes in TailwindCSS, we can use the twMerge function from the tailwind-merge package. First, we need to install tailwind-merge:
npm install tailwind-mergebash
Then, we can use twMerge like this:
import { twMerge } from 'tailwind-merge' type Props = React.HTMLAttributes<HTMLDivElement> export function Maxwidthdiv({ className, children, ...props }: Props) { return ( <div className={twMerge('mx-auto w-full max-w-[1440px] px-8 sm:px-10 md:px-12', className)} {...props} > {children} </div> ); }tsx
With the updated Maxwidthdiv component, if it's called in the App component as before, there will be no ambiguity in the results. The final outcome will definitely use px-4 in the App.
# Handling Complex Combinations with clsx

Github clsx
If our class notation is complex and full of conditional statements, it would be better to use clsx. We can install it with:
npm install clsxbash
Based on the clsx documentation, we can perform several complex combinations like this:
import clsx from 'clsx'; // or import { clsx } from 'clsx'; // 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'ts
# cn() = twMerge + clsx
twMerge helps us merge classes, while clsx helps us combine complex statements into a class string. With both functions, we can create a new function named cn() like this:
import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }ts
With the cn() function, we can perform complex combinations like this:
// 1 cn('text-base font-medium', true && 'font-bold p-10', 'p-4'); // => 'text-base font-bold p-4' // 2 const isLarge = true; cn({ 'text-lg font-bold': isLarge, 'text-sm': !isLarge, 'font-medium text-wrap': true }); // => 'text-lg font-medium text-wrap' // 3 cn({ 'bg-blue-500 text-black': true }, { 'text-white': true }, null, { 'rounded-lg': true }); // => 'bg-blue-500 text-white rounded-lg'ts
Now we can update the Maxwidthdiv component to use the cn() function:
import { cn } from 'path-to-cn' type Props = React.HTMLAttributes<HTMLDivElement> export function Maxwidthdiv({ className, children, ...props }: Props) { return ( <div className={cn('mx-auto w-full max-w-[1440px] px-8 sm:px-10 md:px-12', className)} {...props} > {children} </div> ); }tsx
In this way, we use the Maxwidthdiv component with a class that has complex combinations like this:
export default function App() { const [isCollapsed, setIsCollapsed] = useState(false) return ( <Maxwidthdiv className={ cn( "text-primary font-bold px-4", isCollapsed ? 'w-40 px-1' : 'w-[200px]' ) } > <p>Something</p> <button onClick={() => setIsCollapsed(prev => !prev)}> Collapsed <button> </Maxwidthdiv> ) }tsx
# Conclusion
TailwindCSS is a utility-first CSS framework, so we need to write many CSS classes in React components' className. By using the cn() function, we can make the CSS class notation clear and handle complex conditional statements with the help of twMerge and clsx.
Okay... Maybe that's all for my explanation about the most useful function for styling, cn(). Have you ever used the cn() function before? Or do you have an alternative? You can write it in the comment section below 👇!
# Extra: shadcn-ui

Landing Page shadcn-ui
I first learned about the cn() function actually from shadcn-ui. By installing shadcn-ui, we will obtain the cn() function in the file /src/lib/utils.ts. The cn() function is used in almost all shadcn-ui components aimed at enabling us to style flexibly.
After thinking about it again, is it possible that the "cn" in "shadcn" is actually taken from this cn() function... Hmm... 🤔