Skip to content

Commit db06f11

Browse files
authored
Merge pull request dcastil#618 from dcastil/docs/improve-docs
Docs: Improve docs by clarifying things, adding more examples
2 parents 9f6317d + d2cc60c commit db06f11

File tree

5 files changed

+336
-43
lines changed

5 files changed

+336
-43
lines changed

docs/api-reference.md

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -325,35 +325,80 @@ interface Validators {
325325
}
326326
```
327327
328-
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
328+
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).
329+
330+
**Example usage**:
329331
330332
```ts
331333
const paddingClassGroup = [{ p: [validators.isNumber] }]
334+
const customImageGroup = [{ 'custom-img': [validators.isArbitraryImage] }]
332335
```
333336
334-
A brief summary for each validator:
335-
336-
- `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.
337-
- `isAnyNonArbitrary` checks if the class part is not an arbitrary value or arbitrary variable.
338-
- `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.
339-
- `isArbitraryLength` checks for arbitrary length values (`[3%]`, `[4px]`, `[length:var(--my-var)]`).
340-
- `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.
341-
- `isArbitraryPosition` checks whether class part is an arbitrary value which starts with `position:` (`[position:200px_100px]`) which is necessary for background-position classNames.
342-
- `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`.
343-
- `isArbitrarySize` checks whether class part is an arbitrary value which starts with `size:` (`[size:200px_100px]`) which is necessary for background-size classNames.
344-
- `isArbitraryValue` checks whether the class part is enclosed in brackets (`[something]`)
345-
- `isArbitraryVariable` checks whether the class part is an arbitrary variable (`(--my-var)`)
346-
- `isArbitraryVariableFamilyName` checks whether class part is an arbitrary variable with the `family-name` label (`(family-name:--my-font)`)
347-
- `isArbitraryVariableImage` checks whether class part is an arbitrary variable with the `image` or `url` label (`(image:--my-image)`)
348-
- `isArbitraryVariableLength` checks whether class part is an arbitrary variable with the `length` label (`(length:--my-length)`)
349-
- `isArbitraryVariablePosition` checks whether class part is an arbitrary variable with the `position` label (`(position:--my-position)`)
350-
- `isArbitraryVariableShadow` checks whether class part is an arbitrary variable with the `shadow` label or not label at all (`(shadow:--my-shadow)`, `(--my-shadow)`)
351-
- `isArbitraryVariableSize` checks whether class part is an arbitrary variable with the `size`, `length` or `percentage` label (`(size:--my-size)`)
352-
- `isFraction` checks whether class part is a fraction of two numbers (`1/2`, `127/256`)
353-
- `isInteger` checks for integer values (`3`).
354-
- `isNumber` checks for numbers (`3`, `1.5`)
355-
- `isPercent` checks for percent values (`12.5%`) which is used for color stop positions.
356-
- `isTshirtSize` checks whether class part is a T-shirt size (`sm`, `xl`), optionally with a preceding number (`2xl`).
337+
### Simple Type Validators
338+
339+
These validators check for basic patterns and types:
340+
341+
| Validator | Description | Example Match |
342+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------ |
343+
| `isAny` | Always returns `true`. Use carefully - matches everything. Best when certain no other class groups exist in a namespace. | Matches any value |
344+
| `isAnyNonArbitrary` | Checks if class part is NOT an arbitrary value or variable | `red`, `lg`, `4` |
345+
| `isInteger` | Matches integer values | `3`, `100` |
346+
| `isNumber` | Matches any number (integer or decimal) | `3`, `1.5`, `0.25` |
347+
| `isFraction` | Matches fraction patterns | `1/2`, `127/256` |
348+
| `isPercent` | Matches percentage values | `12.5%`, `50%` |
349+
| `isTshirtSize` | Matches T-shirt sizes, optionally with number prefix | `sm`, `xl`, `2xl` |
350+
351+
### Arbitrary Value Validators
352+
353+
These validators check arbitrary values (values in square brackets `[...]`):
354+
355+
| Validator | Description | Example Match | Common Use |
356+
| --------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | --------------------------------- |
357+
| `isArbitraryValue` | Checks if value is enclosed in brackets | `[something]` | Generic arbitrary value detection |
358+
| `isArbitraryLength` | Checks for arbitrary length values | `[3%]`, `[4px]`, `[length:var(--my-var)]` | Width, height, spacing |
359+
| `isArbitraryNumber` | Checks for arbitrary numbers or `number:` labeled values | `[450]`, `[number:var(--value)]` | Font-weight, z-index |
360+
| `isArbitraryPosition` | Checks for `position:` labeled values | `[position:200px_100px]` | Background-position |
361+
| `isArbitrarySize` | Checks for `size:` labeled values | `[size:200px_100px]` | Background-size |
362+
| `isArbitraryImage` | Checks for image-like values (starts with `image:`, `url:`, `linear-gradient(`, etc.) | `[url('/path.png')]`, `[image:var(--img)]` | Background-image |
363+
| `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 |
364+
365+
### Arbitrary Variable Validators
366+
367+
These validators check arbitrary CSS variables (values in parentheses `(...)`):
368+
369+
| Validator | Description | Example Match | Common Use |
370+
| ------------------------------- | ----------------------------------------------------------------- | --------------------------------------- | -------------------- |
371+
| `isArbitraryVariable` | Checks if value is a CSS variable in parentheses | `(--my-var)` | Generic CSS variable |
372+
| `isArbitraryVariableLength` | Checks for variables with `length` label | `(length:--my-length)` | Length properties |
373+
| `isArbitraryVariableSize` | Checks for variables with `size`, `length`, or `percentage` label | `(size:--my-size)` | Size properties |
374+
| `isArbitraryVariablePosition` | Checks for variables with `position` label | `(position:--my-position)` | Position properties |
375+
| `isArbitraryVariableImage` | Checks for variables with `image` or `url` label | `(image:--my-image)` | Image properties |
376+
| `isArbitraryVariableFamilyName` | Checks for variables with `family-name` label | `(family-name:--my-font)` | Font-family |
377+
| `isArbitraryVariableShadow` | Checks for variables with `shadow` label or no label | `(shadow:--my-shadow)`, `(--my-shadow)` | Shadow properties |
378+
379+
### Usage Examples
380+
381+
```ts
382+
import { extendTailwindMerge, validators } from 'tailwind-merge'
383+
384+
const twMerge = extendTailwindMerge({
385+
extend: {
386+
classGroups: {
387+
// Custom size classes that accept numbers or fractions
388+
'custom-size': [{ size: [validators.isNumber, validators.isFraction] }],
389+
390+
// Custom image classes
391+
'hero-image': [{ 'hero-img': [validators.isArbitraryImage] }],
392+
393+
// Custom spacing with T-shirt sizes
394+
'custom-gap': [{ gap: [validators.isTshirtSize] }],
395+
396+
// Accept any value (use with caution)
397+
'theme-color': [{ theme: [validators.isAny] }],
398+
},
399+
},
400+
})
401+
```
357402
358403
## `Config`
359404

docs/configuration.md

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,28 @@ Sometimes there are conflicts across Tailwind classes which are more complex tha
107107

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

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

112-
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.
112+
- You want `px-3` to apply both `padding-left` and `padding-right`
113+
- The earlier `pr-4` should be removed since `px-3` also sets `padding-right`
114+
- Result: `twMerge('pr-4 px-3') // → 'px-3'`
113115

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

