Resolving Tailwind CSS Class Conflicts with twMerge and Custom Configuration
If you’ve worked with tools like tailwind-merge or Shadcn, you may have encountered cases where some Tailwind classes conflict with each other. In this blog, I’ll explain why this happens and how to resolve it.
We’ll start by creating a Button component with different variants and sizes. We’re using Class Variance Authority (cva) to define the styles for each button and twMerge to handle merging the classes.
Button Component Setup
Here’s a Button component created using Shadcn:
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-primary text-white hover:bg-primary/90',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2 text-font-primary',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8 text-font-lg',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };
Merging Classes with twMerge
The following function merges Tailwind classes using twMerge:
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Font Size Configuration
Notice that the default button size uses the custom class text-font-primary, and the large size uses text-font-lg. These custom classes are defined in the Tailwind configuration file:
...
fontSize: {
'font-primary': '1.2rem',
'font-lg': '1.8rem',
},
...
Testing the Button Component
If we test the button with different sizes:
<Button size="default">Primary Btn</Button>
<Button size="lg">Large Primary Btn</Button>
We’ll observe that while the font size is applied correctly, the text-white class for the default button variant does not work as expected.
...
default: 'bg-primary text-white hover:bg-primary/90',
The button instead uses the default color set by the browser’s user agent.

Why Does This Happen?
The default behavior of the twMerge function can cause this issue when using custom configurations in Tailwind. It works well if:
- Only color names that don’t clash with other Tailwind class names are used.
- Deviations from default classes involve number values (like
font-sizeorpadding). - There are no font-family conflicts with default font-weight classes.
- The default Tailwind configuration is followed.
Sometimes, class conflicts are more complex than simply removing one class when another from the same group is present. For example, consider merging px-3 (sets both padding-left and padding-right) with pr-4 (sets only padding-right). Depending on the order in which these classes appear, twMerge may not resolve the conflicts as expected.
Resolving Class Conflicts with Custom Configuration
To fix this issue, we need to create a custom twMerge function that explicitly defines class group conflicts. Here’s how you can configure it:
import { clsx, type ClassValue } from 'clsx';
import { createTailwindMerge, getDefaultConfig } from 'tailwind-merge';
const customTwMerge = createTailwindMerge(() => {
const config = getDefaultConfig();
config.classGroups['font-size'] = [
{
text: ['font-primary', 'font-lg'],
},
];
return config;
});
export function cn(...inputs: ClassValue[]) {
return customTwMerge(clsx(inputs));
}
With this custom configuration, twMerge will no longer remove the text-white class since it is not conflicting with the font size configuration.
Conclusion
Now, if you test the buttons again, the text color will appear as intended, and twMerge will respect the text-white class.
Summary
When using twMerge to merge Tailwind classes, conflicts can arise with custom configurations, such as font sizes or color classes. This occurs because twMerge uses predefined rules to resolve conflicting classes, which may not always work with custom setups. To fix this, you can create a custom twMerge function to define how conflicting classes should be handled, ensuring that styles like text-white are not unintentionally overridden.