@codefast/tailwind-variants 0.3.7-canary.1
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/CHANGELOG.md +3 -0
- package/LICENSE +21 -0
- package/README.md +963 -0
- package/dist/core/config.cjs +1 -0
- package/dist/core/config.d.ts +31 -0
- package/dist/core/config.js +1 -0
- package/dist/core/tv.cjs +1 -0
- package/dist/core/tv.d.ts +65 -0
- package/dist/core/tv.js +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +1 -0
- package/dist/processing/compound.cjs +1 -0
- package/dist/processing/compound.d.ts +34 -0
- package/dist/processing/compound.js +1 -0
- package/dist/processing/slots.cjs +1 -0
- package/dist/processing/slots.d.ts +46 -0
- package/dist/processing/slots.js +1 -0
- package/dist/types/types.cjs +1 -0
- package/dist/types/types.d.ts +244 -0
- package/dist/types/types.js +0 -0
- package/dist/utilities/utils.cjs +1 -0
- package/dist/utilities/utils.d.ts +90 -0
- package/dist/utilities/utils.js +1 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
# Tailwind Variants
|
|
2
|
+
|
|
3
|
+
Type-safe variant API for Tailwind CSS with enhanced functionality and advanced TypeScript support for building flexible component styling systems.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/codefastlabs/codefast/actions/workflows/release.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/@codefast/tailwind-variants)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://bundlephobia.com/package/@codefast/tailwind-variants)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Install the package via pnpm (recommended):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @codefast/tailwind-variants
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or using npm:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @codefast/tailwind-variants
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Peer Dependencies**:
|
|
26
|
+
|
|
27
|
+
The library works with Tailwind CSS (optional but recommended):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add tailwindcss
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Dependencies**:
|
|
34
|
+
|
|
35
|
+
The library uses these runtime dependencies:
|
|
36
|
+
|
|
37
|
+
- `clsx`: Utility for constructing className strings conditionally
|
|
38
|
+
- `tailwind-merge`: Utility for merging Tailwind CSS classes and resolving conflicts
|
|
39
|
+
|
|
40
|
+
**Requirements**:
|
|
41
|
+
|
|
42
|
+
- Node.js version 20.0.0 or higher
|
|
43
|
+
- TypeScript version 5.9.2 or higher (recommended)
|
|
44
|
+
- Tailwind CSS version 4.0.0 or higher (optional)
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
50
|
+
|
|
51
|
+
const button = tv({
|
|
52
|
+
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors",
|
|
53
|
+
variants: {
|
|
54
|
+
variant: {
|
|
55
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
56
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
57
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
58
|
+
},
|
|
59
|
+
size: {
|
|
60
|
+
sm: "h-9 px-3 text-sm",
|
|
61
|
+
md: "h-10 px-4",
|
|
62
|
+
lg: "h-11 px-8",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
defaultVariants: {
|
|
66
|
+
variant: "default",
|
|
67
|
+
size: "md",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Usage
|
|
72
|
+
console.log(button());
|
|
73
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4"
|
|
74
|
+
|
|
75
|
+
console.log(button({ variant: "destructive", size: "lg" }));
|
|
76
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-destructive text-destructive-foreground hover:bg-destructive/90 h-11 px-8"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
### Core Features
|
|
82
|
+
|
|
83
|
+
The library provides a comprehensive set of features for variant-based styling:
|
|
84
|
+
|
|
85
|
+
#### Variant System
|
|
86
|
+
|
|
87
|
+
- **Basic Variants**: Define component variations with different styling options
|
|
88
|
+
- **Boolean Variants**: Support for boolean-based variant conditions
|
|
89
|
+
- **Default Variants**: Set default values for variant properties
|
|
90
|
+
- **Nested Arrays**: Support for nested array structures in class definitions
|
|
91
|
+
|
|
92
|
+
#### Advanced Features
|
|
93
|
+
|
|
94
|
+
- **Slots**: Multi-part component styling with individual slot control
|
|
95
|
+
- **Compound Variants**: Apply styles when multiple variant conditions are met
|
|
96
|
+
- **Compound Slots**: Apply styles to specific slots based on variant conditions
|
|
97
|
+
- **Configuration Extension**: Extend and override existing variant configurations
|
|
98
|
+
|
|
99
|
+
#### Developer Experience
|
|
100
|
+
|
|
101
|
+
- **Type Safety**: Full TypeScript support with intelligent type inference
|
|
102
|
+
- **Tailwind Merge**: Built-in conflict resolution for Tailwind CSS classes
|
|
103
|
+
- **Performance**: Optimized for minimal runtime overhead
|
|
104
|
+
- **Flexibility**: Support for custom class merging and configuration
|
|
105
|
+
|
|
106
|
+
### Basic Variants
|
|
107
|
+
|
|
108
|
+
Create variant functions with different styling options:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
112
|
+
|
|
113
|
+
const button = tv({
|
|
114
|
+
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors",
|
|
115
|
+
variants: {
|
|
116
|
+
variant: {
|
|
117
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
118
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
119
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
120
|
+
},
|
|
121
|
+
size: {
|
|
122
|
+
sm: "h-9 px-3 text-sm",
|
|
123
|
+
md: "h-10 px-4",
|
|
124
|
+
lg: "h-11 px-8",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
defaultVariants: {
|
|
128
|
+
variant: "default",
|
|
129
|
+
size: "md",
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Usage examples
|
|
134
|
+
console.log(button());
|
|
135
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4"
|
|
136
|
+
|
|
137
|
+
console.log(button({ variant: "destructive", size: "lg" }));
|
|
138
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-destructive text-destructive-foreground hover:bg-destructive/90 h-11 px-8"
|
|
139
|
+
|
|
140
|
+
console.log(button({ variant: "outline", size: "sm" }));
|
|
141
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors border border-input bg-background hover:bg-accent hover:text-accent-foreground h-9 px-3 text-sm"
|
|
142
|
+
|
|
143
|
+
// Add custom classes
|
|
144
|
+
console.log(button({ className: "w-full" }));
|
|
145
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 w-full"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Slots - Multi-part Components
|
|
149
|
+
|
|
150
|
+
Slots enable styling for components with multiple parts:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
154
|
+
|
|
155
|
+
const card = tv({
|
|
156
|
+
slots: {
|
|
157
|
+
base: "rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
158
|
+
header: "flex flex-col space-y-1.5 p-6",
|
|
159
|
+
content: "p-6 pt-0",
|
|
160
|
+
footer: "flex items-center p-6 pt-0",
|
|
161
|
+
},
|
|
162
|
+
variants: {
|
|
163
|
+
variant: {
|
|
164
|
+
default: "",
|
|
165
|
+
destructive: {
|
|
166
|
+
base: "border-destructive",
|
|
167
|
+
header: "text-destructive",
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Usage
|
|
174
|
+
const cardStyles = card();
|
|
175
|
+
console.log(cardStyles.base());
|
|
176
|
+
// Output: "rounded-lg border bg-card text-card-foreground shadow-sm"
|
|
177
|
+
|
|
178
|
+
console.log(cardStyles.header());
|
|
179
|
+
// Output: "flex flex-col space-y-1.5 p-6"
|
|
180
|
+
|
|
181
|
+
console.log(cardStyles.content());
|
|
182
|
+
// Output: "p-6 pt-0"
|
|
183
|
+
|
|
184
|
+
console.log(cardStyles.footer());
|
|
185
|
+
// Output: "flex items-center p-6 pt-0"
|
|
186
|
+
|
|
187
|
+
// With variant
|
|
188
|
+
const destructiveCard = card({ variant: "destructive" });
|
|
189
|
+
console.log(destructiveCard.base());
|
|
190
|
+
// Output: "rounded-lg border bg-card text-card-foreground shadow-sm border-destructive"
|
|
191
|
+
|
|
192
|
+
console.log(destructiveCard.header());
|
|
193
|
+
// Output: "flex flex-col space-y-1.5 p-6 text-destructive"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Compound Variants
|
|
197
|
+
|
|
198
|
+
Apply styles when multiple variant conditions are met:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
202
|
+
|
|
203
|
+
const alert = tv({
|
|
204
|
+
base: "relative w-full rounded-lg border px-4 py-3",
|
|
205
|
+
variants: {
|
|
206
|
+
variant: {
|
|
207
|
+
default: "bg-background text-foreground",
|
|
208
|
+
destructive: "border-destructive/50 text-destructive",
|
|
209
|
+
},
|
|
210
|
+
size: {
|
|
211
|
+
sm: "text-sm",
|
|
212
|
+
md: "text-base",
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
compoundVariants: [
|
|
216
|
+
{
|
|
217
|
+
variant: "destructive",
|
|
218
|
+
size: "md",
|
|
219
|
+
className: "font-semibold", // Only applies when variant=destructive AND size=md
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
defaultVariants: {
|
|
223
|
+
variant: "default",
|
|
224
|
+
size: "md",
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Usage examples
|
|
229
|
+
console.log(alert({ size: "sm" }));
|
|
230
|
+
// Output: "relative w-full rounded-lg border px-4 py-3 bg-background text-foreground text-sm"
|
|
231
|
+
|
|
232
|
+
console.log(alert({ variant: "destructive", size: "md" }));
|
|
233
|
+
// Output: "relative w-full rounded-lg border px-4 py-3 border-destructive/50 text-destructive text-base font-semibold"
|
|
234
|
+
|
|
235
|
+
console.log(alert({ variant: "destructive", size: "sm" }));
|
|
236
|
+
// Output: "relative w-full rounded-lg border px-4 py-3 border-destructive/50 text-destructive text-sm"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Boolean Variants
|
|
240
|
+
|
|
241
|
+
Support for boolean-based variant conditions:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
245
|
+
|
|
246
|
+
const toggle = tv({
|
|
247
|
+
base: "inline-flex items-center justify-center rounded-md text-sm font-medium",
|
|
248
|
+
variants: {
|
|
249
|
+
pressed: {
|
|
250
|
+
true: "bg-accent text-accent-foreground",
|
|
251
|
+
false: "bg-transparent",
|
|
252
|
+
},
|
|
253
|
+
disabled: {
|
|
254
|
+
true: "opacity-50 pointer-events-none",
|
|
255
|
+
false: "",
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
defaultVariants: {
|
|
259
|
+
pressed: false,
|
|
260
|
+
disabled: false,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Usage with boolean values
|
|
265
|
+
console.log(toggle());
|
|
266
|
+
// Output: "inline-flex items-center justify-center rounded-md text-sm font-medium bg-transparent"
|
|
267
|
+
|
|
268
|
+
console.log(toggle({ pressed: true }));
|
|
269
|
+
// Output: "inline-flex items-center justify-center rounded-md text-sm font-medium bg-accent text-accent-foreground"
|
|
270
|
+
|
|
271
|
+
console.log(toggle({ disabled: true }));
|
|
272
|
+
// Output: "inline-flex items-center justify-center rounded-md text-sm font-medium bg-transparent opacity-50 pointer-events-none"
|
|
273
|
+
|
|
274
|
+
console.log(toggle({ pressed: true, disabled: true }));
|
|
275
|
+
// Output: "inline-flex items-center justify-center rounded-md text-sm font-medium bg-accent text-accent-foreground opacity-50 pointer-events-none"
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Configuration Extension
|
|
279
|
+
|
|
280
|
+
Extend existing configurations for reusability:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
284
|
+
|
|
285
|
+
// Base button configuration
|
|
286
|
+
const baseButton = tv({
|
|
287
|
+
base: "inline-flex items-center justify-center rounded-md font-medium",
|
|
288
|
+
variants: {
|
|
289
|
+
size: {
|
|
290
|
+
sm: "h-9 px-3 text-sm",
|
|
291
|
+
md: "h-10 px-4",
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
defaultVariants: {
|
|
295
|
+
size: "md",
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Extended icon button
|
|
300
|
+
const iconButton = tv({
|
|
301
|
+
extend: baseButton,
|
|
302
|
+
base: "aspect-square", // Additional base classes
|
|
303
|
+
variants: {
|
|
304
|
+
variant: {
|
|
305
|
+
// New variant options
|
|
306
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
307
|
+
outline: "border border-input",
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
defaultVariants: {
|
|
311
|
+
variant: "ghost",
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Usage
|
|
316
|
+
console.log(baseButton());
|
|
317
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium h-10 px-4"
|
|
318
|
+
|
|
319
|
+
console.log(iconButton());
|
|
320
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium aspect-square h-10 px-4 hover:bg-accent hover:text-accent-foreground"
|
|
321
|
+
|
|
322
|
+
console.log(iconButton({ variant: "outline", size: "sm" }));
|
|
323
|
+
// Output: "inline-flex items-center justify-center rounded-md font-medium aspect-square h-9 px-3 text-sm border border-input"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Advanced Features
|
|
327
|
+
|
|
328
|
+
### Compound Slots
|
|
329
|
+
|
|
330
|
+
Apply styles to specific slots based on variant conditions:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
334
|
+
|
|
335
|
+
const dialog = tv({
|
|
336
|
+
slots: {
|
|
337
|
+
overlay: "fixed inset-0 bg-background/80 backdrop-blur-sm",
|
|
338
|
+
content: "fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2",
|
|
339
|
+
header: "flex flex-col space-y-1.5 text-center sm:text-left",
|
|
340
|
+
footer: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
341
|
+
},
|
|
342
|
+
variants: {
|
|
343
|
+
size: {
|
|
344
|
+
sm: "",
|
|
345
|
+
lg: "",
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
compoundSlots: [
|
|
349
|
+
{
|
|
350
|
+
size: "sm",
|
|
351
|
+
slots: ["content"],
|
|
352
|
+
className: "max-w-md",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
size: "lg",
|
|
356
|
+
slots: ["content"],
|
|
357
|
+
className: "max-w-2xl",
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Usage
|
|
363
|
+
const smallDialog = dialog({ size: "sm" });
|
|
364
|
+
console.log(smallDialog.content());
|
|
365
|
+
// Output: "fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 max-w-md"
|
|
366
|
+
|
|
367
|
+
const largeDialog = dialog({ size: "lg" });
|
|
368
|
+
console.log(largeDialog.content());
|
|
369
|
+
// Output: "fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 max-w-2xl"
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Global Configuration with createTV
|
|
373
|
+
|
|
374
|
+
Create a factory with global configuration settings:
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { createTV } from "@codefast/tailwind-variants";
|
|
378
|
+
|
|
379
|
+
// Create factory with global configuration
|
|
380
|
+
const { tv, cn } = createTV({
|
|
381
|
+
twMerge: true,
|
|
382
|
+
twMergeConfig: {
|
|
383
|
+
extend: {
|
|
384
|
+
classGroups: {
|
|
385
|
+
// Custom class groups for better conflict resolution
|
|
386
|
+
"font-size": ["text-custom-sm", "text-custom-lg"],
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Use tv with global configuration
|
|
393
|
+
const button = tv({
|
|
394
|
+
base: "px-4 py-2 rounded",
|
|
395
|
+
variants: {
|
|
396
|
+
variant: {
|
|
397
|
+
primary: "bg-blue-500 text-white",
|
|
398
|
+
secondary: "bg-gray-500 text-white",
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Use cn utility with global configuration
|
|
404
|
+
const classes = cn("px-4 py-2", "px-6 py-3"); // Tailwind merge resolves conflicts
|
|
405
|
+
console.log(classes);
|
|
406
|
+
// Output: "px-6 py-3" (px-6 overrides px-4, py-3 overrides py-2)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Nested Arrays Support
|
|
410
|
+
|
|
411
|
+
Support for nested array structures in class definitions:
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
415
|
+
|
|
416
|
+
const component = tv({
|
|
417
|
+
base: ["base-class-1", ["base-class-2", ["base-class-3", "base-class-4"]]], // Automatically flattened
|
|
418
|
+
variants: {
|
|
419
|
+
variant: {
|
|
420
|
+
primary: ["text-blue-500", ["bg-blue-50", ["hover:bg-blue-100", "focus:ring-blue-200"]]],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## API Reference
|
|
427
|
+
|
|
428
|
+
### tv(config, options?)
|
|
429
|
+
|
|
430
|
+
Creates a variant function from configuration.
|
|
431
|
+
|
|
432
|
+
**Parameters**:
|
|
433
|
+
|
|
434
|
+
- `config`: Configuration object defining variants, slots, and styling
|
|
435
|
+
- `options?`: Optional Tailwind Variants configuration for customization
|
|
436
|
+
|
|
437
|
+
**Returns**: Configured variant function
|
|
438
|
+
|
|
439
|
+
### createTV(globalConfig?)
|
|
440
|
+
|
|
441
|
+
Creates a factory with global configuration settings.
|
|
442
|
+
|
|
443
|
+
**Parameters**:
|
|
444
|
+
|
|
445
|
+
- `globalConfig?`: Global Tailwind Variants configuration applied to all instances
|
|
446
|
+
|
|
447
|
+
**Returns**: Object containing `tv` and `cn` functions with global settings
|
|
448
|
+
|
|
449
|
+
### cn(...classes)
|
|
450
|
+
|
|
451
|
+
Combines and merges CSS classes using tailwind-merge for conflict resolution.
|
|
452
|
+
|
|
453
|
+
**Parameters**:
|
|
454
|
+
|
|
455
|
+
- `...classes`: CSS classes to combine and merge
|
|
456
|
+
|
|
457
|
+
**Returns**: Merged class string with conflicts resolved
|
|
458
|
+
|
|
459
|
+
### cx(...classes)
|
|
460
|
+
|
|
461
|
+
Combines CSS classes using clsx without conflict resolution.
|
|
462
|
+
|
|
463
|
+
**Parameters**:
|
|
464
|
+
|
|
465
|
+
- `...classes`: CSS classes to combine
|
|
466
|
+
|
|
467
|
+
**Returns**: Combined class string without merging
|
|
468
|
+
|
|
469
|
+
### VariantProps<T>
|
|
470
|
+
|
|
471
|
+
Extracts variant props type from a variant function for TypeScript integration.
|
|
472
|
+
|
|
473
|
+
**Type Parameter**:
|
|
474
|
+
|
|
475
|
+
- `T`: Variant function type
|
|
476
|
+
|
|
477
|
+
**Returns**: Props type object for component integration
|
|
478
|
+
|
|
479
|
+
### TypeScript Integration
|
|
480
|
+
|
|
481
|
+
The library provides comprehensive TypeScript support:
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
import { tv, type VariantProps } from "@codefast/tailwind-variants";
|
|
485
|
+
|
|
486
|
+
const button = tv({
|
|
487
|
+
base: "px-4 py-2 rounded",
|
|
488
|
+
variants: {
|
|
489
|
+
variant: {
|
|
490
|
+
primary: "bg-blue-500 text-white",
|
|
491
|
+
secondary: "bg-gray-500 text-white",
|
|
492
|
+
},
|
|
493
|
+
size: {
|
|
494
|
+
sm: "text-sm",
|
|
495
|
+
lg: "text-lg",
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// Extract props type
|
|
501
|
+
type ButtonProps = VariantProps<typeof button>;
|
|
502
|
+
// Type: { variant?: "primary" | "secondary"; size?: "sm" | "lg"; className?: string; }
|
|
503
|
+
|
|
504
|
+
// Usage in React components
|
|
505
|
+
interface MyButtonProps extends ButtonProps {
|
|
506
|
+
children: React.ReactNode;
|
|
507
|
+
onClick?: () => void;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const MyButton: React.FC<MyButtonProps> = ({
|
|
511
|
+
variant,
|
|
512
|
+
size,
|
|
513
|
+
className,
|
|
514
|
+
children,
|
|
515
|
+
onClick
|
|
516
|
+
}) => {
|
|
517
|
+
return (
|
|
518
|
+
<button
|
|
519
|
+
className={button({ variant, size, className })}
|
|
520
|
+
onClick={onClick}
|
|
521
|
+
>
|
|
522
|
+
{children}
|
|
523
|
+
</button>
|
|
524
|
+
);
|
|
525
|
+
};
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Slots Type Safety
|
|
529
|
+
|
|
530
|
+
Slots provide full type safety for multi-part components:
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
534
|
+
|
|
535
|
+
const card = tv({
|
|
536
|
+
slots: {
|
|
537
|
+
base: "rounded-lg border",
|
|
538
|
+
header: "p-4 border-b",
|
|
539
|
+
content: "p-4",
|
|
540
|
+
},
|
|
541
|
+
variants: {
|
|
542
|
+
variant: {
|
|
543
|
+
default: "",
|
|
544
|
+
elevated: {
|
|
545
|
+
base: "shadow-lg",
|
|
546
|
+
header: "bg-muted",
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// Type inference for slots
|
|
553
|
+
const styles = card({ variant: "elevated" });
|
|
554
|
+
// styles.base() - available
|
|
555
|
+
// styles.header() - available
|
|
556
|
+
// styles.content() - available
|
|
557
|
+
// styles.nonExistent() - TypeScript error!
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Configuration Options
|
|
561
|
+
|
|
562
|
+
Customize Tailwind merge behavior and other settings:
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
566
|
+
|
|
567
|
+
const component = tv(
|
|
568
|
+
{
|
|
569
|
+
base: "px-4 py-2",
|
|
570
|
+
variants: {
|
|
571
|
+
size: {
|
|
572
|
+
sm: "px-2 py-1",
|
|
573
|
+
lg: "px-6 py-3",
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
twMerge: true, // Enable/disable tailwind merge (default: true)
|
|
579
|
+
twMergeConfig: {
|
|
580
|
+
extend: {
|
|
581
|
+
classGroups: {
|
|
582
|
+
// Custom class groups for better conflict resolution
|
|
583
|
+
"font-size": ["text-custom-sm", "text-custom-lg"],
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
);
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Disable Tailwind Merge
|
|
592
|
+
|
|
593
|
+
In some cases, you may want to disable Tailwind merge:
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
597
|
+
|
|
598
|
+
const component = tv(
|
|
599
|
+
{
|
|
600
|
+
base: "px-4 py-2",
|
|
601
|
+
variants: {
|
|
602
|
+
size: {
|
|
603
|
+
sm: "px-2 py-1", // Will not merge with base classes
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
twMerge: false, // Disable tailwind merge
|
|
609
|
+
},
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
console.log(component({ size: "sm" }));
|
|
613
|
+
// Output: "px-4 py-2 px-2 py-1" (both px classes will be kept)
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## Utility Functions
|
|
617
|
+
|
|
618
|
+
### cn Function
|
|
619
|
+
|
|
620
|
+
Combine and merge CSS classes with conflict resolution:
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
import { cn } from "@codefast/tailwind-variants";
|
|
624
|
+
|
|
625
|
+
// Basic usage
|
|
626
|
+
const classes = cn("px-4 py-2", "bg-blue-500", "text-white");
|
|
627
|
+
console.log(classes);
|
|
628
|
+
// Output: "px-4 py-2 bg-blue-500 text-white"
|
|
629
|
+
|
|
630
|
+
// With conflicting classes (tailwind-merge resolves conflicts)
|
|
631
|
+
const conflicting = cn("px-4 py-2", "px-6 py-3");
|
|
632
|
+
console.log(conflicting);
|
|
633
|
+
// Output: "px-6 py-3" (later classes override earlier ones)
|
|
634
|
+
|
|
635
|
+
// With conditional classes
|
|
636
|
+
const conditional = cn(
|
|
637
|
+
"base-class",
|
|
638
|
+
true && "condition-true-class",
|
|
639
|
+
false && "condition-false-class",
|
|
640
|
+
{ "object-condition": true },
|
|
641
|
+
);
|
|
642
|
+
console.log(conditional);
|
|
643
|
+
// Output: "base-class condition-true-class object-condition"
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### cx Function
|
|
647
|
+
|
|
648
|
+
Combine CSS classes without conflict resolution:
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
import { cx } from "@codefast/tailwind-variants";
|
|
652
|
+
|
|
653
|
+
const classes = cx("px-4 py-2", "px-6 py-3");
|
|
654
|
+
console.log(classes);
|
|
655
|
+
// Output: "px-4 py-2 px-6 py-3" (no conflict resolution)
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
## Best Practices
|
|
659
|
+
|
|
660
|
+
### Component Library Pattern
|
|
661
|
+
|
|
662
|
+
Create consistent component libraries:
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
// components/button.ts
|
|
666
|
+
import { tv, type VariantProps } from "@codefast/tailwind-variants";
|
|
667
|
+
|
|
668
|
+
export const buttonVariants = tv({
|
|
669
|
+
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
|
|
670
|
+
variants: {
|
|
671
|
+
variant: {
|
|
672
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
673
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
674
|
+
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
|
|
675
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
676
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
677
|
+
link: "underline-offset-4 hover:underline text-primary",
|
|
678
|
+
},
|
|
679
|
+
size: {
|
|
680
|
+
default: "h-10 py-2 px-4",
|
|
681
|
+
sm: "h-9 px-3 rounded-md",
|
|
682
|
+
lg: "h-11 px-8 rounded-md",
|
|
683
|
+
icon: "h-10 w-10",
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
defaultVariants: {
|
|
687
|
+
variant: "default",
|
|
688
|
+
size: "default",
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
export type ButtonProps = VariantProps<typeof buttonVariants>;
|
|
693
|
+
|
|
694
|
+
// components/Button.tsx (React component)
|
|
695
|
+
import React from "react";
|
|
696
|
+
import { buttonVariants, type ButtonProps } from "./button";
|
|
697
|
+
|
|
698
|
+
interface ButtonComponentProps
|
|
699
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
700
|
+
ButtonProps {
|
|
701
|
+
asChild?: boolean;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonComponentProps>(
|
|
705
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
706
|
+
return (
|
|
707
|
+
<button
|
|
708
|
+
className={buttonVariants({ variant, size, className })}
|
|
709
|
+
ref={ref}
|
|
710
|
+
{...props}
|
|
711
|
+
/>
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
Button.displayName = "Button";
|
|
717
|
+
|
|
718
|
+
export { Button };
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### Theme System Pattern
|
|
722
|
+
|
|
723
|
+
Create theme systems with global configuration:
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
// theme/variants.ts
|
|
727
|
+
import { createTV } from "@codefast/tailwind-variants";
|
|
728
|
+
|
|
729
|
+
// Global theme configuration
|
|
730
|
+
const { tv: themeTV, cn: themeCN } = createTV({
|
|
731
|
+
twMerge: true,
|
|
732
|
+
twMergeConfig: {
|
|
733
|
+
extend: {
|
|
734
|
+
classGroups: {
|
|
735
|
+
// Theme-specific class groups
|
|
736
|
+
"theme-color": ["theme-primary", "theme-secondary", "theme-accent"],
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// Export themed utilities
|
|
743
|
+
export { themeTV as tv, themeCN as cn };
|
|
744
|
+
|
|
745
|
+
// components/themed-button.ts
|
|
746
|
+
import { tv } from "../theme/variants";
|
|
747
|
+
|
|
748
|
+
export const themedButton = tv({
|
|
749
|
+
base: "inline-flex items-center justify-center rounded-md font-medium",
|
|
750
|
+
variants: {
|
|
751
|
+
theme: {
|
|
752
|
+
light: "bg-white text-black border border-gray-200",
|
|
753
|
+
dark: "bg-gray-900 text-white border border-gray-700",
|
|
754
|
+
},
|
|
755
|
+
variant: {
|
|
756
|
+
primary: "theme-primary", // Handled by custom class group
|
|
757
|
+
secondary: "theme-secondary",
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Responsive Design Pattern
|
|
764
|
+
|
|
765
|
+
Use with responsive classes for mobile-first design:
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
769
|
+
|
|
770
|
+
const responsiveCard = tv({
|
|
771
|
+
base: "rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
772
|
+
variants: {
|
|
773
|
+
size: {
|
|
774
|
+
sm: "p-4 sm:p-6",
|
|
775
|
+
md: "p-6 sm:p-8 lg:p-10",
|
|
776
|
+
lg: "p-8 sm:p-10 lg:p-12 xl:p-16",
|
|
777
|
+
},
|
|
778
|
+
layout: {
|
|
779
|
+
stack: "flex flex-col space-y-4",
|
|
780
|
+
grid: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
compoundVariants: [
|
|
784
|
+
{
|
|
785
|
+
size: "lg",
|
|
786
|
+
layout: "grid",
|
|
787
|
+
className: "lg:grid-cols-4 xl:grid-cols-5",
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
defaultVariants: {
|
|
791
|
+
size: "md",
|
|
792
|
+
layout: "stack",
|
|
793
|
+
},
|
|
794
|
+
});
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
### Performance Optimization
|
|
798
|
+
|
|
799
|
+
Optimize for performance:
|
|
800
|
+
|
|
801
|
+
```typescript
|
|
802
|
+
// ✓ Good: Define variants outside component
|
|
803
|
+
const buttonVariants = tv({
|
|
804
|
+
base: "px-4 py-2 rounded",
|
|
805
|
+
variants: {
|
|
806
|
+
variant: {
|
|
807
|
+
primary: "bg-blue-500 text-white",
|
|
808
|
+
secondary: "bg-gray-500 text-white",
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
const Button = ({ variant, className, ...props }) => {
|
|
814
|
+
return (
|
|
815
|
+
<button
|
|
816
|
+
className={buttonVariants({ variant, className })}
|
|
817
|
+
{...props}
|
|
818
|
+
/>
|
|
819
|
+
);
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
// ✗ Bad: Define variants inside component (recreated on every render)
|
|
823
|
+
const Button = ({ variant, className, ...props }) => {
|
|
824
|
+
const buttonVariants = tv({
|
|
825
|
+
base: "px-4 py-2 rounded",
|
|
826
|
+
variants: {
|
|
827
|
+
variant: {
|
|
828
|
+
primary: "bg-blue-500 text-white",
|
|
829
|
+
secondary: "bg-gray-500 text-white",
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
return (
|
|
835
|
+
<button
|
|
836
|
+
className={buttonVariants({ variant, className })}
|
|
837
|
+
{...props}
|
|
838
|
+
/>
|
|
839
|
+
);
|
|
840
|
+
};
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
## Performance Considerations
|
|
844
|
+
|
|
845
|
+
### Bundle Size
|
|
846
|
+
|
|
847
|
+
The library is optimized for minimal bundle impact:
|
|
848
|
+
|
|
849
|
+
- Tree-shakeable exports for unused functionality
|
|
850
|
+
- Minimal runtime dependencies
|
|
851
|
+
- Efficient class processing algorithms
|
|
852
|
+
|
|
853
|
+
### Runtime Performance
|
|
854
|
+
|
|
855
|
+
- Lazy evaluation of variant resolution
|
|
856
|
+
- Efficient class merging algorithms
|
|
857
|
+
- Minimal memory footprint
|
|
858
|
+
- Cached tailwind-merge instances for reuse
|
|
859
|
+
|
|
860
|
+
## Migration Guide
|
|
861
|
+
|
|
862
|
+
### From class-variance-authority (cva)
|
|
863
|
+
|
|
864
|
+
Migrating from other variant libraries:
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
// Before (cva)
|
|
868
|
+
import { cva } from "class-variance-authority";
|
|
869
|
+
|
|
870
|
+
const button = cva("px-4 py-2 rounded", {
|
|
871
|
+
variants: {
|
|
872
|
+
variant: {
|
|
873
|
+
primary: "bg-blue-500 text-white",
|
|
874
|
+
secondary: "bg-gray-500 text-white",
|
|
875
|
+
},
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// After (@codefast/tailwind-variants)
|
|
880
|
+
import { tv } from "@codefast/tailwind-variants";
|
|
881
|
+
|
|
882
|
+
const button = tv({
|
|
883
|
+
base: "px-4 py-2 rounded",
|
|
884
|
+
variants: {
|
|
885
|
+
variant: {
|
|
886
|
+
primary: "bg-blue-500 text-white",
|
|
887
|
+
secondary: "bg-gray-500 text-white",
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
### Key Differences
|
|
894
|
+
|
|
895
|
+
1. **API Structure**: Uses `base` property instead of first string parameter
|
|
896
|
+
2. **Slots Support**: Native support for multi-part components
|
|
897
|
+
3. **Extended Configuration**: Built-in support for configuration extension
|
|
898
|
+
4. **Enhanced TypeScript**: Improved type inference and safety
|
|
899
|
+
5. **Tailwind Merge**: Built-in conflict resolution
|
|
900
|
+
|
|
901
|
+
## Contributing
|
|
902
|
+
|
|
903
|
+
We welcome all contributions! To get started with development:
|
|
904
|
+
|
|
905
|
+
### Environment Setup
|
|
906
|
+
|
|
907
|
+
1. Fork this repository
|
|
908
|
+
2. Clone to your machine: `git clone <your-fork-url>`
|
|
909
|
+
3. Install dependencies: `pnpm install`
|
|
910
|
+
4. Create a new branch: `git checkout -b feature/feature-name`
|
|
911
|
+
|
|
912
|
+
### Development Workflow
|
|
913
|
+
|
|
914
|
+
```bash
|
|
915
|
+
# Build all packages
|
|
916
|
+
pnpm build:packages
|
|
917
|
+
|
|
918
|
+
# Development mode for tailwind-variants
|
|
919
|
+
pnpm dev --filter=@codefast/tailwind-variants
|
|
920
|
+
|
|
921
|
+
# Run tests
|
|
922
|
+
pnpm test --filter=@codefast/tailwind-variants
|
|
923
|
+
|
|
924
|
+
# Run tests with coverage
|
|
925
|
+
pnpm test:coverage --filter=@codefast/tailwind-variants
|
|
926
|
+
|
|
927
|
+
# Lint and format
|
|
928
|
+
pnpm lint:fix
|
|
929
|
+
pnpm format
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
### Adding New Features
|
|
933
|
+
|
|
934
|
+
1. Implement feature in `src/` directory
|
|
935
|
+
2. Add comprehensive tests in `tests/` directory
|
|
936
|
+
3. Update TypeScript types as needed
|
|
937
|
+
4. Update documentation
|
|
938
|
+
5. Submit a pull request
|
|
939
|
+
|
|
940
|
+
See details at [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
|
941
|
+
|
|
942
|
+
## License
|
|
943
|
+
|
|
944
|
+
Distributed under the MIT License. See [LICENSE](../../LICENSE) for more details.
|
|
945
|
+
|
|
946
|
+
## Contact
|
|
947
|
+
|
|
948
|
+
- npm: [@codefast/tailwind-variants](https://www.npmjs.com/package/@codefast/tailwind-variants)
|
|
949
|
+
- GitHub: [codefastlabs/codefast](https://github.com/codefastlabs/codefast)
|
|
950
|
+
- Issues: [GitHub Issues](https://github.com/codefastlabs/codefast/issues)
|
|
951
|
+
- Documentation: [Component Docs](https://codefast.dev/docs/components)
|
|
952
|
+
|
|
953
|
+
## Acknowledgments
|
|
954
|
+
|
|
955
|
+
This library is built on top of excellent open-source projects:
|
|
956
|
+
|
|
957
|
+
- **[clsx](https://github.com/lukeed/clsx)** - Utility for constructing className strings
|
|
958
|
+
- **[tailwind-merge](https://github.com/dcastil/tailwind-merge)** - Utility for merging Tailwind CSS classes
|
|
959
|
+
- **[Tailwind CSS](https://tailwindcss.com/)** - Utility-first CSS framework
|
|
960
|
+
|
|
961
|
+
## Changelog
|
|
962
|
+
|
|
963
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a complete list of changes and version history.
|