Skip to content

Commit 650405d

Browse files
authored
Merge pull request #294 from dcastil/breaking-feature/199/make-it-easier-to-override-elements-in-config
Make it possible to override elements with `extendTailwindMerge`
2 parents 01a1c48 + 941ac19 commit 650405d

File tree

10 files changed

+310
-153
lines changed

10 files changed

+310
-153
lines changed

docs/api-reference.md

Lines changed: 94 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -59,27 +59,29 @@ It can be used like this:
5959

6060
```ts
6161
extendTailwindMerge({
62-
theme: {
63-
'my-scale': ['foo', 'bar']
62+
extend: {
63+
theme: {
64+
'my-scale': ['foo', 'bar'],
65+
},
66+
classGroups: {
67+
'my-group': [{ 'my-group': [fromTheme('my-scale'), fromTheme('spacing')] }],
68+
'my-group-x': [{ 'my-group-x': [fromTheme('my-scale')] }],
69+
},
6470
},
65-
classGroups: {
66-
'my-group': [{ 'my-group': [fromTheme('my-scale'), fromTheme('spacing')] }]
67-
'my-group-x': [{ 'my-group-x': [fromTheme('my-scale')] }]
68-
}
6971
})
7072
```
7173
7274
## `extendTailwindMerge`
7375
7476
```ts
7577
function extendTailwindMerge(
76-
configExtension: Partial<Config>,
77-
...createConfig: Array<(config: Config) => Config>
78+
configExtension: ConfigExtension,
79+
...createConfig: ((config: Config) => Config)[]
7880
): TailwindMerge
79-
function extendTailwindMerge(...createConfig: Array<(config: Config) => Config>): TailwindMerge
81+
function extendTailwindMerge(...createConfig: ((config: Config) => Config)[]): TailwindMerge
8082
```
8183

82-
Function to create merge function with custom config which extends the default config. Use this if you use the default Tailwind config and just extend it in some places.
84+
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.
8385

