File: README.md
tailwind-merge
Utility function to efficiently merge Tailwind CSS classes in JS without style conflicts.
import { twMerge } from 'tailwind-merge'
twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
// โ 'hover:bg-dark-red p-3 bg-[#B91C1C]'- Supports Tailwind v4.0 up to v4.3 (if you use Tailwind v3, use tailwind-merge v2.6.0)
- Works in all modern browsers and maintained Node versions
- Fully typed
- Check bundle size on Bundlephobia
Get started
- What is it for
- When and how to use it
- Features
- Limitations
- Configuration
- Recipes
- API reference
- Writing plugins
- Versioning
- Contributing
- Similar packages
File: docs/api-reference.md
API reference
Reference to all exports of tailwind-merge.
`twMerge`
function twMerge(
...classLists: Array<string | undefined | null | false | 0 | typeof classLists>
): stringDefault function to use if you're using the default Tailwind config or are close enough to the default config. Check out basic usage for more info.
If twMerge doesn't work for you, you can create your own custom merge function with extendTailwindMerge.
`twJoin`
function twJoin(
...classLists: Array<string | undefined | null | false | 0 | typeof classLists>
): stringFunction to join className strings conditionally without resolving conflicts.
twJoin(
'border border-red-500',
hasBackground && 'bg-red-100',
hasLargeText && 'text-lg',
hasLargeSpacing && ['p-2', hasLargeText ? 'leading-8' : 'leading-7'],
)It is used internally within twMerge and a direct subset of clsx. If you use clsx or classnames to apply Tailwind classes conditionally and don't need support for object arguments, you can use twJoin instead, it is a little faster and will save you a few hundred bytes in bundle size.
Why no object support? Read here.
`getDefaultConfig`
function getDefaultConfig(): satisfies Config<DefaultClassGroupIds, DefaultThemeGroupIds>Function which returns the default config used by tailwind-merge. The tailwind-merge config is different from the Tailwind config. It is optimized for small bundle size and fast runtime performance because it is expected to run in the browser.
`fromTheme`
function fromTheme<
AdditionalThemeGroupIds extends string = never,
DefaultThemeGroupIdsInner extends string = DefaultThemeGroupIds,
>(key: NoInfer<DefaultThemeGroupIdsInner | AdditionalThemeGroupIds>): ThemeGetterFunction to retrieve values from a theme scale, to be used in class groups.
fromTheme doesn't return the values from the theme scale, but rather another function which is used by tailwind-merge internally to retrieve the theme values. tailwind-merge can differentiate the theme getter function from a validator because it has a isThemeGetter property set to true.
When using TypeScript, the function only allows passing the default theme group IDs as the key argument. If you use custom theme group IDs, you need to pass them as the generic type argument AdditionalThemeGroupIds. In case you aren't using the default tailwind-merge config and use a different set of theme group IDs entirely, you can also pass them as the generic type argument DefaultThemeGroupIdsInner. If you want to allow any keys, you can call it as fromTheme<string>('anything-goes-here').
fromTheme can be used like this:
type AdditionalClassGroupIds = 'badge' | 'badge-color'
type AdditionalThemeGroupIds = 'custom-color'
extendTailwindMerge<AdditionalClassGroupIds, AdditionalThemeGroupIds>({
extend: {
theme: {
'custom-color': ['primary', 'secondary'],
},
classGroups: {
badge: [{ badge: [fromTheme('text')] }],
'badge-color': [{ badge: [fromTheme<AdditionalThemeGroupIds>('custom-color')] }],
},
},
})`extendTailwindMerge`
function extendTailwindMerge<
AdditionalClassGroupIds extends string = never,
AdditionalThemeGroupIds extends string = never,
>(
configExtension: ConfigExtension<
DefaultClassGroupIds | AdditionalClassGroupIds,
DefaultThemeGroupIds | AdditionalThemeGroupIds
>,
...createConfig: ((config: GenericConfig) => GenericConfig)[]
): TailwindMerge
function extendTailwindMerge<
AdditionalClassGroupIds extends string = never,
AdditionalThemeGroupIds extends string = never,
>(...createConfig: ((config: GenericConfig) => GenericConfig)[]): TailwindMergeFunction to create merge function with custom config which extends the default config. Use this if you use the default Tailwind config and just modified it in some places.
[!Note]
The functionextendTailwindMergecomputes a large data structure based on the config passed to it. I recommend to call it only once and store the result in a top-level variable instead of calling it inline within another repeatedly called function.
You provide it a configExtension object which gets merged with the default config.
When using TypeScript and you use custom class group IDs or theme group IDs, you need to pass them as the generic type arguments AdditionalClassGroupIds and AdditionalThemeGroupIds. This is enforced to prevent accidental use of non-existing class group IDs accidentally. If you want to allow any custom keys without explicitly defining them, you can pass as string to both arguments.
/* Detailed source-code truncated for AI context efficiency. */Additionally, you can pass multiple createConfig functions (more to that in createTailwindMerge) which is convenient if you want to combine your config with third-party plugins.
const twMerge = extendTailwindMerge({ โฆ }, withSomePlugin)If you only use plugins, you can omit the configExtension object as well.
const twMerge = extendTailwindMerge(withSomePlugin)`createTailwindMerge`
function createTailwindMerge(
...createConfig: [() => Config, ...Array<(config: Config) => Config>]
): TailwindMergeFunction to create merge function with custom config. Use this function instead of extendTailwindMerge if you don't need the default config or want more control over the config.
[!Note]
The functioncreateTailwindMergecomputes a large data structure based on the config passed to it. I recommend to call it only once and store the result in a top-level variable instead of calling it inline within another repeatedly called function.
You need to provide a function which resolves to the config tailwind-merge should use for the new merge function. You can either extend from the default config or create a new one from scratch.
// โ Callback passed to `createTailwindMerge` is called when
// `twMerge` gets called the first time.
const twMerge = createTailwindMerge(() => {
const defaultConfig = getDefaultConfig()
return {
cacheSize: 0,
classGroups: {
...defaultConfig.classGroups,
badge: ['badge', 'badge-pill', { 'badge-dot': ['', 'sm', 'lg'] }],
'icon-size': [{ icon: ['auto', (value) => Number(value) >= 16] }],
card: ['card-sm', 'card-md', 'card-lg'],
},
conflictingClassGroups: {
...defaultConfig.conflictingClassGroups,
badge: ['icon-size'],
},
conflictingClassGroupModifiers: {
...defaultConfig.conflictingClassGroupModifiers,
card: ['icon-size'],
},
postfixLookupClassGroups: defaultConfig.postfixLookupClassGroups,
orderSensitiveModifiers: [...defaultConfig.orderSensitiveModifiers, 'before'],
}
})Same as in extendTailwindMerge you can use multiple createConfig functions which is convenient if you want to combine your config with third-party plugins. Just keep in mind that the first createConfig function does not get passed any arguments, whereas the subsequent functions get each passed the config from the previous function.
const twMerge = createTailwindMerge(getDefaultConfig, withSomePlugin, (config) => ({
// โ Config returned by `withSomePlugin`
...config,
classGroups: {
...config.classGroups,
mySpecialClassGroup: [{ special: ['1', '2'] }],
},
}))But don't merge configs like that. Use mergeConfigs instead.
`mergeConfigs`
function mergeConfigs<ClassGroupIds extends string, ThemeGroupIds extends string = never>(
baseConfig: GenericConfig,
configExtension: ConfigExtension<ClassGroupIds, ThemeGroupIds>,
): GenericConfigHelper function to merge multiple tailwind-merge configs. Properties with the value undefined are skipped.
When using TypeScript, you need to pass a union of all class group IDs and theme group IDs used in configExtension as generic arguments to mergeConfigs or pass string to both arguments to allow any IDs.
const twMerge = createTailwindMerge(getDefaultConfig, (config) =>
mergeConfigs<'shadow' | 'animate' | 'prose'>(config, {
override: {
classGroups: {
// โ Overriding existing class group
shadow: [{ shadow: ['100', '200', '300', '400', '500'] }],
},
},
extend: {
classGroups: {
// โ Adding value to existing class group
animate: ['animate-shimmer'],
// โ Adding new class group
prose: [{ prose: ['', validators.isTshirtSize] }],
},
},
}),
)`validators`
interface Validators {
isAny(value: string): boolean
isAnyNonArbitrary(value: string): boolean
isArbitraryFamilyName(value: string): boolean
isArbitraryImage(value: string): boolean
isArbitraryLength(value: string): boolean
isArbitraryNumber(value: string): boolean
isArbitraryPosition(value: string): boolean
isArbitraryShadow(value: string): boolean
isArbitrarySize(value: string): boolean
isArbitraryValue(value: string): boolean
isArbitraryVariable(value: string): boolean
isArbitraryVariableFamilyName(value: string): boolean
isArbitraryVariableImage(value: string): boolean
isArbitraryVariableLength(value: string): boolean
isArbitraryVariablePosition(value: string): boolean
isArbitraryVariableShadow(value: string): boolean
isArbitraryVariableSize(value: string): boolean
isArbitraryVariableWeight(value: string): boolean
isArbitraryWeight(value: string): boolean
isFraction(value: string): boolean
isInteger(value: string): boolean
isNumber(value: string): boolean
isPercent(value: string): boolean
isTshirtSize(value: string): boolean
}An object containing all the validators used in tailwind-merge. They are useful if you want to use a custom config with extendTailwindMerge or createTailwindMerge.
Example usage:
const paddingClassGroup = [{ p: [validators.isNumber] }]
const customImageGroup = [{ 'custom-img': [validators.isArbitraryImage] }]Simple Type Validators
These validators check for basic patterns and types:
| Validator | Description | Example Match |
|---|---|---|
isAny |
Always returns true. Use carefully - matches everything. Best when certain no other class groups exist in a namespace. |
Matches any value |
isAnyNonArbitrary |
Checks if class part is NOT an arbitrary value or variable | red, lg, 4 |
isNamedContainerQuery |
Checks for named container query classes | @container/sidebar, @container-size/main |
isInteger |
Matches integer values | 3, 100 |
isNumber |
Matches any number (integer or decimal) | 3, 1.5, 0.25 |
isFraction |
Matches fraction patterns | 1/2, 127/256 |
isPercent |
Matches percentage values | 12.5%, 50% |
isTshirtSize |
Matches T-shirt sizes, optionally with number prefix | sm, xl, 2xl |
Arbitrary Value Validators
These validators check arbitrary values (values in square brackets [...]):
| Validator | Description | Example Match | Common Use |
|---|---|---|---|
isArbitraryValue |
Checks if value is enclosed in brackets | [something] |
Generic arbitrary value detection |
isArbitraryLength |
Checks for arbitrary length values | [3%], [4px], [length:var(--my-var)] |
Width, height, spacing |
isArbitraryNumber |
Checks for arbitrary numbers or number: labeled values |
[450], [number:var(--value)] |
Font-weight, z-index |
isArbitraryWeight |
Checks for arbitrary font weight values or weight:/number: labeled values |
[400], [bold], [weight:var(--fw)] |
Font-weight |
isArbitraryFamilyName |
Checks for family-name: labeled values |
[family-name:Open_Sans] |
Font-family |
isArbitraryPosition |
Checks for position: labeled values |
[position:200px_100px] |
Background-position |
isArbitrarySize |
Checks for size: labeled values |
[size:200px_100px] |
Background-size |
isArbitraryImage |
Checks for image-like values (starts with image:, url:, linear-gradient(, etc.) |
[url('/path.png')], [image:var(--img)] |
Background-image |
isArbitraryShadow |
Checks for shadow patterns (two lengths separated by underscore, optionally with inset) |
[0_35px_60px_-15px_rgba(0,0,0,0.3)], [inset_0_4px_8px_rgba(0,0,0,0.1)] |
Box-shadow, text-shadow |
Arbitrary Variable Validators
These validators check arbitrary CSS variables (values in parentheses (...)):
| Validator | Description | Example Match | Common Use |
|---|---|---|---|
isArbitraryVariable |
Checks if value is a CSS variable in parentheses | (--my-var) |
Generic CSS variable |
isArbitraryVariableLength |
Checks for variables with length label |
(length:--my-length) |
Length properties |
isArbitraryVariableSize |
Checks for variables with size, length, or percentage label |
(size:--my-size) |
Size properties |
isArbitraryVariablePosition |
Checks for variables with position label |
(position:--my-position) |
Position properties |
isArbitraryVariableImage |
Checks for variables with image or url label |
(image:--my-image) |
Image properties |
isArbitraryVariableFamilyName |
Checks for variables with family-name label |
(family-name:--my-font) |
Font-family |
isArbitraryVariableWeight |
Checks for variables with weight or number label, or no label |
(weight:--my-fw), (--my-weight) |
Font-weight |
isArbitraryVariableShadow |
Checks for variables with shadow label or no label |
(shadow:--my-shadow), (--my-shadow) |
Shadow properties |
Usage Examples
import { extendTailwindMerge, validators } from 'tailwind-merge'
const twMerge = extendTailwindMerge({
extend: {
classGroups: {
// Custom size classes that accept numbers or fractions
'custom-size': [{ size: [validators.isNumber, validators.isFraction] }],
// Custom image classes
'hero-image': [{ 'hero-img': [validators.isArbitraryImage] }],
// Custom spacing with T-shirt sizes
'custom-gap': [{ gap: [validators.isTshirtSize] }],
// Accept any value (use with caution)
'theme-color': [{ theme: [validators.isAny] }],
},
},
})`Config`
interface Config<ClassGroupIds extends string, ThemeGroupIds extends string> { โฆ }TypeScript type for config object. Useful if you want to build a createConfig function but don't want to define it inline in extendTailwindMerge or createTailwindMerge.
`DefaultClassGroupIds`
type DefaultClassGroupIds = 'accent' | 'align-content' | 'align-items' | โฆTypeScript type for all class group IDs defined in the default config of tailwind-merge.
`DefaultThemeGroupIds`
type DefaultThemeGroupIds = 'blur' | 'borderColor' | 'borderRadius' | โฆTypeScript type for all theme group IDs defined in the default config of tailwind-merge.
`ClassNameValue`
type ClassNameValue = string | null | undefined | 0 | false | ClassNameValue[]TypeScript type for arguments accepted by twMerge and twJoin. You might want to use it if you wrap any of those with your own function.
function myWrappedTwMerge(...args: ClassNameValue[]) {
doSomething()
return twMerge(...args)
}`ClassValidator`
type ClassValidator = (value: string) => booleanTypeScript type for class validators accepted in class definitions within extendTailwindMerge and createTailwindMerge.
Next: Writing plugins
Previous: Recipes
File: docs/configuration.md
Configuration
Installation
The tailwind-merge package is hosted on npm under the name tailwind-merge. There are lots of package managers for installing packages hosted on npm. Here are installation instructions for the most popular ones: npm, yarn, pnpm and bun.
npm add tailwind-merge
yarn add tailwind-merge
pnpm add tailwind-merge
bun add tailwind-mergeBasic usage
If you're using Tailwind CSS without any extra config, you can use twMerge right away. You can safely stop reading the documentation here.
Usage with custom Tailwind config
If you're using a custom Tailwind config, you may need to configure tailwind-merge as well to merge classes properly.
The default twMerge function is configured in a way that you can still use it if all the following points apply to your Tailwind config:
- Only using color names which don't clash with other Tailwind class names
- Only deviating by number values from number-based Tailwind classes
- Only using font-family classes which don't clash with default font-weight classes
- Sticking to default Tailwind config for everything else
If some of these points don't apply to you, you can test whether twMerge still works as intended with your custom classes. Otherwise, you need create your own custom merge function by either extending the default tailwind-merge config or using a completely custom one.
The tailwind-merge config is different from the Tailwind config because it's expected to be shipped and run in the browser as opposed to the Tailwind config which is meant to run at build-time. Be careful in case you're using your Tailwind config directly to configure tailwind-merge in your client-side code because that could result in an unnecessarily large bundle size.
Shape of tailwind-merge config
The tailwind-merge config is an object with a few keys.
const tailwindMergeConfig = {
// โ Set how many values should be stored in cache.
cacheSize: 500,
// โ Optional prefix from Tailwind config
prefix: 'tw',
theme: {
// Theme scales are defined here
},
classGroups: {
// Class groups are defined here
},
conflictingClassGroups: {
// Conflicts between class groups are defined here
},
conflictingClassGroupModifiers: {
// Conflicts between postfix modifier of a class group and another class group are defined here
},
postfixLookupClassGroups: [
// Class group IDs which should be resolved again with their postfix modifier attached
],
orderSensitiveModifiers: [
// Modifiers whose order among multiple modifiers should be preserved because their order
// changes which element gets targeted.
],
}Class groups
The library uses a concept of class groups which is an array of Tailwind classes which all modify the same CSS property. E.g. here is the position class group.
const positionClassGroup = ['static', 'fixed', 'absolute', 'relative', 'sticky']tailwind-merge resolves conflicts between classes in a class group and only keeps the last one passed to the merge function call.
twMerge('static sticky relative') // โ 'relative'Tailwind classes often share the beginning of the class name, so elements in a class group can also be an object with values of the same shape as a class group (yes, the shape is recursive). In the object each key is joined with all the elements in the corresponding array with a dash (-) in between.
E.g. here is the overflow class group which results in the classes overflow-auto, overflow-hidden, overflow-visible and overflow-scroll.
const overflowClassGroup = [{ overflow: ['auto', 'hidden', 'visible', 'scroll'] }]Sometimes it isn't possible to enumerate all elements in a class group. Think of a Tailwind class which allows arbitrary values. In this scenario you can use a validator function which takes a class part and returns a boolean indicating whether a class is part of a class group.
E.g. here is the fill class group.
const isArbitraryValue = (classPart: string) => /^\[.+\]$/.test(classPart)
const fillClassGroup = [{ fill: ['current', isArbitraryValue] }]Because the function is under the fill key, it will only get called for classes which start with fill-. Also, the function only gets passed the part of the class name which comes after fill-, this way you can use the same function in multiple class groups. tailwind-merge exports its own validators, so you don't need to recreate them.
You can use an empty string ('') as a class part if you want to indicate that the preceding part was the end. This is useful for defining elements which are marked as DEFAULT in the Tailwind config.
// โ Resolves to filter and filter-none
const filterClassGroup = [{ filter: ['', 'none'] }]Each class group is defined under its ID in the classGroups object in the config. This ID is only used internally, and the only thing that matters is that it is unique among all class groups.
Conflicting class groups
Sometimes there are conflicts across Tailwind classes which are more complex than "remove all those other classes when a class from this group is present in the class list string".
One example is the combination of the classes px-3 (setting padding-left and padding-right) and pr-4 (setting padding-right).
Scenario 1: When classes are ordered as pr-4 px-3:
- You want
px-3to apply bothpadding-leftandpadding-right - The earlier
pr-4should be removed sincepx-3also setspadding-right - Result:
twMerge('pr-4 px-3') // โ 'px-3'
Scenario 2: When classes are ordered as px-3 pr-4:
- You want
px-3to setpadding-left - You want
pr-4to override just thepadding-rightfrompx-3 - The
px-3class should NOT be removed - Result:
twMerge('px-3 pr-4') // โ 'px-3 pr-4'
To summarize, px-3 should stand in conflict with pr-4, but pr-4 should not stand in conflict with px-3. To achieve this, we need to define asymmetric conflicts across class groups.
Defining asymmetric conflicts
This is what the conflictingClassGroups object in the tailwind-merge config is for. You define a key in it which is the ID of a class group which creates a conflict and the value is an array of IDs of class groups which receive a conflict.
const conflictingClassGroups = {
// โ px creates a conflict with pr and pl
px: ['pr', 'pl'],
}If a class group creates a conflict, it means that if it appears in a class list string passed to twMerge, all preceding class groups in the string which receive the conflict will be removed.
When we think of our example, the px class group creates a conflict which is received by the class groups pr and pl. This way px-3 removes a preceding pr-4, but not the other way around.
Common conflict patterns:
| Creates Conflict โ | Receives Conflict โ | Example |
|---|---|---|
px |
pl, pr |
twMerge('pr-2 px-4') // โ 'px-4' |
py |
pt, pb |
twMerge('pt-2 py-4') // โ 'py-4' |
p |
px, py, pt, pr, pb, pl |
twMerge('px-2 py-2 p-4') // โ 'p-4' |
inset |
top, right, bottom, left, inset-x, inset-y |
twMerge('top-0 inset-0') // โ 'inset-0' |
inset-x |
left, right |
twMerge('right-0 inset-x-0') // โ 'inset-x-0' |
inset-y |
top, bottom |
twMerge('top-0 inset-y-0') // โ 'inset-y-0' |
Postfix modifiers conflicting with class groups
Tailwind CSS allows postfix modifiers for some classes. E.g. you can set font-size and line-height together with text-lg/7 with /7 being the postfix modifier. This means that any line-height classes preceding a font-size class with a modifier should be removed.
For this tailwind-merge has the conflictingClassGroupModifiers object in its config with the same shape as conflictingClassGroups explained in the section above. This time the key is the ID of a class group whose modifier creates a conflict and the value is an array of IDs of class groups which receive the conflict.
const conflictingClassGroupModifiers = {
'font-size': ['leading'],
}Postfix lookup class groups
When a class contains a slash (/), tailwind-merge first treats the part after the slash as a possible postfix modifier. For example, text-lg/7 is resolved as the text-lg class with /7 as its postfix modifier.
In rare cases, the slash belongs to the full class name instead. The postfixLookupClassGroups config property lets you opt in class groups where tailwind-merge should also try resolving the full class name after resolving the part before the slash.
const postfixLookupClassGroups = ['container-type']This is needed for classes like @container-size/sidebar, where @container-size is a container type class, but @container-size/sidebar is a named container query class which should be resolved as a different class group.
Order-sensitive modifiers
In Tailwind CSS, not all modifiers behave the same when you stack them.
In most cases the order of modifiers doesn't matter. E.g. hover:focus:bg-red-500 and focus:hover:bg-red-500 behave the same and in the context of tailwind-merge, you'd want them both to override each other. tailwind-merge sorts the modifiers internally to be able to override classes with the same modifiers, even if they are in a different order.
However, there are some modifiers where the order matters, e.g. the direct children modifier *. The class *:hover:text-red-500 modifies the text color of a child if that particular child is hovered, but the class hover:*:text-red-500 modifies the text color of all direct children if the parent is hovered. In this case, you would want tailwind-merge to preserve both classes although they have the same modifiers, just in a different order.
To know which modifiers are order-sensitive, tailwind-merge has the orderSensitiveModifiers property in its config. twMerge is pre-configured with all the order-sensitive modifiers that Tailwind CSS has by default. You'll only need to configure this property if you add your own order-sensitive modifiers or change the meaning of the default order-sensitive modifiers.
Theme
In the Tailwind config you can modify your theme variable namespace to add classes with custom values. tailwind-merge follows the same naming scheme as Tailwind CSS for its theme scales:
| Tailwind CSS namespace | tailwind-merge theme key |
|---|---|
--color-* |
color |
--font-* |
font |
--text-* |
text |
--font-weight-* |
font-weight |
--tracking-* |
tracking |
--leading-* |
leading |
--breakpoint-* |
breakpoint |
--container-* |
container |
--spacing-* |
spacing |
--radius-* |
radius |
--shadow-* |
shadow |
--inset-shadow-* |
inset-shadow |
--text-shadow-* |
text-shadow |
--drop-shadow-* |
drop-shadow |
--blur-* |
blur |
--perspective-* |
perspective |
--aspect-* |
aspect |
--ease-* |
ease |
--animate-* |
animate |
If you modified one of the theme namespaces in your Tailwind config, you need to add the variable names to the theme object in tailwind-merge as well so that tailwind-merge knows about them.
Example: Adding custom font sizes
Let's say you added a custom font size variable --text-huge: 100px to your Tailwind config:
/* In your CSS or Tailwind config */
@theme {
--text-huge: 100px;
}This enables the class text-huge in your HTML. To make sure tailwind-merge merges these classes correctly, you need to configure tailwind-merge like this:
import { extendTailwindMerge } from 'tailwind-merge'
const customTwMerge = extendTailwindMerge({
extend: {
theme: {
// โ `text` is the key of the namespace `--text-*`
// โ `huge` is the variable name without the namespace prefix
text: ['huge'],
},
},
})
// Now tailwind-merge correctly handles your custom font size
customTwMerge('text-lg text-huge') // โ 'text-huge'
customTwMerge('text-huge text-sm') // โ 'text-sm'Example: Adding custom spacing values
For custom spacing in the --spacing-* namespace:
/* In your CSS or Tailwind config */
@theme {
--spacing-gutter: 1.5rem;
--spacing-section: 5rem;
}Configure tailwind-merge:
const customTwMerge = extendTailwindMerge({
extend: {
theme: {
spacing: ['gutter', 'section'],
},
},
})
// Now works with custom spacing values across all spacing utilities
customTwMerge('p-4 p-gutter') // โ 'p-gutter'
customTwMerge('mt-section mt-4') // โ 'mt-4'
customTwMerge('gap-2 gap-gutter') // โ 'gap-gutter'Note: The spacing theme scale is used by many utilities including padding (p-*), margin (m-*), gap (gap-*), top/right/bottom/left positioning, and more. Adding a value to the spacing theme makes it available across all these utilities.
Note about custom colors
Custom colors in the --color-* namespace do not need to be configured in tailwind-merge. The library uses a permissive validator that accepts any color name, so custom colors work out of the box:
/* In your CSS or Tailwind config */
@theme {
--color-brand-primary: #3b82f6;
--color-brand-secondary: #8b5cf6;
}import { twMerge } from 'tailwind-merge'
// Works without any configuration
twMerge('bg-blue-500 bg-brand-primary') // โ 'bg-brand-primary'
twMerge('text-brand-primary text-brand-secondary') // โ 'text-brand-secondary'
twMerge('border-custom-color border-brand-primary') // โ 'border-brand-primary'This applies to all color utilities: bg-*, text-*, border-*, ring-*, etc.
Extending the tailwind-merge config
If you only need to slightly modify the default tailwind-merge config, extendTailwindMerge is the easiest way to extend the config. You provide it a configExtension object which gets merged with the default config. Therefore, all keys here are optional.
import { extendTailwindMerge } from 'tailwind-merge'
const twMerge = extendTailwindMerge<'badge' | 'icon-size' | 'card'>({
// โ Override elements from the default config
// It has the same shape as the `extend` object, so we're going to skip it here.
override: {},
// โ Extend values from the default config
extend: {
// โ Add values to existing theme scale or create a new one
theme: {
spacing: ['sm', 'md', 'lg'],
},
// โ Add values to existing class groups or define new ones
classGroups: {
badge: ['badge', 'badge-pill', { 'badge-dot': ['', 'sm', 'lg'] }],
'icon-size': [{ icon: ['auto', (value) => Number(value) >= 16] }],
card: ['card-sm', 'card-md', 'card-lg'],
},
// โ Here you can define additional conflicts across class groups
conflictingClassGroups: {
badge: ['icon-size'],
},
// โ Define conflicts between postfix modifiers and class groups
conflictingClassGroupModifiers: {
card: ['icon-size'],
},
// โ Define order-sensitive modifiers
orderSensitiveModifiers: ['custom-variant'],
},
})[!Note]
The functionextendTailwindMergecomputes a large data structure based on the config passed to it. I recommend to call it only once and store the result in a top-level variable instead of calling it inline within another repeatedly called function.
TypeScript types for `extendTailwindMerge`
If you're using TypeScript, you'll notice that all the places in the configExtension object where class group IDs and theme group IDs are used are typed strictly so that you're only allowed to use IDs which are defined in the default config of tailwind-merge. The strict TypeScript types are meant as a safety net to prevent you from making typos in your config and to give you auto-completion for IDs.
In case you want to define new class groups or theme objects, you need to add the IDs as a generic argument to extendTailwindMerge:
import { extendTailwindMerge } from 'tailwind-merge'
type AdditionalClassGroupIDs = 'class-a' | 'class-b'
type AdditionalThemeGroupIDs = 'theme-c' | 'theme-d'
const twMerge = extendTailwindMerge<
// โ Add additional class group IDs as the first generic argument
AdditionalClassGroupIDs,
// โ Optionally, you can add additional theme group IDs as the second generic argument
AdditionalThemeGroupIDs
>({
extend: {
theme: {
// โ Works because we defined 'theme-c' as additional theme group ID
'theme-c': [โฆ],
// โ Works because we defined 'theme-d' as additional theme group ID
'theme-d': [โฆ],
},
classGroups: {
// โ Works because it's part of the default additional class group IDs
shadow: [โฆ],
// โ Works because we defined 'class-a' as additional class group ID
'class-a': [โฆ],
// โ Works because we defined 'class-b' as additional class group ID
'class-b': [โฆ],
// โ Type [โฆ] is not assignable to type [โฆ].
// Object literal may only specify known properties, and ''not-defined'' does not exist in type [โฆ]. ts(2322)
'not-defined': [],
},
},
})If those strict TypeScript types for IDs are too restrictive for you, you can also allow any strings as IDs by using the string type as generic argument.
import { extendTailwindMerge } from 'tailwind-merge'
const twMerge = extendTailwindMerge<string, string>(/* anything goes here */)Using completely custom tailwind-merge config
If you need to modify the tailwind-merge config and need more control than extendTailwindMerge gives you or don't want to use the default config (and tree-shake it out of your bundle), you can use createTailwindMerge.
The function takes a callback which returns the config you want to use and returns a custom twMerge function.
import { createTailwindMerge } from 'tailwind-merge'
const twMerge = createTailwindMerge(() => ({
cacheSize: 500,
theme: {},
classGroups: {
badge: ['badge', 'badge-pill', { 'badge-dot': ['', 'sm', 'lg'] }],
'icon-size': [{ icon: ['auto', (value) => Number(value) >= 16] }],
card: ['card-sm', 'card-md', 'card-lg'],
},
conflictingClassGroups: {
badge: ['icon-size'],
},
conflictingClassGroupModifiers: {
card: ['icon-size'],
},
orderSensitiveModifiers: [],
}))[!Note]
The functioncreateTailwindMergecomputes a large data structure based on the config passed to it. I recommend to call it only once and store the result in a top-level variable instead of calling it inline within another repeatedly called function.
The callback passed to createTailwindMerge will be called when twMerge is called the first time, so you don't need to worry about the computations in it affecting app startup performance in case you aren't using tailwind-merge at app startup.
Using tailwind-merge plugins
You can use both extendTailwindMerge and createTailwindMerge with third-party plugins. Just add them as arguments after your config.
import { extendTailwindMerge, createTailwindMerge } from 'tailwind-merge'
import { withMagic } from 'tailwind-merge-magic-plugin'
import { withMoreMagic } from 'tailwind-merge-more-magic-plugin'
// With your own config
const twMerge1 = extendTailwindMerge({ โฆ }, withMagic, withMoreMagic)
// Only using plugin with default config
const twMerge2 = extendTailwindMerge(withMagic, withMoreMagic)
// Using `createTailwindMerge`
const twMerge3 = createTailwindMerge(() => ({ โฆ }), withMagic, withMoreMagic)Next: Recipes
Previous: Limitations
File: docs/contributing.md
Contributing
Please see CONTRIBUTING for details.
Next: Similar packages
Previous: Versioning
File: docs/features.md
Features
Merging behavior
tailwind-merge is built to be intuitive. It follows a set of rules to determine which class wins when there are conflicts. Here is a brief overview of its conflict resolution.
Last conflicting class wins
twMerge('p-5 p-2 p-4') // โ 'p-4'Allows refinements
twMerge('p-3 px-5') // โ 'p-3 px-5'
twMerge('inset-x-4 right-4') // โ 'inset-x-4 right-4'Resolves non-trivial conflicts
twMerge('inset-x-px -inset-1') // โ '-inset-1'
twMerge('bottom-auto inset-y-6') // โ 'inset-y-6'
twMerge('inline block') // โ 'block'Supports modifiers and stacked modifiers
twMerge('p-2 hover:p-4') // โ 'p-2 hover:p-4'
twMerge('hover:p-2 hover:p-4') // โ 'hover:p-4'
twMerge('hover:focus:p-2 focus:hover:p-4') // โ 'focus:hover:p-4'tailwind-merge knows when the order of standard modifiers matters and when not and resolves conflicts accordingly.
Supports arbitrary values
twMerge('bg-black bg-(--my-color) bg-[color:var(--mystery-var)]')
// โ 'bg-[color:var(--mystery-var)]'
twMerge('grid-cols-[1fr,auto] grid-cols-2') // โ 'grid-cols-2'Type detection for arbitrary values
tailwind-merge automatically detects the type of arbitrary values in most cases, but sometimes explicit labels are needed.
Automatic detection (works out of the box):
// Length detected by calc() function
twMerge('text-[calc(1rem+2px)] text-lg') // โ 'text-lg'
// Length detected by unit
twMerge('text-[14px] text-[16px]') // โ 'text-[16px]'
// Color detected by hex format
twMerge('text-[#ff0000] text-[#00ff00]') // โ 'text-[#00ff00]'
// Color detected by color function
twMerge('bg-[rgb(255,0,0)] bg-[hsl(0,100%,50%)]') // โ 'bg-[hsl(0,100%,50%)]'When labels are necessary:
For ambiguous classes where tailwind-merge cannot determine the type from context (like text-* which can be font-size OR color), you need to use CSS data type labels:
// โ Without label - defaults to color
twMerge('text-[theme(myCustomScale.rebecca)] text-lg')
// โ 'text-[theme(myCustomScale.rebecca)] text-lg' (both kept, treated as different types)
// โ
With label - correctly identified as length/font-size
twMerge('text-[length:theme(myCustomScale.rebecca)] text-lg')
// โ 'text-lg' (conflicting font-size classes)Common labels you might need:
length:- for sizes/lengths (font-size, width, etc.)color:- for colorsposition:- for background-positionsize:- for background-sizenumber:- for numeric values (font-weight, z-index, etc.)
See Limitations for more details on when labels are required.
Supports arbitrary properties
twMerge('[mask-type:luminance] [mask-type:alpha]') // โ '[mask-type:alpha]'
twMerge('[--scroll-offset:56px] lg:[--scroll-offset:44px]')
// โ '[--scroll-offset:56px] lg:[--scroll-offset:44px]'
// Don't do this!
twMerge('[padding:1rem] p-8') // โ '[padding:1rem] p-8'[!Note]
tailwind-merge does not resolve conflicts between arbitrary properties and their matching Tailwind classes to keep the bundle size small.
Supports arbitrary variants
twMerge('[&:nth-child(3)]:py-0 [&:nth-child(3)]:py-4') // โ '[&:nth-child(3)]:py-4'
twMerge('dark:hover:[&:nth-child(3)]:py-0 hover:dark:[&:nth-child(3)]:py-4')
// โ 'hover:dark:[&:nth-child(3)]:py-4'
// Don't do this!
twMerge('[&:focus]:ring focus:ring-4') // โ '[&:focus]:ring focus:ring-4'[!Note]
Similarly to arbitrary properties, tailwind-merge does not resolve conflicts between arbitrary variants and their matching predefined modifiers for bundle size reasons.
The order of standard modifiers before and after an arbitrary variant in isolation (all modifiers before are one group, all modifiers after are another group) does not matter for tailwind-merge. However, it does matter whether a standard modifier is before or after an arbitrary variant both for Tailwind CSS and tailwind-merge because the resulting CSS selectors are different.
Supports important modifier
twMerge('p-3! p-4! p-5') // โ 'p-4! p-5'
twMerge('right-2! -inset-x-1!') // โ '-inset-x-1!'Supports postfix modifiers
twMerge('text-sm leading-6 text-lg/7') // โ 'text-lg/7'Preserves non-Tailwind classes
twMerge('p-5 p-2 my-non-tailwind-class p-4') // โ 'my-non-tailwind-class p-4'Supports custom colors out of the box
twMerge('text-red text-secret-sauce') // โ 'text-secret-sauce'Composition
tailwind-merge has some features that simplify composing class strings together. Those allow you to compose classes like in clsx, classnames or classix.
Supports multiple arguments
twMerge('some-class', 'another-class yet-another-class', 'so-many-classes')
// โ 'some-class another-class yet-another-class so-many-classes'Supports conditional classes
twMerge('some-class', undefined, null, false, 0) // โ 'some-class'
twMerge('my-class', false && 'not-this', null && 'also-not-this', true && 'but-this')
// โ 'my-class but-this'Supports arrays and nested arrays
twMerge('some-class', [undefined, ['another-class', false]], ['third-class'])
// โ 'some-class another-class third-class'
twMerge('hi', true && ['hello', ['hey', false]], false && ['bye'])
// โ 'hi hello hey'Why no object support? Read here.
Performance
tailwind-merge is optimized for speed when running in the browser. This includes the speed of loading the code and the speed of running the code.
Results are cached
Results get cached by default, so you don't need to worry about wasteful re-renders. The library uses a computationally lightweight LRU cache which stores up to 500 different results by default. The cache is applied after all arguments are joined together to a single string. This means that if you call twMerge repeatedly with different arguments that result in the same string when joined, the cache will be hit.
The cache size can be modified or opt-out of by using extendTailwindMerge.
Data structures are reused between calls
Expensive computations happen upfront so that twMerge calls without a cache hit stay fast.
Lazy initialization
The initial computations are called lazily on the first call to twMerge to prevent it from impacting app startup performance if it isn't used initially.
Next: Limitations
Previous: What is it for
File: docs/limitations.md
Limitations
tailwind-merge is designed to work intelligently with Tailwind CSS classes, but there are some limitations and edge cases to be aware of.
Don't use classes that look like Tailwind classes but apply different styles
tailwind-merge applies some heuristics to detect the type of a class even if that particular class does not exist in the default Tailwind config. E.g. the class text-1000xl does not exist in Tailwind CSS by default but is treated like a font-size class in tailwind-merge because it starts with text- followed by an optional number and a T-shirt size, like all the other font-size classes.
This behavior has the advantage that you're less likely to need to configure tailwind-merge if you're only changing or extending some scales in your Tailwind config. But it also means that tailwind-merge treats classes that look like Tailwind classes as Tailwind classes although they might not be defined in your Tailwind config.
Example of potential issues:
import { twMerge } from 'tailwind-merge'
// โ Problem: If you create a custom class that matches a Tailwind pattern
// Tailwind detects T-shirt sizes with optional numbers: xs, sm, md, lg, xl, 2xl, 3xl, etc.
// So even non-existent sizes like text-10xl are treated as font-size classes:
twMerge('text-lg text-10xl') // โ 'text-10xl'
// text-lg is removed because text-10xl matches the font-size pattern (text + number + xl)
// This is especially problematic if you want to create custom utility classes
// that happen to match these patterns. For example, if you have a custom
// `text-2xs` class that applies specific styles (not just font-size):
twMerge('text-sm text-2xs') // โ 'text-2xs'
// text-sm is removed even if your text-2xs does completely different thingsHow to avoid this:
- Use prefixes that don't match Tailwind patterns - Instead of
text-2xs, usetypography-2xs - Use component-specific prefixes - Prefix custom utilities with
app-,ui-, or your project name - Don't merge custom component classes - Keep your custom utility classes separate from classes that are passed through
twMerge
// โ
Good: Use non-conflicting naming
twMerge('text-lg typography-custom') // โ 'text-lg typography-custom'
twMerge('text-sm ui-text-special') // โ 'text-sm ui-text-special'
// โ
Good: Keep custom classes separate from merging
<Component className={twJoin(twMerge('text-lg', props.className), 'text-custom')} />You need to use labels in ambiguous arbitrary value classes
tailwind-merge detects the type of class by parsing the class name. Some arbitrary value patterns are ambiguous and need explicit labels.
Arbitrary `font-weight` and `font-family` classes
Both font-weight and font-family use the font-* prefix in Tailwind CSS. When using arbitrary variables without labels, tailwind-merge cannot determine whether the class sets font-weight or font-family, so it defaults to treating unlabeled arbitrary variables as font-weight. This matches Tailwind CSS behavior, which also defaults to font-weight for ambiguous font-* classes.
// โ ๏ธ Unlabeled arbitrary variables default to font-weight
twMerge('font-(--my-family) font-(--my-weight)')
// โ 'font-(--my-weight)' (both treated as font-weight, first one removed)
// โ
Use `family-name:` label for font-family
twMerge('font-(family-name:--my-family) font-(--my-weight)')
// โ 'font-(family-name:--my-family) font-(--my-weight)' (both kept)Required labels:
font-family: usefamily-name:prefix (e.g.,font-(family-name:--my-font))font-weight: no label needed, but you can useweight:ornumber:for clarity (e.g.,font-(weight:--my-weight))
The same applies to arbitrary values with square brackets:
// โ ๏ธ Unlabeled arbitrary values default to font-weight
twMerge('font-[var(--family)] font-[var(--weight)]')
// โ 'font-[var(--weight)]'
// โ
Use explicit labels
twMerge('font-[family-name:var(--family)] font-[var(--weight)]')
// โ 'font-[family-name:var(--family)] font-[var(--weight)]'Arbitrary `background-position` and `background-size` classes
When using a class like bg-[30%_30%], tailwind-merge can't determine whether the class is a background-position or background-size class.
// โ Ambiguous - could be position or size
// Without a label, tailwind-merge cannot determine the type
twMerge('bg-[30%_30%] bg-cover') // โ 'bg-[30%_30%] bg-cover'
// โ
Use explicit labels
twMerge('bg-[position:30%_30%] bg-cover') // โ 'bg-[position:30%_30%] bg-cover'
twMerge('bg-[size:30%_30%] bg-cover') // โ 'bg-cover'Required labels:
background-position: useposition:prefix (e.g.,bg-[position:30%_30%])background-size: uselength:,size:, orpercentage:prefix (e.g.,bg-[size:200px_100px])
Arbitrary values in `text-*` classes
The text-* prefix is used for both font-size and text-color. For arbitrary values, tailwind-merge tries to infer the type, but explicit labels help in ambiguous cases:
// โ
Clear cases (no label needed)
twMerge('text-[12px] text-[16px]') // โ 'text-[16px]' (detected as font-size)
twMerge('text-[#ff0000] text-[#00ff00]') // โ 'text-[#00ff00]' (detected as color)
// โ ๏ธ Ambiguous cases (label recommended)
twMerge('text-[theme(myCustomScale.value)] text-lg')
// โ 'text-[theme(myCustomScale.value)] text-lg' (without label, defaults to color interpretation)
// โ
Use explicit label for clarity
twMerge('text-[length:theme(myCustomScale.value)] text-lg') // โ 'text-lg'Arbitrary properties don't merge with standard classes
tailwind-merge does not resolve conflicts between arbitrary properties and their matching Tailwind classes to keep bundle size small.
// โ These won't conflict with each other
twMerge('p-4 [padding:1rem]') // โ 'p-4 [padding:1rem]' (both kept)
// โ
Use standard Tailwind classes when possible
twMerge('p-4 p-8') // โ 'p-8' (correctly merged)Why this limitation exists: Parsing arbitrary property names and determining their conflicts would require including CSS property knowledge in the bundle, significantly increasing the library size.
Arbitrary variants don't merge with standard modifiers
Similar to arbitrary properties, arbitrary variants don't conflict with standard modifiers:
// โ These won't conflict
twMerge('[&:focus]:ring focus:ring-4') // โ '[&:focus]:ring focus:ring-4' (both kept)
// โ
Use standard modifiers when possible
twMerge('focus:ring focus:ring-4') // โ 'focus:ring-4' (correctly merged)Doesn't understand custom CSS
tailwind-merge only understands Tailwind classes and doesn't analyze your custom CSS:
// Custom classes are preserved but not understood
twMerge('my-custom-padding p-4') // โ 'my-custom-padding p-4'
// Even if my-custom-padding sets padding, tailwind-merge can't know thatIf you have custom classes created with @apply or custom CSS that conflict with Tailwind classes, see the recipes documentation for alternatives.
Performance with extremely long class strings
While tailwind-merge is optimized for typical use cases and includes caching, processing extremely long class strings (1000+ classes) may have performance implications in hot paths:
// โ ๏ธ Avoid in frequently called render functions if classList is huge
const veryLongClassList = Array(1000).fill('p-2 p-3').join(' ')
twMerge(veryLongClassList) // Works, but slower with cache missesFor most real-world use cases, this is not an issue due to the LRU cache.
Next: Configuration
Previous: Features
File: docs/README.md
tailwind-merge
Utility function to efficiently merge Tailwind CSS classes in JS without style conflicts.
import { twMerge } from 'tailwind-merge'
twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
// โ 'hover:bg-dark-red p-3 bg-[#B91C1C]'- Supports Tailwind v4.0 up to v4.3 (if you use Tailwind v3, use tailwind-merge v2.6.0)
- Works in all modern browsers and maintained Node versions
- Fully typed, with published declarations compatible with TypeScript 3.8 and newer
- Check bundle size on Bundlephobia
Get started
- What is it for
- When and how to use it
- Features
- Limitations
- Configuration
- Recipes
- API reference
- Writing plugins
- Versioning
- Contributing
- Similar packages
File: docs/recipes.md
Recipes
How to configure tailwind-merge with some common patterns.
Adding custom scale from Tailwind config to tailwind-merge config
I have a custom shadow scale with the keys 100, 200 and 300 configured in Tailwind. How do I make tailwind-merge resolve conflicts among those?
We'll be able to do this by creating a custom twMerge function with extendTailwindMerge.
First, we need to know whether we want to override or extend the default scale. Let's say we extended the default config by adding the CSS variable --shadow-100, --shadow-200 and --shadow-300 into the @theme layer, meaning that the default variables like --shadow-sm stay the same.
Then we check whether our particular theme scale is included in tailwind-merge's theme config object here. Because tailwind-merge supports Tailwind's shadow theme scale, we can add it to the tailwind-merge config like this:
import { extendTailwindMerge } from 'tailwind-merge'
const twMerge = extendTailwindMerge({
extend: {
theme: {
// We only need to define the custom scale values without the `shadow-` prefix when adding them to the theme object
shadow: ['100', '200', '300'],
},
},
})In the hypothetical case of the shadow theme scale not being supported in tailwind-merge, we would need to check out the default config of tailwind-merge and search for the class group ID of the box shadow scale. After a quick search we would find that tailwind-merge is using the key shadow for that group. We could add our custom classes to that group like this:
import { extendTailwindMerge } from 'tailwind-merge'
const twMerge = extendTailwindMerge({
extend: {
classGroups: {
// In class groups we always need to define the entire class name like `shadow-100`, `shadow-200` and `shadow-300`
// `{ shadow: ['100', '200', '300'] }` is a short-hand syntax for `'shadow-100', 'shadow-200', 'shadow-300'`
shadow: [{ shadow: ['100', '200', '300'] }],
},
},
})Note that by using the extend object we're only adding our custom classes to the existing ones in the config, so twMerge('shadow-200 shadow-lg') will return the string shadow-lg. If we want to override the class instead, we need to use the override object instead.
Extracting classes with Tailwind's [`@apply`](https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply)
How do I make tailwind-merge resolve conflicts with a custom class created with
@apply?css.btn-primary { @apply py-2 px-4 bg-blue-500 text-white rounded-lg hover:bg-blue-700; }
I don't recommend using Tailwind's @apply directive for classes that might get processed with tailwind-merge.
tailwind-merge would need to be configured so that it knows about which classes .btn-primary is in conflict with. This means: If someone adds another Tailwind class to the @apply directive, the tailwind-merge config would need to get modified accordingly, keeping it in sync with the written CSS. This easy-to-miss dependency is fragile and can lead to bugs with incorrect merging behavior.
Instead of creating custom CSS classes, I recommend keeping the collection of Tailwind classes in a string variable in JavaScript and access it whenever you want to apply those styles. This way you can reuse the collection of styles but don't need to touch the tailwind-merge config.
// React components with JSX syntax used in this example
import { twMerge } from 'tailwind-merge'
const BTN_PRIMARY_CLASSNAMES = 'py-2 px-4 bg-blue-500 text-white rounded-lg hover:bg-blue-700'
function ButtonPrimary(props) {
return
}Modifying inputs and output of `twMerge`
How do I make
twMergeaccept the same argument types as clsx/classnames?
You can wrap twMerge in another function which can modify the inputs and/or output.
import { twMerge as twMergeOriginal } from 'tailwind-merge'
function twMerge(...inputs) {
const modifiedInputs = modifyInputs(inputs)
return twMergeOriginal(modifiedInputs)
}Next: API reference
Previous: Configuration
File: docs/similar-packages.md
Similar packages
[!Note]
If you know of a package that isn't listed here, feel free to submit a PR adding the package to this page.
TypeScript/JavaScript
Other languages
- tailwind_merge (Ruby)
- Twix (Elixir)
- yieldstudio/tailwind-merge-php (PHP)
- gehrisandro/tailwind-merge-php (PHP)
- tailwind-merge-laravel (Laravel, PHP)
- tailwind-merge-go (Golang)
- tailwind-merge-dotnet (C#)
Previous: Contributing
File: docs/versioning.md
Versioning
This package follows the SemVer versioning rules. More specifically:
Patch version gets incremented when unintended behavior is fixed, which doesn't break any existing API. Note that bug fixes can still alter which styles are applied. E.g. a bug gets fixed in which the conflicting classes
inlineandblockweren't merged correctly so that both would end up in the result.Minor version gets incremented when additional features are added which don't break any existing API. However, a minor version update might still alter which styles are applied if you use Tailwind features not yet supported by tailwind-merge. E.g. a new Tailwind prefix
magicgets added to this package which changes the result oftwMerge('magic:px-1 magic:p-3')frommagic:px-1 magic:p-3tomagic:p-3.Major version gets incremented when breaking changes are introduced to the package API. E.g. the return type of
twMergechanges.Published type declarations support TypeScript 3.8 and newer. The TypeScript version used to build tailwind-merge is an internal development detail and can be newer than the versions used by consumers. Increasing the minimum supported consumer TypeScript version is a breaking change and therefore requires a new major version.
alphareleases might introduce breaking changes on any update.betareleases intend to only introduce new features or bug fixes, but can introduce breaking changes in rare cases.Any API that has
experimentalin its name can introduce breaking changes in any minor version update.Releases with major version 0 might introduce breaking changes on a minor version update.
A non-production-ready version of every commit pushed to the main branch is released under the
devtag for testing purposes. It has a format like1.6.1-dev.4202ccf913525617f19fbc493db478a76d64d054in which the first numbers are the corresponding last release and the hash at the end is the git SHA of the commit. You can install the latest dev release withnpm install tailwind-merge@dev.A changelog is documented in GitHub Releases.
Next: Contributing
Previous: Writing plugins