## 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 ``` 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 ``` 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 ``` 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): 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('anything-goes-here')`. `fromTheme` can be used like this: ```ts type AdditionalClassGroupIds = 'badge' | 'badge-color' type AdditionalThemeGroupIds = 'custom-color' extendTailwindMerge({ extend: { theme: { 'custom-color': ['primary', 'secondary'], }, classGroups: { badge: [{ badge: [fromTheme('text')] }], 'badge-color': [{ badge: [fromTheme('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( baseConfig: GenericConfig, configExtension: ConfigExtension, ): 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 { … } ``` 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(/* 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]() 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 ``` ## 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)