tailwind-variants 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,57 +24,30 @@
24
24
  - Composition support
25
25
  - Fully typed
26
26
  - Framework agnostic
27
- - Automatic conflict resolution
28
- - Tailwindcss V4 support
27
+ - Built-in conflict resolution
28
+ - Tailwind CSS v4 support
29
29
 
30
- ## Documentation
31
-
32
- For full documentation, visit [tailwind-variants.org](https://tailwind-variants.org)
33
-
34
- > ❕ Note: `Tailwindcss V4` no longer supports the `config.content.transform` so we remove the `responsive variants` feature
35
- >
36
- > If you want to use `responsive variants`, you need to add it manually to your classname.
37
-
38
- ## Quick Start
39
-
40
- 1. Installation:
41
- To use Tailwind Variants in your project, you can install it as a dependency:
30
+ ## Installation
42
31
 
43
32
  ```bash
44
- yarn add tailwind-variants
45
- # or
46
33
  npm i tailwind-variants
47
34
  # or
48
- pnpm add tailwind-variants
49
- ```
50
-
51
- **Optional:** If you want automatic conflict resolution, also install `tailwind-merge`:
52
-
53
- ```bash
54
- yarn add tailwind-merge
55
- # or
56
- npm i tailwind-merge
35
+ yarn add tailwind-variants
57
36
  # or
58
- pnpm add tailwind-merge
37
+ pnpm add tailwind-variants
59
38
  ```
60
39
 
61
- > **⚠️ Compatibility Note:** Supports Tailwind CSS v4.x (requires `tailwind-merge` v3.x). If you use Tailwind CSS v3.x, use tailwind-variants v0.x with [tailwind-merge v2.6.0](https://github.com/dcastil/tailwind-merge/tree/v2.6.0).
40
+ **Lite mode:** import from `tailwind-variants/lite` for a smaller bundle without conflict resolution.
62
41
 
63
- > **💡 Lite mode (v3):** For smaller bundle size and faster runtime without conflict resolution, use the `/lite` import:
64
- >
65
- > ```js
66
- > import {tv} from "tailwind-variants/lite";
67
- > ```
42
+ **Upgrading?**
68
43
 
69
- > **⚠️ Upgrading?**
70
- >
71
- > - From v2 to v3: See the [v3 migration guide](./MIGRATION-V3.md)
72
- > - From v1 to v2: See the [v2 migration guide](./MIGRATION-V2.md)
44
+ - v2 → v3: [migration guide](./.docs/migrations/v2-to-v3.md)
45
+ - v1 → v2: [migration guide](./.docs/migrations/v1-to-v2.md)
73
46
 
74
- 2. Usage:
47
+ ## Quick Start
75
48
 
76
49
  ```js
77
- import {tv} from "tailwind-variants";
50
+ import { tv } from "tailwind-variants";
78
51
 
79
52
  const button = tv({
80
53
  base: "font-medium bg-blue-500 text-white rounded-full active:opacity-80",
@@ -101,73 +74,113 @@ const button = tv({
101
74
  },
102
75
  });
103
76
 
104
- return <button className={button({size: "sm", color: "secondary"})}>Click me</button>;
77
+ button({ size: "sm", color: "secondary" });
78
+ // => "font-medium rounded-full active:opacity-80 bg-purple-500 text-white text-sm px-3 py-1"
105
79
  ```
106
80
 
107
- ## Utility Functions
108
-
109
- Tailwind Variants provides several utility functions for combining and merging class names:
81
+ > **Note:** Tailwind CSS v4 no longer supports `config.content.transform`, so responsive variants
82
+ > were removed. Add responsive classes to your class names manually if needed.
110
83
 
111
- ### `cx` - Simple Concatenation
84
+ ## Conflict Resolution
112
85
 
113
- Combines class names without merging conflicting classes (similar to `clsx`):
86
+ Conflict resolution is built into the default entry no extra package is required. It is available
87
+ on `tv`, `createTV`, `cn`, and `cnMerge` (`cx` and `/lite` do not merge).
114
88
 
115
89
  ```js
116
- import {cx} from "tailwind-variants";
90
+ import { tv, cn } from "tailwind-variants";
117
91
 
118
- cx("text-xl", "font-bold"); // => "text-xl font-bold"
119
- cx("px-2", "px-4"); // => "px-2 px-4" (no merging)
92
+ cn("px-2", "px-4"); // => "px-4"
93
+
94
+ tv({ base: "px-2", variants: { size: { lg: "px-4" } } })({ size: "lg" }); // => "px-4"
120
95
  ```
121
96
 
122
- ### `cn` - Merge with Default Config
97
+ ### Custom configuration
123
98
 
124
- > **Updated in v3.2.2** - Now returns a string directly (no function call needed)
99
+ Pass `twMergeConfig` to teach the resolver about custom utilities. Prefer `{ extend, override }` —
100
+ `extend` appends to the defaults, `override` replaces them:
125
101
 
126
- Combines class names and merges conflicting Tailwind CSS classes using the default `tailwind-merge` config. Returns a string directly:
102
+ ```ts
103
+ import { cnMerge, createTV, tv, type TWMergeConfig } from "tailwind-variants";
127
104
 
128
- ```js
129
- import {cn} from "tailwind-variants";
105
+ const twMergeConfig = {
106
+ extend: {
107
+ classGroups: {
108
+ elevation: ["elevation-low", "elevation-high"],
109
+ },
110
+ },
111
+ } satisfies TWMergeConfig;
130
112
 
131
- cn("bg-red-500", "bg-blue-500"); // => "bg-blue-500"
132
- cn("px-2", "px-4", "py-2"); // => "px-4 py-2"
113
+ tv({ base: "elevation-low", variants: { raised: { true: "elevation-high" } } }, { twMergeConfig });
114
+ createTV({ twMergeConfig });
115
+ cnMerge("elevation-low", "elevation-high")({ twMergeConfig }); // => "elevation-high"
133
116
  ```
134
117
 
135
- ### `cnMerge` - Merge with Custom Config
118
+ Disable merging with `{ twMerge: false }` on `tv`, `createTV`, or `cnMerge`.
136
119
 
137
- > **Available from v3.2.2**
120
+ ### Reusing a `tailwind-merge` config
138
121
 
139
- Combines class names and merges conflicting Tailwind CSS classes with support for custom `twMerge` configuration via a second function call:
122
+ If you already configure `extendTailwindMerge`, reuse the same config object as `twMergeConfig`
123
+ (pass the object — not the returned merge function):
140
124
 
141
- ```js
142
- import {cnMerge} from "tailwind-variants";
125
+ ```ts
126
+ import { extendTailwindMerge } from "tailwind-merge";
127
+ import { createTV, type TWMergeConfig } from "tailwind-variants";
128
+
129
+ const mergeConfig = {
130
+ extend: {
131
+ classGroups: {
132
+ elevation: ["elevation-low", "elevation-high"],
133
+ },
134
+ },
135
+ } satisfies TWMergeConfig;
136
+
137
+ extendTailwindMerge(mergeConfig);
138
+ createTV({ twMergeConfig: mergeConfig });
139
+ ```
140
+
141
+ For `createTailwindMerge`, move the custom parts of the factory into `{ extend, override }` and pass
142
+ that object. Merge functions and full default configs cannot be passed directly.
143
143
 
144
- // Disable merging
145
- cnMerge("px-2", "px-4")({twMerge: false}) // => "px-2 px-4"
144
+ ## Utility Functions
146
145
 
147
- // Enable merging explicitly
148
- cnMerge("bg-red-500", "bg-blue-500")({twMerge: true}) // => "bg-blue-500"
146
+ | Function | Behavior |
147
+ | --------- | ---------------------------------------------------- |
148
+ | `cx` | Concatenate class names (no merging) |
149
+ | `cn` | Concatenate and merge with the default config |
150
+ | `cnMerge` | Concatenate and merge, with optional per-call config |
151
+
152
+ ```js
153
+ import { cx, cn, cnMerge } from "tailwind-variants";
149
154
 
150
- // Use custom twMergeConfig
151
- cnMerge("px-2", "px-4")({twMergeConfig: {...}}) // => merged with custom config
155
+ cx("px-2", "px-4"); // => "px-2 px-4"
156
+ cn("px-2", "px-4"); // => "px-4"
157
+ cnMerge("px-2", "px-4")({ twMerge: false }); // => "px-2 px-4"
152
158
  ```
153
159
 
154
- **When to use which:**
160
+ ## Documentation
155
161
 
156
- - Use `cx` when you want simple concatenation without any merging
157
- - Use `cn` for most cases when you want automatic conflict resolution with default settings
158
- - Use `cnMerge` when you need to customize the merge behavior (disable merging, custom config, etc.)
162
+ For full documentation, visit [tailwind-variants.org](https://tailwind-variants.org).
159
163
 
160
164
  ## Acknowledgements
161
165
 
162
166
  - [**cva**](https://github.com/joe-bell/cva) ([Joe Bell](https://github.com/joe-bell))
163
- This project as started as an extension of Joe's work on `cva` a great tool for generating variants for a single element with Tailwind CSS. Big shoutout to [Joe Bell](https://github.com/joe-bell) and [contributors](https://github.com/joe-bell/cva/graphs/contributors) you guys rock! 🤘 - we recommend to use `cva` if don't need any of the **Tailwind Variants** features listed [here](https://www.tailwind-variants.org/docs/comparison).
167
+ This project started as an extension of Joe's work on `cva` a great tool for generating variants
168
+ for a single element with Tailwind CSS. Big shoutout to [Joe Bell](https://github.com/joe-bell) and
169
+ [contributors](https://github.com/joe-bell/cva/graphs/contributors)! If you don't need the
170
+ **Tailwind Variants** features listed
171
+ [here](https://www.tailwind-variants.org/docs/comparison), we recommend `cva`.
172
+
173
+ - [**Stitches**](https://stitches.dev/) ([Modulz](https://modulz.app))
174
+ The pioneers of the `variants` API movement. Immense thanks to [Modulz](https://modulz.app) for
175
+ their work on Stitches and the community around it.
164
176
 
165
- - [**Stitches**](https://stitches.dev/) ([Modulz](https://modulz.app))
166
- The pioneers of the `variants` API movement. Inmense thanks to [Modulz](https://modulz.app) for their work on Stitches and the community around it. 🙏
177
+ - [**tailwind-merge**](https://github.com/dcastil/tailwind-merge), [**clsx**](https://github.com/lukeed/clsx), and [**cnfast**](https://github.com/aidenybai/cnfast)
178
+ Conflict resolution draws on ideas and MIT-licensed work from these projects.
167
179
 
168
180
  ## Community
169
181
 
170
- We're excited to see the community adopt HeroUI, raise issues, and provide feedback. Whether it's a feature request, bug report, or a project to showcase, please get involved!
182
+ We're excited to see the community adopt HeroUI, raise issues, and provide feedback. Whether it's a
183
+ feature request, bug report, or a project to showcase, please get involved!
171
184
 
172
185
  - [Discord](https://discord.gg/9b6yyZKmH4)
173
186
  - [Twitter](https://twitter.com/getnextui)
@@ -177,17 +190,15 @@ We're excited to see the community adopt HeroUI, raise issues, and provide feedb
177
190
 
178
191
  Contributions are always welcome!
179
192
 
180
- Please follow our [contributing guidelines](./CONTRIBUTING.md).
181
-
182
- Please adhere to this project's [CODE_OF_CONDUCT](./CODE_OF_CONDUCT.md).
193
+ - [Contributing guidelines](./CONTRIBUTING.md)
194
+ - [Code of conduct](./CODE_OF_CONDUCT.md)
195
+ - [Security policy](./SECURITY.md)
183
196
 
184
197
  ## Authors
185
198
 
186
- - Junior garcia ([@jrgarciadev](https://github.com/jrgaciadev))
199
+ - Junior Garcia ([@jrgarciadev](https://github.com/jrgarciadev))
187
200
  - Tianen Pang ([@tianenpang](https://github.com/tianenpang))
188
201
 
189
202
  ## License
190
203
 
191
- Licensed under the MIT License.
192
-
193
- See [LICENSE](./LICENSE.md) for more information.
204
+ Licensed under the MIT License. See [LICENSE](./LICENSE.md) for more information.
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ // src/internal/join-class-value.ts
4
+ var isArray = Array.isArray;
5
+ var joinClassValue = (value) => {
6
+ if (!value && value !== 0 && value !== 0n) return "";
7
+ if (typeof value === "string") return value;
8
+ if (typeof value === "number") {
9
+ if (value !== value) return "";
10
+ return "" + value;
11
+ }
12
+ if (typeof value === "bigint") return "" + value;
13
+ let result = "";
14
+ if (isArray(value)) {
15
+ const length = value.length;
16
+ for (let index = 0; index < length; index++) {
17
+ const item = value[index];
18
+ if (!item && item !== 0 && item !== 0n) continue;
19
+ const resolved = typeof item === "string" ? item : joinClassValue(item);
20
+ if (resolved) {
21
+ if (result) result += " ";
22
+ result += resolved;
23
+ }
24
+ }
25
+ return result;
26
+ }
27
+ if (typeof value === "object") {
28
+ for (const key in value) {
29
+ if (value[key]) {
30
+ if (result) result += " ";
31
+ result += key;
32
+ }
33
+ }
34
+ }
35
+ return result;
36
+ };
37
+
38
+ // src/utils.ts
39
+ var SPACE_REGEX = /\s+/g;
40
+ var isArray2 = Array.isArray;
41
+ var removeExtraSpaces = (str) => {
42
+ if (typeof str !== "string" || !str) return str;
43
+ return str.replace(SPACE_REGEX, " ").trim();
44
+ };
45
+ var stringNeedsNormalize = (str) => {
46
+ const len = str.length;
47
+ if (len === 0) return false;
48
+ const first = str.charCodeAt(0);
49
+ const last = str.charCodeAt(len - 1);
50
+ if (first === 32 || last === 32 || first >= 9 && first <= 13 || first === 160 || last >= 9 && last <= 13 || last === 160) {
51
+ return true;
52
+ }
53
+ for (let i = 0; i < len; i++) {
54
+ const code = str.charCodeAt(i);
55
+ if (code >= 9 && code <= 13 || code === 160) return true;
56
+ if (code === 32 && i + 1 < len && str.charCodeAt(i + 1) === 32) return true;
57
+ }
58
+ return false;
59
+ };
60
+ var cx = (...classnames) => {
61
+ const result = joinClassValue(classnames);
62
+ if (!result) return void 0;
63
+ return stringNeedsNormalize(result) ? removeExtraSpaces(result) : result;
64
+ };
65
+ var falsyToString = (value) => value === false ? "false" : value === true ? "true" : value === 0 ? "0" : value;
66
+ var isEmptyObject = (obj) => {
67
+ if (!obj || typeof obj !== "object") return true;
68
+ for (const _ in obj) return false;
69
+ return true;
70
+ };
71
+ var isEqual = (obj1, obj2) => {
72
+ if (obj1 === obj2) return true;
73
+ if (!obj1 || !obj2) return false;
74
+ const record1 = obj1;
75
+ const record2 = obj2;
76
+ const keys1 = Object.keys(record1);
77
+ const keys2 = Object.keys(record2);
78
+ if (keys1.length !== keys2.length) return false;
79
+ for (let i = 0; i < keys1.length; i++) {
80
+ const key = keys1[i];
81
+ if (!keys2.includes(key)) return false;
82
+ if (record1[key] !== record2[key]) return false;
83
+ }
84
+ return true;
85
+ };
86
+ var isBoolean = (value) => value === true || value === false;
87
+ var joinObjects = (obj1, obj2) => {
88
+ const target = obj1;
89
+ for (const key in obj2) {
90
+ if (Object.hasOwn(obj2, key)) {
91
+ const val2 = obj2[key];
92
+ if (key in target) {
93
+ target[key] = cx(target[key], val2);
94
+ } else {
95
+ target[key] = val2;
96
+ }
97
+ }
98
+ }
99
+ return obj1;
100
+ };
101
+ var flat = (arr, target) => {
102
+ for (let i = 0; i < arr.length; i++) {
103
+ const el = arr[i];
104
+ if (isArray2(el)) flat(el, target);
105
+ else if (el) target.push(el);
106
+ }
107
+ };
108
+ function flatArray(arr) {
109
+ const flattened = [];
110
+ flat(arr, flattened);
111
+ return flattened;
112
+ }
113
+ var flatMergeArrays = (...arrays) => {
114
+ const result = [];
115
+ flat(arrays, result);
116
+ const filtered = [];
117
+ for (let i = 0; i < result.length; i++) {
118
+ if (result[i]) filtered.push(result[i]);
119
+ }
120
+ return filtered;
121
+ };
122
+ var mergeObjects = (obj1, obj2) => {
123
+ const record1 = obj1;
124
+ const record2 = obj2;
125
+ const result = {};
126
+ for (const key in record1) {
127
+ const val1 = record1[key];
128
+ if (key in record2) {
129
+ const val2 = record2[key];
130
+ if (isArray2(val1) || isArray2(val2)) {
131
+ result[key] = flatMergeArrays(val2, val1);
132
+ } else if (typeof val1 === "object" && typeof val2 === "object" && val1 && val2) {
133
+ result[key] = mergeObjects(val1, val2);
134
+ } else {
135
+ result[key] = val2 + " " + val1;
136
+ }
137
+ } else {
138
+ result[key] = val1;
139
+ }
140
+ }
141
+ for (const key in record2) {
142
+ if (!(key in record1)) {
143
+ result[key] = record2[key];
144
+ }
145
+ }
146
+ return result;
147
+ };
148
+
149
+ exports.cx = cx;
150
+ exports.falsyToString = falsyToString;
151
+ exports.flat = flat;
152
+ exports.flatArray = flatArray;
153
+ exports.flatMergeArrays = flatMergeArrays;
154
+ exports.isBoolean = isBoolean;
155
+ exports.isEmptyObject = isEmptyObject;
156
+ exports.isEqual = isEqual;
157
+ exports.joinClassValue = joinClassValue;
158
+ exports.joinObjects = joinObjects;
159
+ exports.mergeObjects = mergeObjects;
160
+ exports.removeExtraSpaces = removeExtraSpaces;