AyushShah
Back to Blog
Stop Repeating Tailwind Classes: Build Scalable UIs with `tailwind-variants`
Web DevelopmentFrontend DevelopmentDesign Systems

Stop Repeating Tailwind Classes: Build Scalable UIs with `tailwind-variants`

By AyushMay 18, 2025
31 views

If you’ve been using Tailwind CSS for a while, you’ve likely felt the pain of **long, repetitive class strings**. As your components grow, so does the mess:

<button className="bg-blue-500 text-white px-4 py-2 rounded-full font-medium hover:bg-blue-600">
 Click me
</button>

Now imagine 10 buttons, each slightly different. That’s a **lot** of copy-pasting. More code. More bugs. Less clarity.

Let’s fix that.

Introducing tailwind-variants — a lightweight, powerful tool to help you write cleaner, more reusable Tailwind components using prop-based styling

🚀 Why Use `tailwind-variants`?

Tailwind is great for rapid styling, but it’s missing structured logic. `tailwind-variants` adds that missing layer:

  • ✅ Centralized styling logic
  • ✅ Prop-based customization
  • ✅ Cleaner, readable, reusable classes

It brings the power of component libraries without losing the flexibility of Tailwind.

🧠 What is `tv()`?

At the heart of tailwind-variants is the tv() function:

import { tv } from "tailwind-variants";

const button = tv({
 base: "font-medium rounded-full",
});

You define the **base styles** every version of the component will have.

🎛️ Adding Variants

const button = tv({
 base: "font-medium rounded-full",
 variants: {
 size: {
 sm: "px-2 py-1 text-sm",
 lg: "px-4 py-2 text-lg",
 },
 color: {
 primary: "bg-blue-500 text-white",
 secondary: "bg-gray-200 text-black",
 },
 },
});

Now you can use props to style:

<button className={button({ size: "lg", color: "primary" }) }>
 Click Me
</button>

Output:

<button class="font-medium rounded-full px-4 py-2 text-lg bg-blue-500 text-white">
 Click Me
</button>

🧰 Default Variants

Set fallback values for props using `defaultVariants`:

defaultVariants: {
 size: "sm",
 color: "primary",
}

Now this works too:

<button className={button() }>
 Click me
</button>

Result:

<button class="font-medium rounded-full px-2 py-1 text-sm bg-blue-500 text-white">
 Click me
</button>

🔀 Compound Variants

Need to apply a style **only** when multiple props are combined? Use `compoundVariants`:

compoundVariants: [
 {
   size: "lg",
   color: "primary",
   class: "shadow-lg",
 },
]

Now this will include the shadow:

<button className={button({ size: "lg", color: "primary" }) }>
 Click Me
</button>

🧩 Styling Subcomponents with `slots`

Got multi-part components like cards, modals, or complex buttons? Use `slots` to define styles for each internal piece:

const card = tv({
 slots: {
 base: "bg-white p-4",
 title: "text-lg font-bold",
 content: "text-sm text-gray-700",
 },
});

Usage:

const { base, title, content } = card();

return (
 <div className={base() }>
 <h2 className={title() }>Card Title</h2>
 <p className={content() }>Card content goes here.</p>
 </div>
);

🎯 Overriding Styles

You can override classes easily using the `class` prop:

button({
 color: "secondary",
 class: "bg-pink-500 hover:bg-pink-500",
});

Output:

<button class="font-semibold text-white px-3 py-1 rounded-full bg-pink-500 hover:bg-pink-500">

🛠 Creating a Real Button Component

const button = tv({
 base: "inline-flex items-center gap-2 rounded-full font-medium",
 variants: {
 size: {
 sm: "px-2 py-1 text-sm",
 lg: "px-4 py-2 text-lg",
 },
 color: {
 primary: "bg-blue-500 text-white",
 secondary: "bg-gray-200 text-black",
 },
 },
 defaultVariants: {
 size: "sm",
 color: "primary",
 },
});

export function Button({ size, color, children }) {
 return (
 <button className={button({ size, color }) }>
 {children}
 </button>
 );
}

📦 Final Thoughts

`tailwind-variants` adds superpowers to your Tailwind codebase:

  • 🧠 Less duplication
  • ✅ Better structure
  • 🎯 Easier component scaling

It’s time to stop copying long strings and start building logic-driven design systems.

Tags:
Web DevelopmentFrontend DevelopmentDesign Systems