## File: README.md

# tailwind-merge

Utility function to efficiently merge [Tailwind CSS](https://tailwindcss.com) classes in JS without style conflicts.

```ts
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](https://github.com/dcastil/tailwind-merge/tree/v2.6.0))
- Works in all modern browsers and maintained Node versions
- Fully typed
- [Check bundle size on Bundlephobia](https://bundlephobia.com/package/tailwind-merge)

## Get started

- [What is it for](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/what-is-it-for.md)
- [When and how to use it](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/when-and-how-to-use-it.md)
- [Features](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/features.md)
- [Limitations](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/limitations.md)
- [Configuration](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/configuration.md)
- [Recipes](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/recipes.md)
- [API reference](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/api-reference.md)
- [Writing plugins](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/writing-plugins.md)
- [Versioning](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/versioning.md)
- [Contributing](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/contributing.md)
- [Similar packages](https://github.com/dcastil/tailwind-merge/blob/v3.6.0/docs/similar-packages.md)

---

## File: docs/api-reference.md

# API reference

Reference to all exports of tailwind-merge.

## `twMerge`

```ts
function twMerge(
    ...classLists: Array<string | undefined | null | false | 0 | typeof classLists>
): string
```

Default function to use if you're using the default Tailwind config or are close enough to the default config. Check out [basic usage](./configuration.md#basic-usage) for more info.

If `twMerge` doesn't work for you, you can create your own custom merge function with [`extendTailwindMerge`](#extendtailwindmerge).

## `twJoin`

```ts
function twJoin(
    ...classLists: Array<string | undefined | null | false | 0 | typeof classLists>
): string
```

Function to join className strings conditionally without resolving conflicts.

```ts
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`](https://www.npmjs.com/package/clsx). If you use `clsx` or [`classnames`](https://www.npmjs.com/package/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](https://github.com/dcastil/tailwind-merge/discussions/137#discussioncomment-3481605).

## `getDefaultConfig`

```ts
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`

```ts
function fromTheme<
    AdditionalThemeGroupIds extends string = never,
    DefaultThemeGroupIdsInner extends string = DefaultThemeGroupIds,
>(key: NoInfer<DefaultThemeGroupIdsInner | AdditionalThemeGroupIds>): ThemeGetter
```

Function 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:

```ts
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`

```ts
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)[]): TailwindMerge
```

Function 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 function `extendTailwindMerge` computes 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](#mergeconfigs) 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`](#createtailwindmerge)) which is convenient if you want to combine your config with third-party plugins.

```ts
const twMerge = extendTailwindMerge({ … }, withSomePlugin)
```

If you only use plugins, you can omit the `configExtension` object as well.

```ts
const twMerge = extendTailwindMerge(withSomePlugin)
```

## `createTailwindMerge`

```ts
function createTailwindMerge(
    ...createConfig: [() => Config, ...Array<(config: Config) => Config>]
): TailwindMerge
```

Function to create merge function with custom config. Use this function instead of [`extendTailwindMerge`](#extendtailwindmerge) if you don't need the default config or want more control over the config.

> [!Note]
> The function `createTailwindMerge` computes 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.

```ts
// ↓ 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`](#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.

```ts
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`](#mergeconfigs) instead.

## `mergeConfigs`

```ts
function mergeConfigs<ClassGroupIds extends string, ThemeGroupIds extends string = never>(
    baseConfig: GenericConfig,
    configExtension: ConfigExtension<ClassGroupIds, ThemeGroupIds>,
): GenericConfig
```

Helper 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.

```ts
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`

```ts
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`](#extendtailwindmerge) or [`createTailwindMerge`](#createtailwindmerge).

**Example usage**:

```ts
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

```ts
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`

```ts
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`](#extendtailwindmerge) or [`createTailwindMerge`](#createtailwindmerge).

## `DefaultClassGroupIds`

```ts
type DefaultClassGroupIds = 'accent' | 'align-content' | 'align-items' | …
```

TypeScript type for all class group IDs defined in the default config of tailwind-merge.

## `DefaultThemeGroupIds`

```ts
type DefaultThemeGroupIds = 'blur' | 'borderColor' | 'borderRadius' | …
```

TypeScript type for all theme group IDs defined in the default config of tailwind-merge.

## `ClassNameValue`

```ts
type ClassNameValue = string | null | undefined | 0 | false | ClassNameValue[]
```

TypeScript type for arguments accepted by [`twMerge`](#twmerge) and [`twJoin`](#twjoin). You might want to use it if you wrap any of those with your own function.

```ts
function myWrappedTwMerge(...args: ClassNameValue[]) {
    doSomething()
    return twMerge(...args)
}
```

## `ClassValidator`

```ts
type ClassValidator = (value: string) => boolean
```

TypeScript type for class validators accepted in class definitions within [`extendTailwindMerge`](#extendtailwindmerge) and [`createTailwindMerge`](#createtailwindmerge).

---

Next: [Writing plugins](./writing-plugins.md)

Previous: [Recipes](./recipes.md)

[Back to overview](./README.md)

---

## File: docs/configuration.md

# Configuration

## Installation

The tailwind-merge package is hosted on npm under the name [`tailwind-merge`](https://www.npmjs.com/package/tailwind-merge). There are lots of package managers for installing packages hosted on npm. Here are installation instructions for the most popular ones: [npm](https://npmjs.com), [yarn](https://yarnpkg.com), [pnpm](https://pnpm.io) and [bun](https://bun.sh).

```sh
npm add tailwind-merge
yarn add tailwind-merge
pnpm add tailwind-merge
bun add tailwind-merge
```

## Basic usage

If you're using Tailwind CSS without any extra config, you can use [`twMerge`](./api-reference.md#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`](./api-reference.md#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.

```ts
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.

```ts
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.

```ts
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`.

```ts
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.

```ts
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](./api-reference.md#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.

```ts
// ↓ 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-3` to apply both `padding-left` and `padding-right`
- The earlier `pr-4` should be removed since `px-3` also sets `padding-right`
- Result: `twMerge('pr-4 px-3') // → 'px-3'`

**Scenario 2**: When classes are ordered as `px-3 pr-4`:

- You want `px-3` to set `padding-left`
- You want `pr-4` to override just the `padding-right` from `px-3`
- The `px-3` class 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.

```ts
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](#conflicting-class-groups). 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.

```ts
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.

```ts
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:

```css
/* 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:

```ts
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:

```css
/* In your CSS or Tailwind config */
@theme {
    --spacing-gutter: 1.5rem;
    --spacing-section: 5rem;
}
```

Configure tailwind-merge:

```ts
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:

```css
/* In your CSS or Tailwind config */
@theme {
    --color-brand-primary: #3b82f6;
    --color-brand-secondary: #8b5cf6;
}
```

```ts
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`](./api-reference.md#extendtailwindmerge) is the easiest way to extend the config. You provide it a `configExtension` object which gets [merged](./api-reference.md#mergeconfigs) with the default config. Therefore, all keys here are optional.

```ts
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 function `extendTailwindMerge` computes 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`:

```ts
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.

```ts
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`](./api-reference.md#extendtailwindmerge) gives you or don't want to use the default config (and tree-shake it out of your bundle), you can use [`createTailwindMerge`](./api-reference.md#createtailwindmerge).

The function takes a callback which returns the config you want to use and returns a custom `twMerge` function.

```ts
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 function `createTailwindMerge` computes 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`](./api-reference.md#extendtailwindmerge) and [`createTailwindMerge`](./api-reference.md#createtailwindmerge) with third-party plugins. Just add them as arguments after your config.

```ts
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](./recipes.md)

Previous: [Limitations](./limitations.md)

[Back to overview](./README.md)

---

## File: docs/contributing.md

# Contributing

Please see [CONTRIBUTING](../.github/CONTRIBUTING.md) for details.

---

Next: [Similar packages](./similar-packages.md)

Previous: [Versioning](./versioning.md)

[Back to overview](./README.md)

---

## 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

```ts
twMerge('p-5 p-2 p-4') // → 'p-4'
```

### Allows refinements

```ts
twMerge('p-3 px-5') // → 'p-3 px-5'
twMerge('inset-x-4 right-4') // → 'inset-x-4 right-4'
```

### Resolves non-trivial conflicts

```ts
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

```ts
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

```ts
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)**:

```ts
// 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](https://tailwindcss.com/docs/adding-custom-styles#resolving-ambiguities):

```ts
// ❌ 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 colors
- `position:` - for background-position
- `size:` - for background-size
- `number:` - for numeric values (font-weight, z-index, etc.)

See [Limitations](./limitations.md#you-need-to-use-labels-in-ambiguous-arbitrary-value-classes) for more details on when labels are required.

### Supports arbitrary properties

```ts
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

```ts
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

```ts
twMerge('p-3! p-4! p-5') // → 'p-4! p-5'
twMerge('right-2! -inset-x-1!') // → '-inset-x-1!'
```

### Supports postfix modifiers

```ts
twMerge('text-sm leading-6 text-lg/7') // → 'text-lg/7'
```

### Preserves non-Tailwind classes

```ts
twMerge('p-5 p-2 my-non-tailwind-class p-4') // → 'my-non-tailwind-class p-4'
```

### Supports custom colors out of the box

```ts
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](https://www.npmjs.com/package/clsx), [classnames](https://www.npmjs.com/package/classnames) or [classix](https://www.npmjs.com/package/classix).

### Supports multiple arguments

```ts
twMerge('some-class', 'another-class yet-another-class', 'so-many-classes')
// → 'some-class another-class yet-another-class so-many-classes'
```

### Supports conditional classes

```ts
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

```ts
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](https://github.com/dcastil/tailwind-merge/discussions/137#discussioncomment-3481605).

## 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](<https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)>) which stores up to 500 different results by default. The cache is applied after all arguments are [joined](./api-reference.md#twjoin) 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`](./api-reference.md#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](./limitations.md)

Previous: [What is it for](./what-is-it-for.md)

[Back to overview](./README.md)

---

## 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**:

```ts
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 things
```

**How to avoid this**:

1. **Use prefixes that don't match Tailwind patterns** - Instead of `text-2xs`, use `typography-2xs`
2. **Use component-specific prefixes** - Prefix custom utilities with `app-`, `ui-`, or your project name
3. **Don't merge custom component classes** - Keep your custom utility classes separate from classes that are passed through `twMerge`

```ts
// ✅ 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.

```ts
// ⚠️ 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`: use `family-name:` prefix (e.g., `font-(family-name:--my-font)`)
- `font-weight`: no label needed, but you can use `weight:` or `number:` for clarity (e.g., `font-(weight:--my-weight)`)

The same applies to arbitrary values with square brackets:

```ts
// ⚠️ 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.

```ts
// ❌ 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`: use `position:` prefix (e.g., `bg-[position:30%_30%]`)
- `background-size`: use `length:`, `size:`, or `percentage:` 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:

```ts
// ✅ 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.

```ts
// ❌ 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:

```ts
// ❌ 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:

```ts
// 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 that
```

If you have custom classes created with `@apply` or custom CSS that conflict with Tailwind classes, see the [recipes documentation](./recipes.md#extracting-classes-with-tailwinds-apply) 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:

```ts
// ⚠️ 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 misses
```

For most real-world use cases, this is not an issue due to the LRU cache.

---

Next: [Configuration](./configuration.md)

Previous: [Features](./features.md)

[Back to overview](./README.md)

---

## File: docs/README.md

# tailwind-merge

Utility function to efficiently merge [Tailwind CSS](https://tailwindcss.com) classes in JS without style conflicts.

```ts
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](https://github.com/dcastil/tailwind-merge/tree/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](https://bundlephobia.com/package/tailwind-merge)

## Get started

- [What is it for](./what-is-it-for.md)
- [When and how to use it](./when-and-how-to-use-it.md)
- [Features](./features.md)
- [Limitations](./limitations.md)
- [Configuration](./configuration.md)
- [Recipes](./recipes.md)
- [API reference](./api-reference.md)
- [Writing plugins](./writing-plugins.md)
- [Versioning](./versioning.md)
- [Contributing](./contributing.md)
- [Similar packages](./similar-packages.md)

---

## 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`](./api-reference.md#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](./configuration.md#theme). Because tailwind-merge supports Tailwind's `shadow` theme scale, we can add it to the tailwind-merge config like this:

```js
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](../src/lib/default-config.ts) 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:

```js
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.

```jsx
// 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 `twMerge` accept the same argument types as clsx/classnames?

You can wrap `twMerge` in another function which can modify the inputs and/or output.

```js
import { twMerge as twMergeOriginal } from 'tailwind-merge'

function twMerge(...inputs) {
    const modifiedInputs = modifyInputs(inputs)
    return twMergeOriginal(modifiedInputs)
}
```

---

Next: [API reference](./api-reference.md)

Previous: [Configuration](./configuration.md)

[Back to overview](./README.md)

---

## 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

-   [@robit-dev/tailwindcss-class-combiner](https://www.npmjs.com/package/@robit-dev/tailwindcss-class-combiner)
-   [tailshake](https://www.npmjs.com/package/tailshake)
-   [tailwind-classlist](https://www.npmjs.com/package/tailwind-classlist)
-   [tailwind-override](https://www.npmjs.com/package/tailwind-override)

## Other languages

-   [tailwind_merge](https://rubygems.org/gems/tailwind_merge) (Ruby)
-   [Twix](https://hex.pm/packages/twix) (Elixir)
-   [yieldstudio/tailwind-merge-php](https://packagist.org/packages/yieldstudio/tailwind-merge-php) (PHP)
-   [gehrisandro/tailwind-merge-php](https://packagist.org/packages/gehrisandro/tailwind-merge-php) (PHP)
-   [tailwind-merge-laravel](https://packagist.org/packages/gehrisandro/tailwind-merge-laravel) (Laravel, PHP)
-   [tailwind-merge-go](https://github.com/Oudwins/tailwind-merge-go) (Golang)
-   [tailwind-merge-dotnet](https://github.com/desmondinho/tailwind-merge-dotnet) (C#)

---

Previous: [Contributing](./contributing.md)

[Back to overview](./README.md)

---

## File: docs/versioning.md

# Versioning

This package follows the [SemVer](https://semver.org) 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 `inline` and `block` weren'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 `magic` gets added to this package which changes the result of `twMerge('magic:px-1 magic:p-3')` from `magic:px-1 magic:p-3` to `magic:p-3`.

-   Major version gets incremented when breaking changes are introduced to the package API. E.g. the return type of `twMerge` changes.

-   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.

-   `alpha` releases might introduce breaking changes on any update. `beta` releases intend to only introduce new features or bug fixes, but can introduce breaking changes in rare cases.

-   Any API that has `experimental` in 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 `dev` tag for testing purposes. It has a format like [`1.6.1-dev.4202ccf913525617f19fbc493db478a76d64d054`](https://www.npmjs.com/package/tailwind-merge/v/1.6.1-dev.4202ccf913525617f19fbc493db478a76d64d054) in 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 with `npm install tailwind-merge@dev`.

-   A changelog is documented in [GitHub Releases](https://github.com/dcastil/tailwind-merge/releases).

---

Next: [Contributing](./contributing.md)

Previous: [Writing plugins](./writing-plugins.md)

[Back to overview](./README.md)