cn (classNames) Helper
The cn function is a utility helper designed to simplify the process of conditionally applying CSS classes in React components. It's particularly useful when working with Tailwind CSS, but can be used with any CSS framework or custom classes.
Introduction
The cn
(classNames) helper function is a powerful utility designed to simplify the process of combining and conditionally applying CSS classes in React components. It is used in the creation of nearly all components and leverages two popular libraries, clsx
and tailwind-merge
, to provide an efficient and flexible way of handling class names, particularly when working with Tailwind CSS.
How it works
- clsx is used to conditionally construct className strings.
- tailwind-merge intelligently merges Tailwind CSS classes without style conflicts.
- The resulting function is exported as
cn
for use throughout the project.
Key features of the cn
function:
- Accepts any number of arguments (strings, objects, or arrays) representing class names.
- Efficiently combines classes using
clsx
. - Intelligently merges Tailwind classes to prevent conflicts using
tailwind-merge
. - Automatically removes falsy values, duplicates, and excess whitespace.
- Supports conditional class application through object and array syntax.
import { cn } from "@/lib/utils"
function MyComponent({ variant, isActive, className }) {
return (
<div
className={cn(
"base-classes",
variant === "primary" && "bg-blue-500 text-white",
variant === "secondary" && "bg-gray-200 text-gray-800",
isActive && "ring-2 ring-offset-2 ring-blue-500",
className
)}
>
{/* Component content */}
</div>
);
}
export default MyComponent;
Additional Examples
Combining with class-variance-authority
The cn
function is often used in conjunction with class-variance-authority
(CVA) to create flexible, variant-based components. This powerful combination allows for the creation of highly customizable components with a consistent API and styling structure.
How it works
class-variance-authority
is used to define a set of variant styles for a component.- The
cn
function is then used to merge these variant styles with any additional classes, ensuring proper style hierarchy and avoiding conflicts.
Here's an example of how this is implemented in a Button component:
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
import { Slot } from "@radix-ui/react-slot"
import { forwardRef } from "react"
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }```