cn()
The cn() function, popularized by shadcn/ui , is a utility function that helps manage conditional CSS classes in components.
Here is the function:
import { function clsx(...inputs: ClassValue[]): stringclsx, type type ClassValue = string | number | bigint | boolean | ClassArray | ClassDictionary | null | undefinedClassValue } from "clsx";
import { const twMerge: (...classLists: ClassNameValue[]) => stringtwMerge } from "tailwind-merge";
export function function cn(inputs: ClassValue[]): stringcn(inputs: ClassValue[]inputs: type ClassValue = string | number | bigint | boolean | ClassArray | ClassDictionary | null | undefinedClassValue[]) {
return function twMerge(...classLists: ClassNameValue[]): stringtwMerge(function clsx(...inputs: ClassValue[]): stringclsx(inputs: ClassValue[]inputs));
}
This function combines two essential libraries that each serve a distinct purpose. The first is clsx , which handles conditional class name construction by intelligently joining classes based on conditions, while the second is tailwind-merge , which resolves styling conflicts by properly merging Tailwind CSS classes without letting them override each other incorrectly.
Consider the practical example below:
import { import cncn } from "@/lib/utils";
import * as React from "react";
interface ButtonProps extends React.type ComponentProps<T extends keyof React.JSX.IntrinsicElements | React.JSXElementConstructor<any>> = T extends React.JSXElementConstructor<infer Props> ? Props : T extends keyof React.JSX.IntrinsicElements ? React.JSX.IntrinsicElements[T] : {}Used to retrieve the props a component accepts. Can either be passed a string,
indicating a DOM element (e.g. 'div', 'span', etc.) or the type of a React
component.
It's usually better to use
{@link
ComponentPropsWithRef
}
or
{@link
ComponentPropsWithoutRef
}
instead of this type, as they let you be explicit about whether or not to include
the `ref` prop.ComponentProps<"button"> {
ButtonProps.className?: string | undefinedclassName?: string;
}
function function Button({ className, ...props }: ButtonProps): React.JSX.ElementButton({ className: string | undefinedclassName, ...props: {
ref?: React.Ref<HTMLButtonElement> | undefined;
key?: React.Key | null | undefined;
disabled?: boolean | undefined;
form?: string | undefined;
formAction?: string | ((formData: FormData) => void | Promise<void>) | React.DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_FORM_ACTIONS[keyof React.DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_FORM_ACTIONS] | undefined;
... 283 more ...;
onTransitionStartCapture?: React.TransitionEventHandler<...> | undefined;
}
props }: ButtonProps) {
return (
<JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button HTMLAttributes<HTMLButtonElement>.className?: string | undefinedclassName={import cncn("px-2 py-1 font-medium", className: string | undefinedclassName)} {...props: {
ref?: React.Ref<HTMLButtonElement> | undefined;
key?: React.Key | null | undefined;
disabled?: boolean | undefined;
form?: string | undefined;
formAction?: string | ((formData: FormData) => void | Promise<void>) | React.DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_FORM_ACTIONS[keyof React.DO_NOT_USE_OR_YOU_WILL_BE_FIRED_EXPERIMENTAL_FORM_ACTIONS] | undefined;
... 283 more ...;
onTransitionStartCapture?: React.TransitionEventHandler<...> | undefined;
}
props} />
);
}
This implementation serves two crucial purposes.
First, it allows you to easily pass conditional classes for dynamic styling. For example, you can write
<Button className={isPrimary && "bg-blue-500"}>Food</Button>
to conditionally apply a blue background only when needed, and clsx will handle the conditional logic cleanly.
Second, and more importantly, it automatically resolves styling conflicts through tailwind-merge. To understand why this matters, consider what happens without it. If your Button has default px-2 py-1 padding and you try to override it with <Button className="p-3">, the new p-3 class would be ignored due to CSS specificity rules(the original classes in the stylesheet would take precedence). However, with tailwind-merge working inside cn(), the function intelligently detects these conflicting utility classes and ensures that p-3 properly replaces the existing padding classes rather than being ignored.
cn is a simple utility function that is quite helpful when building flexible, reusable components with Tailwind CSS.