Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 69 additions & 24 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,35 +325,80 @@ interface Validators {
}
```

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). E.g. the `classGroup` for padding is defined as
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] }]
```

A brief summary for each validator:

- `isAny` always returns true. Be careful with this validator as it might match unwanted classes. I use it primarily to match colors or when I'm certain there are no other class groups in a namespace.
- `isAnyNonArbitrary` checks if the class part is not an arbitrary value or arbitrary variable.
- `isArbitraryImage` checks whether class part is an arbitrary value which is an image, e.g. by starting with `image:`, `url:`, `linear-gradient(` or `url(` (`[url('/path-to-image.png')]`, `image:var(--maybe-an-image-at-runtime)]`) which is necessary for background-image classNames.
- `isArbitraryLength` checks for arbitrary length values (`[3%]`, `[4px]`, `[length:var(--my-var)]`).
- `isArbitraryNumber` checks whether class part is an arbitrary value which starts with `number:` or is a number (`[number:var(--value)]`, `[450]`) which is necessary for font-weight and stroke-width classNames.
- `isArbitraryPosition` checks whether class part is an arbitrary value which starts with `position:` (`[position:200px_100px]`) which is necessary for background-position classNames.
- `isArbitraryShadow` checks whether class part is an arbitrary value which starts with the same pattern as a shadow value (`[0_35px_60px_-15px_rgba(0,0,0,0.3)]`), namely with two lengths separated by a underscore, optionally prepended by `inset`.
- `isArbitrarySize` checks whether class part is an arbitrary value which starts with `size:` (`[size:200px_100px]`) which is necessary for background-size classNames.
- `isArbitraryValue` checks whether the class part is enclosed in brackets (`[something]`)
- `isArbitraryVariable` checks whether the class part is an arbitrary variable (`(--my-var)`)
- `isArbitraryVariableFamilyName` checks whether class part is an arbitrary variable with the `family-name` label (`(family-name:--my-font)`)
- `isArbitraryVariableImage` checks whether class part is an arbitrary variable with the `image` or `url` label (`(image:--my-image)`)
- `isArbitraryVariableLength` checks whether class part is an arbitrary variable with the `length` label (`(length:--my-length)`)
- `isArbitraryVariablePosition` checks whether class part is an arbitrary variable with the `position` label (`(position:--my-position)`)
- `isArbitraryVariableShadow` checks whether class part is an arbitrary variable with the `shadow` label or not label at all (`(shadow:--my-shadow)`, `(--my-shadow)`)
- `isArbitraryVariableSize` checks whether class part is an arbitrary variable with the `size`, `length` or `percentage` label (`(size:--my-size)`)
- `isFraction` checks whether class part is a fraction of two numbers (`1/2`, `127/256`)
- `isInteger` checks for integer values (`3`).
- `isNumber` checks for numbers (`3`, `1.5`)
- `isPercent` checks for percent values (`12.5%`) which is used for color stop positions.
- `isTshirtSize` checks whether class part is a T-shirt size (`sm`, `xl`), optionally with a preceding number (`2xl`).
### 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` |
| `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 |
| `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 |
| `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`

Expand Down
108 changes: 100 additions & 8 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,28 @@ Sometimes there are conflicts across Tailwind classes which are more complex tha

One example is the combination of the classes `px-3` (setting `padding-left` and `padding-right`) and `pr-4` (setting `padding-right`).

If they are passed to `twMerge` as `pr-4 px-3`, you most likely intend to apply `padding-left` and `padding-right` from the `px-3` class and want `pr-4` to be removed, indicating that both these classes should belong to a single class group.
**Scenario 1**: When classes are ordered as `pr-4 px-3`:

But if they are passed to `twMerge` as `px-3 pr-4`, you want to set the `padding-right` from `pr-4` but still want to apply the `padding-left` from `px-3`, so `px-3` shouldn't be removed when inserting the classes in this order, indicating they shouldn't be in the same class group.
- 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'`

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.
**Scenario 2**: When classes are ordered as `px-3 pr-4`:

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 group which _receive_ a conflict.
- 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'],
}
```
Expand All @@ -125,6 +137,17 @@ If a class group _creates_ a conflict, it means that if it appears in a class li

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.
Expand Down Expand Up @@ -174,22 +197,91 @@ In the Tailwind config you can modify your theme variable namespace to add class

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.

E.g. let's say you added the variable `--text-huge-af: 100px` to your Tailwind config which enables classes like `text-huge-af`. To make sure that tailwind-merge merges these classes correctly, you need to configure tailwind-merge like this:
#### 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 twMerge = extendTailwindMerge({
const customTwMerge = extendTailwindMerge({
extend: {
theme: {
// ↓ `text` is the key of the namespace `--text-*`
// ↓ `huge-af` is the variable name in the namespace
text: ['huge-af'],
// ↓ `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.
Expand Down
Loading
Loading