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.