116-
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.
118+
- You want `px-3` to set `padding-left`
119+
- You want `pr-4` to override just the `padding-right` from `px-3`
120+
- The `px-3` class should NOT be removed
121+
- Result: `twMerge('px-3 pr-4') // → 'px-3 pr-4'`
122+
123+
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.
124+
125+
#### Defining asymmetric conflicts
126+
127+
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.
117128

118129
```ts
119130
const conflictingClassGroups = {
131+
// ↓ px creates a conflict with pr and pl
120132
px: ['pr', 'pl'],
121133
}
122134
```
@@ -125,6 +137,17 @@ If a class group _creates_ a conflict, it means that if it appears in a class li
125137

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

140+
**Common conflict patterns**:
141+
142+
| Creates Conflict → | Receives Conflict ← | Example |
143+
| ------------------ | ------------------------------------------------------ | ----------------------------------------------- |
144+
| `px` | `pl`, `pr` | `twMerge('pr-2 px-4') // → 'px-4'` |
145+
| `py` | `pt`, `pb` | `twMerge('pt-2 py-4') // → 'py-4'` |
146+
| `p` | `px`, `py`, `pt`, `pr`, `pb`, `pl` | `twMerge('px-2 py-2 p-4') // → 'p-4'` |
147+
| `inset` | `top`, `right`, `bottom`, `left`, `inset-x`, `inset-y` | `twMerge('top-0 inset-0') // → 'inset-0'` |
148+
| `inset-x` | `left`, `right` | `twMerge('right-0 inset-x-0') // → 'inset-x-0'` |
149+
| `inset-y` | `top`, `bottom` | `twMerge('top-0 inset-y-0') // → 'inset-y-0'` |
150+
128151
### Postfix modifiers conflicting with class groups
129152

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

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

