DRY Components

Repeating the same 15 Tailwind classes on every button is painful. Learn how to extract reusable components and build flexible variant systems.

The Problem

Imagine you have three buttons on a page. Without any strategy, your code looks like this:

The Painful Reality
<button class="px-4 py-2 rounded-lg bg-indigo-500 text-white
  font-semibold shadow-md hover:bg-indigo-600
  transition-colors duration-200">
  Save
</button>

<button class="px-4 py-2 rounded-lg bg-indigo-500 text-white
  font-semibold shadow-md hover:bg-indigo-600
  transition-colors duration-200">
  Submit
</button>

<button class="px-4 py-2 rounded-lg bg-indigo-500 text-white
  font-semibold shadow-md hover:bg-indigo-600
  transition-colors duration-200">
  Continue
</button>

{/* ๐Ÿ˜ฉ The same 8 classes, copy-pasted 3 times */}
Why this is bad: If you need to change the border-radius from rounded-lg to rounded-xl, you have to find and update every single instance. Miss one, and your UI becomes inconsistent.
1

Extract to React Components

This is the #1 recommended approach by the Tailwind team themselves. In a component framework like React or Next.js, you should extract repeated UI into reusable components.

โŒ Before (Raw HTML)
{/* Three identical buttons */}
<button class="px-4 py-2 rounded-lg
  bg-indigo-500 text-white font-semibold
  shadow-md hover:bg-indigo-600
  transition-colors">
  Save
</button>
<button class="px-4 py-2 rounded-lg
  bg-indigo-500 text-white font-semibold
  shadow-md hover:bg-indigo-600
  transition-colors">
  Submit
</button>
โœ… After (Component)
// components/ui/button.tsx
function Button({ children }) {
  return (
    <button className="px-4 py-2 rounded-lg
      bg-indigo-500 text-white font-semibold
      shadow-md hover:bg-indigo-600
      transition-colors">
      {children}
    </button>
  );
}

// Usage โ€” clean & consistent!
<Button>Save</Button>
<Button>Submit</Button>
<Button>Continue</Button>
Pro Tip: This is exactly how production component libraries like shadcn/ui work. Every <Button>, <Card>, and <Input> in this application is a reusable React component with Tailwind baked in.
2

The cn() Utility (clsx + tailwind-merge)

Components often need variants (primary, secondary, destructive). The cn() utility lets you intelligently merge and override Tailwind classes without conflicts.

โŒ Naive String Concatenation
// This BREAKS in Tailwind!
function Button({ className, children }) {
  return (
    <button className={
      "px-4 py-2 bg-indigo-500 " + className
    }>
      {children}
    </button>
  );
}

// Usage:
<Button className="bg-red-500">Delete</Button>

// Result in DOM:
// class="px-4 py-2 bg-indigo-500 bg-red-500"
//        ^^^^^^^^^^^ ^^^^^^^^^^
//  BOTH classes exist โ€” which wins?
//  It's UNPREDICTABLE! ๐Ÿ˜ฑ
โœ… Using cn() (tailwind-merge)
import { cn } from "@/lib/utils";

function Button({ className, children }) {
  return (
    <button className={cn(
      "px-4 py-2 bg-indigo-500",
      className  // overrides safely!
    )}>
      {children}
    </button>
  );
}

// Usage:
<Button className="bg-red-500">Delete</Button>

// Result in DOM:
// class="px-4 py-2 bg-red-500"
//                  ^^^^^^^^^^
//  bg-indigo-500 is REMOVED. Clean! โœ…
How it works: The cn() function in this project (src/lib/utils.ts) combines clsx (for conditional classes) with tailwind-merge(which intelligently resolves conflicting Tailwind utilities). It's the industry standard pattern.