Back to Blog
Building Reusable React Components with cn() and Class Variance Authority

Building Reusable React Components with cn() and Class Variance Authority

Learn how to create scalable, type-safe React components using the cn utility function and Class Variance Authority (CVA) for better component architecture and styling consistency.

Naim HasanNaim Hasan
09-20-2025
5 min

As React applications grow, maintaining consistent styling and component behavior becomes challenging. Today, I'll show you how to build truly reusable components using two powerful tools: the cn() utility function and Class Variance Authority (CVA).

Why This Approach Matters

Traditional component styling often leads to:

  • Inconsistent design patterns across components
  • Repetitive CSS class combinations
  • Difficult prop-based styling management
  • Poor TypeScript support for variant props

The cn() + CVA combo solves these issues elegantly.

Setting Up the Foundation

First, let's create our cn() utility function. This is a simple wrapper around clsx and tailwind-merge:

// lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

This function does two things:

  1. clsx - Conditionally joins class names together
  2. twMerge - Intelligently merges Tailwind classes, avoiding conflicts

Creating Your First CVA Component

Now let's build a Button component using Class Variance Authority:

// components/ui/button.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { ButtonHTMLAttributes, forwardRef } from "react";

// Define variants with CVA
const buttonVariants = cva(
  // Base classes - applied to all buttons
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-blue-600 text-white hover:bg-blue-700",
        destructive: "bg-red-600 text-white hover:bg-red-700",
        outline: "border border-gray-300 bg-transparent hover:bg-gray-100",
        ghost: "hover:bg-gray-100",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

// Create the component interface
interface ButtonProps
  extends ButtonHTMLAttributes,
    VariantProps {
  asChild?: boolean;
}

const Button = forwardRef(
  ({ className, variant, size, ...props }, ref) => {
    return (
      

The Magic Behind This Approach

Here's what makes this pattern so powerful:

1. Type Safety

CVA automatically generates TypeScript types for your variants. Your IDE will autocomplete variant options and catch invalid combinations.

2. Class Conflict Resolution

The cn() function intelligently merges classes. If you pass conflicting Tailwind classes, it keeps the last one:

// Without cn(): "bg-red-500 bg-blue-500" (both applied)
// With cn(): "bg-blue-500" (conflict resolved)

3. Flexible Composition

You can easily override or extend styles:

// Override padding while keeping other styles


// Add additional classes

Building More Complex Components

Let's create a Card component with multiple parts:

// components/ui/card.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { HTMLAttributes } from "react";

const cardVariants = cva(
  "rounded-lg border bg-white text-gray-950 shadow-sm",
  {
    variants: {
      variant: {
        default: "border-gray-200",
        destructive: "border-red-200 bg-red-50",
        success: "border-green-200 bg-green-50",
      },
      size: {
        default: "p-6",
        sm: "p-4",
        lg: "p-8",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

interface CardProps
  extends HTMLAttributes,
    VariantProps {}

function Card({ className, variant, size, ...props }: CardProps) {
  return (
    
); } function CardHeader({ className, ...props }: HTMLAttributes) { return (
); } function CardTitle({ className, ...props }: HTMLAttributes) { return (

); } function CardContent({ className, ...props }: HTMLAttributes) { return
; } export { Card, CardHeader, CardTitle, CardContent };

Usage Examples

Now you can use these components with confidence:

function MyComponent() {
  return (
    
{/* Basic usage */} {/* Different variants */} {/* Custom styling */} {/* Card component */} Success!

Your action completed successfully.

); }

Pro Tips for Success

1. Keep Variants Focused

Don't create too many variants. Stick to the core use cases and let consumers handle edge cases with the className prop.

2. Use Compound Variants

CVA supports compound variants for complex combinations:

const buttonVariants = cva("base-classes", {
  variants: {
    variant: { default: "...", destructive: "..." },
    size: { sm: "...", lg: "..." },
  },
  compoundVariants: [
    {
      variant: "destructive",
      size: "lg",
      class: "text-xl font-bold", // Special styling for large destructive buttons
    },
  ],
});

3. Export Variants for Reuse

Export your variant functions so other components can reuse the same styling logic.

Wrapping Up

The combination of cn() and CVA gives you:

  • ✅ Type-safe component APIs
  • ✅ Consistent design system
  • ✅ Flexible styling options
  • ✅ Better developer experience
  • ✅ Easier maintenance and scaling

Start with simple components like buttons and inputs, then gradually build your component library. Your future self (and your team) will thank you for the consistency and maintainability this approach provides.

The best part? This pattern scales beautifully from small projects to enterprise applications. Once you experience the developer experience improvement, you'll never want to go back to traditional component styling approaches.

#React#TypeScript#CVA#Tailwind CSS#Component Library#Best Practices