177-
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:
200+
#### Example: Adding custom font sizes
201+
202+
Let's say you added a custom font size variable `--text-huge: 100px` to your Tailwind config:
203+
204+
```css
205+
/* In your CSS or Tailwind config */
206+
@theme {
207+
--text-huge: 100px;
208+
}
209+
```
210+
211+
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:
178212

179213
```ts
180214
import { extendTailwindMerge } from 'tailwind-merge'
181215

182-
const twMerge = extendTailwindMerge({
216+
const customTwMerge = extendTailwindMerge({
183217
extend: {
184218
theme: {
185219
// ↓ `text` is the key of the namespace `--text-*`
186-
// ↓ `huge-af` is the variable name in the namespace
187-
text: ['huge-af'],
220+
// ↓ `huge` is the variable name without the namespace prefix
221+
text: ['huge'],
188222
},
189223
},
190224
})
225+
226+
// Now tailwind-merge correctly handles your custom font size
227+
customTwMerge('text-lg text-huge') // → 'text-huge'
228+
customTwMerge('text-huge text-sm') // → 'text-sm'
191229
```
192230

231+
#### Example: Adding custom spacing values
232+
233+
For custom spacing in the `--spacing-*` namespace:
234+
235+
```css
236+
/* In your CSS or Tailwind config */
237+
@theme {
238+
--spacing-gutter: 1.5rem;
239+
--spacing-section: 5rem;
240+
}
241+
```
242+
243+
Configure tailwind-merge:
244+
245+
```ts
246+
const customTwMerge = extendTailwindMerge({
247+
extend: {
248+
theme: {
249+
spacing: ['gutter', 'section'],
250+
},
251+
},
252+
})
253+
254+
// Now works with custom spacing values across all spacing utilities
255+
customTwMerge('p-4 p-gutter') // → 'p-gutter'
256+
customTwMerge('mt-section mt-4') // → 'mt-4'
257+
customTwMerge('gap-2 gap-gutter') // → 'gap-gutter'
258+
```
259+
260+
**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.
261+
262+
#### Note about custom colors
263+
264+
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:
265+
266+
```css
267+
/* In your CSS or Tailwind config */
268+
@theme {
269+
--color-brand-primary: #3b82f6;
270+
--color-brand-secondary: #8b5cf6;
271+
}
272+
```
273+
274+
```ts
275+
import { twMerge } from 'tailwind-merge'
276+
277+
// Works without any configuration
278+
twMerge('bg-blue-500 bg-brand-primary') // → 'bg-brand-primary'
279+
twMerge('text-brand-primary text-brand-secondary') // → 'text-brand-secondary'
280+
twMerge('border-custom-color border-brand-primary') // → 'border-brand-primary'
281+
```
282+
283+
This applies to all color utilities: `bg-*`, `text-*`, `border-*`, `ring-*`, etc.
284+
193285
### Extending the tailwind-merge config
194286

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

0 commit comments

Comments
 (0)