Éviter les conflits de classes avec tailwind-merge
Comprenez le fonctionnement de la spécificité des classes Tailwind et utilisez tailwind-merge pour garantir que la dernière variante appliquée l’emporte, sans problèmes de spécificité.
Éviter les conflits de classes avec tailwind-merge est une leçon Tailwind CSS Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Tailwind CSS Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Tailwind CSS Academy comprend 4 leçons au total.
Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.
How Tailwind Class Conflicts Arise
Tailwind utility classes set individual CSS properties. When two classes target the same property — like p-4 and p-8, or text-blue-500 and text-red-500 — both end up in the element's class list. The browser resolves the conflict using CSS cascade order: whichever utility was generated later in Tailwind's stylesheet wins, regardless of the order in your HTML. This makes overriding parent component styles unpredictable without tailwind-merge.
<!-- Both p-4 and p-8 appear in the DOM -->
<div class="p-4 p-8">...</div>
<!-- Which padding is applied? Depends on Tailwind's
stylesheet order, not the class string order. -->
<!-- Same problem with text colors -->
<div class="text-gray-900 text-blue-500">...</div>
<!-- Will the text be gray or blue? You cannot be sure. -->What tailwind-merge Does
tailwind-merge is a runtime utility that analyzes a class string and removes conflicting Tailwind classes, keeping only the last class from each conflicting group. It has an internal map of which Tailwind utilities conflict with each other — understanding that p-4 and p-8 both set padding, or that font-bold and font-medium both set font-weight. The last class in the input string always wins.
import { twMerge } from 'tailwind-merge';
// Conflict resolution: last value wins
twMerge('p-4 p-8')
// Output: 'p-8'
twMerge('text-gray-900 text-blue-500')
// Output: 'text-blue-500'
twMerge('font-bold font-medium text-sm text-lg')
// Output: 'font-medium text-lg'
// Non-conflicting classes are kept
twMerge('flex items-center gap-4 p-4')
// Output: 'flex items-center gap-4 p-4'Installing and Basic Usage
Install tailwind-merge as a production dependency (not dev-only, since it runs at runtime). Import twMerge and wrap any class string that might have conflicts. The function accepts multiple arguments and merges them all, similar to how clsx accepts multiple arguments — making it easy to drop into existing code.
npm install tailwind-merge
import { twMerge } from 'tailwind-merge';
// Single string
const cls = twMerge('bg-blue-500 bg-red-500');
// → 'bg-red-500'
// Multiple arguments (like clsx)
const cls2 = twMerge(
'px-4 py-2 rounded', // base
'px-8', // override padding-x
'text-white'
);
// → 'py-2 rounded px-8 text-white'twMerge Understands Utility Groups
tailwind-merge understands Tailwind's full utility taxonomy. It knows that px-4 sets horizontal padding while py-2 sets vertical padding, so they do not conflict. It knows that shadow-md and shadow-lg both set the box-shadow property and will conflict. It also handles variants like hover:bg-blue-500 and hover:bg-red-500 as a separate conflict group from their non-variant counterparts.
import { twMerge } from 'tailwind-merge';
// px and py don't conflict with each other
twMerge('px-4 py-2 px-8')
// → 'py-2 px-8' (px-4 removed, px-8 wins)
// Hover variants are separate groups
twMerge('hover:bg-blue-500 hover:bg-red-500 bg-white')
// → 'bg-white hover:bg-red-500'
// Shadow variants
twMerge('shadow-sm shadow-lg shadow-md')
// → 'shadow-md'
// Responsive prefixes are separate groups
twMerge('md:text-xl md:text-2xl text-sm')
// → 'text-sm md:text-2xl'The cn() Helper Pattern
The standard pattern in React/Next.js projects is to combine clsx and twMerge into a single cn() helper function. clsx handles conditional class logic and filters falsy values; twMerge then resolves any conflicts in the resulting string. Define this once in your utils file and use it everywhere — this is the approach used by shadcn/ui and most modern Tailwind component libraries.
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Usage: conditional classes with conflict resolution
function Badge({ active, className }) {
return (
<span
className={cn(
'px-2 py-1 rounded-full text-sm font-medium',
active ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-600',
className // caller override — twMerge resolves conflicts
)}
>
Label
</span>
);
}className Prop Override Pattern
The primary use case for tailwind-merge in React components is enabling safe className prop overrides. When a component has default styles and a caller provides additional or replacement classes via the className prop, twMerge ensures the caller's intent is honored. This makes components genuinely customizable without requiring consumers to fight CSS specificity or use !important.
// Without twMerge: caller override might not work
function Card({ className, children }) {
return (
<div className={'bg-white rounded-xl p-6 shadow ' + className}>
{children}
</div>
);
}
<Card className='bg-gray-50' /> // bg-white and bg-gray-50 both present!
// With twMerge: caller override always wins
function Card({ className, children }) {
return (
<div className={cn('bg-white rounded-xl p-6 shadow', className)}>
{children}
</div>
);
}
<Card className='bg-gray-50' /> // only bg-gray-50 (bg-white removed)Arbitrary Values and twMerge
tailwind-merge also handles arbitrary value classes using Tailwind's bracket notation. It correctly identifies that p-[10px] conflicts with p-4 and that w-[300px] conflicts with w-full. This is important because arbitrary values are common when designing components that need precise measurements not available in the default scale.
import { twMerge } from 'tailwind-merge';
// Arbitrary values conflict with standard utilities
twMerge('p-4 p-[10px]')
// → 'p-[10px]' (last wins)
twMerge('w-full w-[300px]')
// → 'w-[300px]'
twMerge('text-sm text-[15px]')
// → 'text-[15px]'
// Mix of standard and arbitrary
twMerge('bg-blue-500 bg-[#2563eb]')
// → 'bg-[#2563eb]'Configuring twMerge for Custom Classes
Out of the box, tailwind-merge only knows about Tailwind's default utilities. If you have added custom utilities via plugins or the config, twMerge will not know they conflict with related defaults. Use extendTailwindMerge() to teach twMerge about your custom classes, ensuring conflict resolution works correctly for your extended utility set.
import { extendTailwindMerge } from 'tailwind-merge';
// Tell twMerge about custom text-shadow utilities
const customTwMerge = extendTailwindMerge({
extend: {
classGroups: {
'text-shadow': ['text-shadow-sm', 'text-shadow-md', 'text-shadow-lg', 'text-shadow-none']
}
}
});
// Now conflicts are resolved correctly
customTwMerge('text-shadow-sm text-shadow-lg')
// → 'text-shadow-lg'
// Update cn() to use the custom instance
export const cn = (...inputs) => customTwMerge(clsx(inputs));Performance Considerations
tailwind-merge parses and resolves class strings at runtime in the browser. For most applications, this is imperceptibly fast. However, components that render thousands of instances (like virtual lists or table cells) may benefit from caching the resolved class string. tailwind-merge provides a createTailwindMerge() factory that enables a custom cache adapter. For most apps, the default cache (which uses an LRU internally) is sufficient.
import { createTailwindMerge, getDefaultConfig } from 'tailwind-merge';
// Create a version with a larger cache for high-volume usage
const twMerge = createTailwindMerge(getDefaultConfig);
// For critical performance: memoize with React.useMemo
function VirtualListItem({ selected, disabled, className }) {
const itemClass = useMemo(
() => cn(
'flex items-center px-3 py-2',
selected && 'bg-blue-50',
disabled && 'opacity-50',
className
),
[selected, disabled, className]
);
return <div className={itemClass}>{/* ... */}</div>;
}Common Pitfalls With tailwind-merge
tailwind-merge has a few edge cases to be aware of. It does not handle CSS Modules classes — only Tailwind utility names. If a class string contains non-Tailwind classes (like custom BEM classes), twMerge passes them through unchanged. Also, some plugin-generated utilities may not be recognized without extending the merge config. Lastly, twMerge operates on the class string, not on the rendered CSS, so it cannot resolve conflicts caused by CSS cascade beyond Tailwind utilities.
import { twMerge } from 'tailwind-merge';
// Non-Tailwind classes pass through untouched
twMerge('card__header bg-white bg-gray-50')
// → 'card__header bg-gray-50' (custom class kept, Tailwind conflict resolved)
// CSS Modules classes (hash-based) also pass through
twMerge('styles__button_abc123 bg-blue-500 bg-red-500')
// → 'styles__button_abc123 bg-red-500'
// Unknown plugin classes (without extendTailwindMerge) pass through
twMerge('text-shadow-lg text-shadow-sm') // both kept if not configured
// → 'text-shadow-lg text-shadow-sm' (no conflict detected)Testing With tailwind-merge
When writing unit tests for components that use cn(), test the actual class string output to verify that conflicts are resolved correctly. This is especially valuable for component libraries where callers must be able to override defaults reliably. Snapshot tests for className outputs ensure that future refactors do not accidentally break the override behavior.
// card.test.ts
import { cn } from '@/lib/utils';
import { cardVariants } from './Card';
test('caller className overrides default bg', () => {
const result = cn(cardVariants({ variant: 'white' }), 'bg-gray-50');
// bg-white from variant should be removed, bg-gray-50 should win
expect(result).not.toContain('bg-white');
expect(result).toContain('bg-gray-50');
});
test('non-conflicting classes are additive', () => {
const result = cn('p-6 rounded-xl', 'mt-4');
expect(result).toContain('p-6');
expect(result).toContain('rounded-xl');
expect(result).toContain('mt-4');
});Quick Check
Test your understanding of Tailwind CSS Mastery concepts from this lesson.
Lesson Recap
In this lesson you learned: tailwind-merge resolves conflicting Tailwind utilities by keeping only the last class from each conflict group, the cn() helper combines clsx and twMerge for conditional and conflict-free class handling, and extendTailwindMerge() teaches the library about custom plugin classes. Next up we explore Headless UI and how it integrates with Tailwind for accessible components.
Questions Fréquemment Posées
La leçon « Éviter les conflits de classes avec tailwind-merge » est-elle gratuite ?
Oui — le texte complet de « Éviter les conflits de classes avec tailwind-merge » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Tailwind CSS Academy, passe à CoddyKit PRO. Le cours Tailwind CSS Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Éviter les conflits de classes avec tailwind-merge » ?
Comprenez le fonctionnement de la spécificité des classes Tailwind et utilisez tailwind-merge pour garantir que la dernière variante appliquée l’emporte, sans problèmes de spécificité. Tu pratiques Tailwind CSS Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Tailwind CSS Academy ?
Aucune expérience préalable n'est requise. Tailwind CSS Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Éviter les conflits de classes avec tailwind-merge » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Tailwind CSS Academy ?
Oui. Chaque leçon Tailwind CSS Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Configurer Tailwind dans Next.js
- Classes conditionnelles dans React
- Variantes de composants avec CVA
- Éviter les conflits de classes avec tailwind-merge