Skip to content

Commit

Permalink
Merge pull request #518 from dcastil/breaking-feature/513/tailwind-cs…
Browse files Browse the repository at this point in the history
…s-v4-support

Breaking: Tailwind CSS v4 support
  • Loading branch information
dcastil authored Jan 30, 2025
2 parents 47c87d8 + 7f043a6 commit 7c8c994
Show file tree
Hide file tree
Showing 36 changed files with 2,040 additions and 1,153 deletions.
89 changes: 54 additions & 35 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,17 @@ When using TypeScript and you use custom class group IDs or theme group IDs, you
type AdditionalClassGroupIds = 'aspect-w' | 'aspect-h' | 'aspect-reset'
type AdditionalThemeGroupIds = never

const customTwMerge = extendTailwindMerge<AdditionalClassGroupIds, AdditionalThemeGroupIds>({
const twMerge = extendTailwindMerge<AdditionalClassGroupIds, AdditionalThemeGroupIds>({
// ↓ Optional cache size
// Here we're disabling the cache
cacheSize: 0,
// ↓ Optional prefix from TaiLwind config
prefix: 'tw-',
// ↓ Optional separator from TaiLwind config
separator: '_',
prefix: 'tw',

// ↓ Optional config overrides
// Only elements from the second level onwards are overridden
override: {
// ↓ Theme scales to override
// Not all theme keys from the Tailwind config are supported by default.
theme: {
colors: ['black', 'white', 'yellow-500'],
},
Expand All @@ -156,13 +153,15 @@ const customTwMerge = extendTailwindMerge<AdditionalClassGroupIds, AdditionalThe
// You probably won't need this, but it follows the same shape as
// `conflictingClassGroups`.
},
// ↓ Modifiers whose order among multiple modifiers should be preserved because their
// order changes which element gets targeted. Overrides default value.
orderSensitiveModifiers: ['before'],
},

// ↓ Optional config extensions
// Follows same shape as the `override` object.
extend: {
// ↓ Theme scales to extend or create
// Not all theme keys from the Tailwind config are supported by default.
theme: {
spacing: ['sm', 'md', 'lg'],
},
Expand Down Expand Up @@ -195,20 +194,23 @@ const customTwMerge = extendTailwindMerge<AdditionalClassGroupIds, AdditionalThe
// You probably won't need this, but it follows the same shape as
// `conflictingClassGroups`.
},
// ↓ Modifiers whose order among multiple modifiers should be preserved because their
// order changes which element gets targeted. Extends default value.
orderSensitiveModifiers: ['before'],
},
})
```

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 customTwMerge = extendTailwindMerge({ … }, withSomePlugin)
const twMerge = extendTailwindMerge({ … }, withSomePlugin)
```

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

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

## `createTailwindMerge`
Expand All @@ -228,8 +230,8 @@ You need to provide a function which resolves to the config tailwind-merge shoul

```ts
// ↓ Callback passed to `createTailwindMerge` is called when
// `customTwMerge` gets called the first time.
const customTwMerge = createTailwindMerge(() => {
// `twMerge` gets called the first time.
const twMerge = createTailwindMerge(() => {
const defaultConfig = getDefaultConfig()

return {
Expand All @@ -248,14 +250,15 @@ const customTwMerge = createTailwindMerge(() => {
...defaultConfig.conflictingClassGroupModifiers,
baz: ['bar'],
},
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 customTwMerge = createTailwindMerge(getDefaultConfig, withSomePlugin, (config) => ({
const twMerge = createTailwindMerge(getDefaultConfig, withSomePlugin, (config) => ({
// ↓ Config returned by `withSomePlugin`
...config,
classGroups: {
Expand All @@ -281,7 +284,7 @@ Helper function to merge multiple tailwind-merge configs. Properties with the va
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 customTwMerge = createTailwindMerge(getDefaultConfig, (config) =>
const twMerge = createTailwindMerge(getDefaultConfig, (config) =>
mergeConfigs<'shadow' | 'animate' | 'prose'>(config, {
override: {
classGroups: {
Expand All @@ -305,43 +308,59 @@ const customTwMerge = createTailwindMerge(getDefaultConfig, (config) =>

```ts
interface Validators {
isLength(value: string): boolean
isAny(value: string): boolean
isAnyNonArbitrary(value: string): boolean
isArbitraryImage(value: string): boolean
isArbitraryLength(value: string): boolean
isNumber(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
isFraction(value: string): boolean
isInteger(value: string): boolean
isNumber(value: string): boolean
isPercent(value: string): boolean
isArbitraryValue(value: string): boolean
isTshirtSize(value: string): boolean
isArbitrarySize(value: string): boolean
isArbitraryPosition(value: string): boolean
isArbitraryImage(value: string): boolean
isArbitraryNumber(value: string): boolean
isArbitraryShadow(value: string): boolean
isAny(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). E.g. the `classGroup` for padding is defined as
```ts
const paddingClassGroup = [{ p: [validators.isLength] }]
const paddingClassGroup = [{ p: [validators.isNumber] }]
```
A brief summary for each validator:
- `isLength` checks whether a class part is a number (`3`, `1.5`), a fraction (`3/4`), or one of the strings `px`, `full` or `screen`.
- `isArbitraryLength` checks for arbitrary length values (`[3%]`, `[4px]`, `[length:var(--my-var)]`).
- `isNumber` checks for numbers (`3`, `1.5`)
- `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.
- `isInteger` checks for integer values (`3`).
- `isPercent` checks for percent values (`12.5%`) which is used for color stop positions.
- `isArbitraryValue` checks whether the class part is enclosed in brackets (`[something]`)
- `isTshirtSize`checks whether class part is a T-shirt size (`sm`, `xl`), optionally with a preceding number (`2xl`).
- `isArbitrarySize` checks whether class part is an arbitrary value which starts with `size:` (`[size:200px_100px]`) which is necessary for background-size classNames.
- `isArbitraryPosition` checks whether class part is an arbitrary value which starts with `position:` (`[position:200px_100px]`) which is necessary for background-position classNames.
- `isArbitraryImage` checks whether class part is an arbitrary value which is an iamge, 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.
- `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`.
- `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.
- `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 iamge, 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`).

## `Config`

Expand Down
Loading

0 comments on commit 7c8c994

Please sign in to comment.