Frequently Asked Questions (FAQ)
v3 Migration
What's new in v3?
v3 introduces two major improvements:
- Two build options: Original (with tailwind-merge) and Lite (without tailwind-merge)
- Massive performance improvements: Up to 500x faster when using tailwind-merge
- Enhanced
cn
utility: Now supports functionality similar toclassnames
/clsx
Should I use the original build or lite build?
Use the original build if:
- You need automatic Tailwind CSS conflict resolution
- You're already using tailwind-merge in your project
- Performance with conflict resolution is important (v3 is 400-500x faster)
Use the lite build if:
- Bundle size is critical (~80% smaller)
- You don't need automatic conflict resolution
- You handle class merging manually
How do I migrate from v2 to v3?
For most users, just ensure tailwind-merge
is installed:
npm install tailwind-merge
If you want to use the lite build instead:
// Change your imports from:
import { tv } from 'tailwind-variants';
// To:
import { tv } from 'tailwind-variants/lite';
What happened to lazy loading in v3?
v3 removes lazy loading of tailwind-merge
in favor of two separate builds. This fixes issues where classes wouldn't merge correctly on first render. The original build now statically imports tailwind-merge
, while the lite build doesn't include it at all.
v2 Migration
Why is tailwind-merge now optional?
In v2, we made tailwind-merge
an optional peer dependency to give users more control over their bundle size. Not all projects need automatic conflict resolution, and by making it optional, users who don't need this feature can save significant bundle size. The library itself is now up to 80% smaller - only 5.5KB minified (2.1KB gzipped), compared to v1's 28.3KB minified (9.2KB gzipped).
Do I need to install tailwind-merge separately?
Yes, if you want to use the automatic conflict resolution feature (which is enabled by default), you need to install tailwind-merge
as a peer dependency:
npm install tailwind-merge
If you don't need conflict resolution, you can disable it by setting twMerge: false
in your config.
What happens if I don't install tailwind-merge but keep twMerge enabled?
Your application will throw an error at runtime when trying to use tailwind-variants. You must either:
- Install
tailwind-merge
as a dependency, or - Set
twMerge: false
in your configuration
How much faster is v2 compared to v1?
v2 is 37-62% faster for most operations. The performance improvements come from:
- Optimized object creation and array operations
- Reduced function call overhead
- Better memory management
- More efficient class merging algorithms
Common Issues
Why are my Tailwind classes not being applied correctly?
This usually happens when:
- Conflict resolution is disabled: Make sure you have
tailwind-merge
installed andtwMerge
is set totrue
(default) - Class order matters: Later classes in your variants override earlier ones
- Specificity issues: Custom classes passed via
class
/className
prop will override variant classes
How do I override styles from a variant?
You can override styles in several ways:
// Using the class prop
button({ color: 'primary', class: 'bg-pink-500' })
// Using compound variants
tv({
variants: { /* ... */ },
compoundVariants: [
{
color: 'primary',
size: 'large',
class: 'bg-pink-500'
}
]
})
Why is TypeScript not inferring my variant types correctly?
Make sure you're using as const
for your variant definitions if you're defining them outside of the tv
function:
const variants = {
color: {
primary: 'bg-blue-500',
secondary: 'bg-gray-500'
}
} as const;
const button = tv({ variants });
Performance
Should I disable twMerge for better performance?
While disabling twMerge
can provide a small performance boost, we recommend keeping it enabled unless:
- You're building a performance-critical application
- You're confident you won't have class conflicts
- You're already handling class merging manually
How can I optimize my tailwind-variants usage?
- Reuse tv instances: Create tv components once and reuse them
- Avoid dynamic class generation: Define all variants upfront rather than generating classes dynamically
- Use compound variants wisely: They add overhead, so use them only when necessary
// Good - defined once
const button = tv({ /* ... */ });
// Avoid - creates new instance each render
function Component() {
const button = tv({ /* ... */ });
}
Usage Patterns
Can I use tailwind-variants without React?
Yes! Tailwind Variants is framework agnostic. It works with any JavaScript framework or vanilla JavaScript:
// Vanilla JS
const button = tv({ /* ... */ });
document.getElementById('button').className = button({ color: 'primary' });
// Vue
<template>
<button :class="button({ color: 'primary' })">Click me</button>
</template>
// Svelte
<button class={button({ color: 'primary' })}>Click me</button>
How do I handle responsive variants?
You can use Tailwind's responsive prefixes directly in your variant definitions:
const component = tv({
base: 'text-sm md:text-base lg:text-lg',
variants: {
color: {
primary: 'text-blue-500 md:text-blue-600',
secondary: 'text-gray-500 md:text-gray-600'
}
}
});
Can I extend multiple components?
No, you can only extend one component at a time. However, you can compose multiple components manually:
const baseButton = tv({ /* ... */ });
const iconButton = tv({ /* ... */ });
// Compose manually
const combinedButton = tv({
base: [baseButton(), iconButton()],
// additional variants...
});
How do I use slots with TypeScript?
When using slots with TypeScript, you can extract the slot functions for better type inference:
const card = tv({
slots: {
base: 'p-4',
title: 'text-lg font-bold',
content: 'text-sm'
}
});
// Extract with types
const { base, title, content } = card();
// Or use directly
<div className={card().base()}>
<h1 className={card().title()}>Title</h1>
</div>
Still have questions?
If you have any questions not covered here, please ask in the Discord (opens in a new tab) or open a GitHub Discussion (opens in a new tab).