8486
> **Note**
8587
> 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.
@@ -88,40 +90,82 @@ You provide it a `configExtension` object which gets [merged](#mergeconfigs) wit
8890

8991
```ts
9092
const customTwMerge = extendTailwindMerge({
91-
cacheSize: 0, // ← Disabling cache
93+
// ↓ Optional cache size
94+
// Here we're disabling the cache
95+
cacheSize: 0,
9296
// ↓ Optional prefix from TaiLwind config
9397
prefix: 'tw-',
9498
// ↓ Optional separator from TaiLwind config
9599
separator: '_',
96-
// ↓ Add values to existing theme scale or create a new one
97-
// Not all theme keys form the Tailwind config are supported by default.
98-
theme: {
99-
spacing: ['sm', 'md', 'lg'],
100-
},
101-
// ↓ Here you define class groups
102-
classGroups: {
103-
// ↓ The `foo` key here is the class group ID
104-
// ↓ Creates group of classes which have conflicting styles
105-
// Classes here: foo, foo-2, bar-baz, bar-baz-1, bar-baz-2
106-
foo: ['foo', 'foo-2', { 'bar-baz': ['', '1', '2'] }],
107-
// ↓ Functions can also be used to match classes.
108-
// Classes here: qux-auto, qux-1000, qux-1001,…
109-
bar: [{ qux: ['auto', (value) => Number(value) >= 1000] }],
110-
baz: ['baz-sm', 'baz-md', 'baz-lg'],
111-
},
112-
// ↓ Here you can define additional conflicts across different groups
113-
conflictingClassGroups: {
114-
// ↓ ID of class group which creates a conflict with…
115-
// ↓ …classes from groups with these IDs
116-
// In this case `twMerge('qux-auto foo') → 'foo'`
117-
foo: ['bar'],
100+
101+
// ↓ Optional config overrides
102+
// Only elements from the second level onwards are overridden
103+
override: {
104+
// ↓ Theme scales to override
105+
// Not all theme keys from the Tailwind config are supported by default.
106+
theme: {
107+
colors: ['black', 'white', 'yellow-500'],
108+
},
109+
// ↓ Class groups to override
110+
classGroups: {
111+
// ↓ The `shadow` key here is the class group ID
112+
// ↓ Creates group of classes which have conflicting styles
113+
// Classes here: shadow-100, shadow-200, shadow-300, shadow-400, shadow-500
114+
shadow: [{ shadow: ['100', '200', '300', '400', '500'] }],
115+
},
116+
// ↓ Conflicts across different groups to override
117+
conflictingClassGroups: {
118+
// ↓ ID of class group which creates a conflict with…
119+
// ↓ …classes from groups with these IDs
120+
// Here we remove the default conflict between the font-size and leading class
121+
// groups.
122+
'font-size': [],
123+
},
124+
// ↓ Conflicts between the postfix modifier of a group and a different class group to
125+
// override
126+
conflictingClassGroupModifiers: {
127+
// You probably won't need this, but it follows the same shape as
128+
// `conflictingClassGroups`.
129+
},
118130
},
119-
// ↓ Here you can define conflicts between the postfix modifier of a group and a different class group.
120-
conflictingClassGroupModifiers: {
121-
// ↓ ID of class group whose postfix modifier creates a conflict with…
122-
// ↓ …classes from groups with these IDs
123-
// In this case `twMerge('qux-auto baz-sm/1000') → 'baz-sm/1000'`
124-
baz: ['bar'],
131+
132+
// ↓ Optional config extensions
133+
// Follows same shape as the `override` object.
134+
extend: {
135+
// ↓ Theme scales to extend or create
136+
// Not all theme keys from the Tailwind config are supported by default.
137+
theme: {
138+
spacing: ['sm', 'md', 'lg'],
139+
},
140+
// ↓ Class groups to extend or create
141+
classGroups: {
142+
// ↓ The `animate` key here is the class group ID
143+
// ↓ Adds class animate-shimmer to existing group with ID `animate` or creates
144+
// new class group if it doesn't exist.
145+
animate: ['animate-shimmer'],
146+
// ↓ Functions can also be used to match classes
147+
// They take the class part value as argument and return a boolean defining whether
148+
// it is a match.
149+
// Here we accept all string classes starting with `aspec-w-` followed by a number.
150+
'aspect-w': [{ 'aspect-w': [(value) => Boolean(value) && !isNaN(value)] }],
151+
'aspect-h': [{ 'aspect-h': [(value) => Boolean(value) && !isNaN(value)] }],
152+
'aspect-reset': ['aspect-none'],
153+
// ↓ You can also use validators exported by tailwind-merge
154+
'prose-size': [{ prose: ['base', validators.isTshirtSize] }],
155+
},
156+
// ↓ Conflicts across different groups to extend or create
157+
conflictingClassGroups: {
158+
// ↓ ID of class group which creates a conflict with…
159+
// ↓ …classes from groups with these IDs
160+
// In this case `twMerge('aspect-w-5 aspect-none') → 'aspect-none'`
161+
'aspect-reset': ['aspect-w', 'aspect-h'],
162+
},
163+
// ↓ Conflicts between the postfix modifier of a group and a different class group to
164+
// extend or create
165+
conflictingClassGroupModifiers: {
166+
// You probably won't need this, but it follows the same shape as
167+
// `conflictingClassGroups`.
168+
},
125169
},
126170
})
127171
```
@@ -200,16 +244,22 @@ But don't merge configs like that. Use [`mergeConfigs`](#mergeconfigs) instead.
200244
function mergeConfigs(baseConfig: Config, configExtension: Partial<Config>): Config
201245
```
202246

203-
Helper function to merge multiple config objects. Objects are merged, arrays are concatenated, scalar values are overridden and `undefined` does nothing. The function assumes that both parameters are tailwind-merge config objects and shouldn't be used as a generic merge function.
247+
Helper function to merge multiple tailwind-merge configs. Properties with the value `undefined` are skipped.
204248

205249
```ts
206250
const customTwMerge = createTailwindMerge(getDefaultConfig, (config) =>
207251
mergeConfigs(config, {
208-
classGroups: {
209-
// ↓ Adding new class group
210-
mySpecialClassGroup: [{ special: ['1', '2'] }],
252+
override: {
253+
classGroups: {
254+
// ↓ Overriding existing class group
255+
shadow: [{ shadow: ['100', '200', '300', '400', '500'] }],
256+
},
257+
}
258+
extend: {
211259
// ↓ Adding value to existing class group
212-
animate: ['animate-magic'],
260+
animate: ['animate-shimmer'],
261+
// ↓ Adding new class group
262+
prose: [{ prose: ['', validators.isTshirtSize] }],
213263
},
214264
}),
215265
)

docs/configuration.md

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -160,29 +160,35 @@ If you modified one of these theme scales in your Tailwind config, you can add a
160160

161161
### Extending the tailwind-merge config
162162

163-
If you only need to extend 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.
163+
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.
164164

165165
```ts
166166
import { extendTailwindMerge } from 'tailwind-merge'
167167

168168
const customTwMerge = extendTailwindMerge({
169-
// ↓ Add values to existing theme scale or create a new one
170-
theme: {
171-
spacing: ['sm', 'md', 'lg'],
172-
},
173-
// ↓ Add values to existing class groups or define new ones
174-
classGroups: {
175-
foo: ['foo', 'foo-2', { 'bar-baz': ['', '1', '2'] }],
176-
bar: [{ qux: ['auto', (value) => Number(value) >= 1000] }],
177-
baz: ['baz-sm', 'baz-md', 'baz-lg'],
178-
},
179-
// ↓ Here you can define additional conflicts across class groups
180-
conflictingClassGroups: {
181-
foo: ['bar'],
182-
},
183-
// ↓ Define conflicts between postfix modifiers and class groups
184-
conflictingClassGroupModifiers: {
185-
baz: ['bar'],
169+
// ↓ Override eleemnts from the default config
170+
// It has the same shape as the `extend` object, so we're going to skip it here.
171+
override: {},
172+
// ↓ Extend values from the default config
173+
extend: {
174+
// ↓ Add values to existing theme scale or create a new one
175+
theme: {
176+
spacing: ['sm', 'md', 'lg'],
177+
},
178+
// ↓ Add values to existing class groups or define new ones
179+
classGroups: {
180+
foo: ['foo', 'foo-2', { 'bar-baz': ['', '1', '2'] }],
181+
bar: [{ qux: ['auto', (value) => Number(value) >= 1000] }],
182+
baz: ['baz-sm', 'baz-md', 'baz-lg'],
183+
},
184+
// ↓ Here you can define additional conflicts across class groups
185+
conflictingClassGroups: {
186+
foo: ['bar'],
187+
},
188+
// ↓ Define conflicts between postfix modifiers and class groups
189+
conflictingClassGroupModifiers: {
190+
baz: ['bar'],
191+
},
186192
},
187193
})
188194
```

docs/recipes.md

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ How to configure tailwind-merge with some common patterns.
66

77
> 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?
88
9-
You'll be able to do this by creating a custom `twMerge` functon with [`extendTailwindMerge`](./api-reference.md#extendtailwindmerge).
9+
We'll be able to do this by creating a custom `twMerge` functon with [`extendTailwindMerge`](./api-reference.md#extendtailwindmerge).
1010

11-
First, check whether your particular theme scale is included in tailwind-merge's theme config object [here](./configuration.md#theme). In the hypothetical case that tailwind-merge supported Tailwind's `boxShadow` theme scale, you could add it to the tailwind-merge config like this:
11+
First, we need to know whether we want to override or extend the default scale. Let's say we extended the default config by putting the scale into the `extend` key in the Tailwind config.
12+
13+
Then we check whether our particular theme scale is included in tailwind-merge's theme config object [here](./configuration.md#theme). In the hypothetical case that tailwind-merge supported Tailwind's `boxShadow` theme scale, we could add it to the tailwind-merge config like this:
1214

1315
```js
1416
const customTwMerge = extendTailwindMerge({
15-
theme: {
16-
// The `boxShadow` key isn't actually supported
17-
boxShadow: [{ shadow: ['100', '200', '300'] }],
17+
extend: {
18+
theme: {
19+
// The `boxShadow` key isn't actually supported
20+
boxShadow: [{ shadow: ['100', '200', '300'] }],
21+
},
1822
},
1923
})
2024
```
@@ -23,23 +27,15 @@ In the case of the `boxShadow` scale, tailwind-merge doesn't include it in the t
2327

2428
```js
2529
const customTwMerge = extendTailwindMerge({
26-
classGroups: {
27-
shadow: [{ shadow: ['100', '200', '300'] }],
30+
extend: {
31+
classGroups: {
32+
shadow: [{ shadow: ['100', '200', '300'] }],
33+
},
2834
},
2935
})
3036
```
3137

32-
Note that by using `extendTailwindMerge` 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`. In most cases that's fine because you won't use that class in your project.
33-
34-
If you expect classes like `shadow-lg` to be input in `twMerge` and don't want the class to cause incorrect merges, you can explicitly override the class group with [`createTailwindMerge`](./api-reference.md#createtailwindmerge), removing the default classes.
35-
36-
```js
37-
const customTwMerge = createTailwindMerge(() => {
38-
const config = getDefaultConfig()
39-
config.classGroups.shadow = [{ shadow: ['100', '200', '300'] }]
40-
return config
41-
})
42-
```
38+
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.
4339

4440
## Extracting classes with Tailwind's [`@apply`](https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply)
4541

docs/writing-plugins.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import { mergeConfigs, validators, Config } from 'tailwind-merge'
1414

1515
export function withMagic(config: Config): Config {
1616
return mergeConfigs(config, {
17-
classGroups: {
18-
'magic.my-group': [{ magic: [validators.isLength, 'wow'] }],
17+
extend: {
18+
classGroups: {
19+
'magic.my-group': [{ magic: [validators.isLength, 'wow'] }],
20+
},
1921
},
2022
})
2123
}

src/lib/extend-tailwind-merge.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { createTailwindMerge } from './create-tailwind-merge'
22
import { getDefaultConfig } from './default-config'
33
import { mergeConfigs } from './merge-configs'
4-
import { Config } from './types'
4+
import { Config, ConfigExtension } from './types'
55

66
type CreateConfigSubsequent = (config: Config) => Config
77

88
export function extendTailwindMerge(
9-
configExtension: Partial<Config> | CreateConfigSubsequent,
9+
configExtension: ConfigExtension | CreateConfigSubsequent,
1010
...createConfig: CreateConfigSubsequent[]
1111
) {
1212
return typeof configExtension === 'function'

0 commit comments

Comments
 (0)