classname-variants 1.7.0 → 1.7.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/LICENSE +7 -0
- package/README.md +112 -9
- package/llms.txt +61 -9
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2022 Felix Gnass
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -12,7 +12,28 @@ Library to create type-safe components that render their class name based on a s
|
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install classname-variants
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Import Paths
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// Core — vanilla DOM, no framework dependency
|
|
25
|
+
import { variants, classNames, tw } from "classname-variants";
|
|
26
|
+
|
|
27
|
+
// React — styled components, variantProps, type utilities
|
|
28
|
+
import { styled, variantProps, tw } from "classname-variants/react";
|
|
29
|
+
import type { VariantPropsOf } from "classname-variants/react";
|
|
30
|
+
|
|
31
|
+
// Preact — same API, accepts both `class` and `className`
|
|
32
|
+
import { styled, variantProps, tw } from "classname-variants/preact";
|
|
33
|
+
import type { VariantPropsOf } from "classname-variants/preact";
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
16
37
|
|
|
17
38
|
Here is an example that uses React and Tailwind CSS:
|
|
18
39
|
|
|
@@ -36,11 +57,9 @@ function UsageExample() {
|
|
|
36
57
|
}
|
|
37
58
|
```
|
|
38
59
|
|
|
39
|
-
[](https://codesandbox.io/s/classname-variants-react-3bzjl?fontsize=14&hidenavigation=1&theme=dark)
|
|
40
|
-
|
|
41
60
|
While the library has been designed with tools like Tailwind in mind, it can be also used with custom classes or CSS modules:
|
|
42
61
|
|
|
43
|
-
|
|
62
|
+
### Preact + CSS modules
|
|
44
63
|
|
|
45
64
|
```tsx
|
|
46
65
|
import { styled } from "classname-variants/preact";
|
|
@@ -56,7 +75,7 @@ const Button = styled("button", {
|
|
|
56
75
|
});
|
|
57
76
|
```
|
|
58
77
|
|
|
59
|
-
|
|
78
|
+
### Vanilla DOM
|
|
60
79
|
|
|
61
80
|
The core of the library is completely framework-agnostic:
|
|
62
81
|
|
|
@@ -80,7 +99,7 @@ document.write(`
|
|
|
80
99
|
`);
|
|
81
100
|
```
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
## API
|
|
84
103
|
|
|
85
104
|
### Defining variants
|
|
86
105
|
|
|
@@ -121,6 +140,8 @@ Variants can be typed as `boolean` by using `true` / `false` as key:
|
|
|
121
140
|
<Button primary>Click Me!</Button>
|
|
122
141
|
```
|
|
123
142
|
|
|
143
|
+
### Compound variants
|
|
144
|
+
|
|
124
145
|
The `compoundVariants` option can be used to apply class names based on a combination of other variants:
|
|
125
146
|
|
|
126
147
|
```ts
|
|
@@ -231,6 +252,71 @@ const Button = styled("button", {
|
|
|
231
252
|
<Button disabled />;
|
|
232
253
|
```
|
|
233
254
|
|
|
255
|
+
### Chaining additional class names
|
|
256
|
+
|
|
257
|
+
Styled components accept a `className` prop that gets merged with the variant output. This is useful for one-off overrides:
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
<Button className="mt-4" size="large">Submit</Button>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The Preact adapter accepts both `class` and `className` — use whichever you prefer:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
<Button class="mt-4" size="large">Submit</Button>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Ref forwarding
|
|
270
|
+
|
|
271
|
+
All `styled()` components support refs via `React.forwardRef`:
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
const Input = styled("input", {
|
|
275
|
+
base: "border rounded px-2",
|
|
276
|
+
variants: { ... },
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
280
|
+
<Input ref={ref} />;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### `variantProps()`
|
|
284
|
+
|
|
285
|
+
The lower-level `variantProps()` function lets you separate variant logic from rendering. This is useful for headless components or when you need more control:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { variantProps } from "classname-variants/react";
|
|
289
|
+
|
|
290
|
+
const buttonProps = variantProps({
|
|
291
|
+
base: "rounded px-4 py-2",
|
|
292
|
+
variants: {
|
|
293
|
+
intent: {
|
|
294
|
+
primary: "bg-teal-500 text-white",
|
|
295
|
+
secondary: "bg-slate-200",
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
function Button(props) {
|
|
301
|
+
// Extracts variant props, returns { className, ...rest }
|
|
302
|
+
const { className, ...rest } = buttonProps(props);
|
|
303
|
+
return <button className={className} {...rest} />;
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### `VariantPropsOf<T>`
|
|
308
|
+
|
|
309
|
+
Use this utility type to extract the variant props accepted by a `variantProps` function — helpful when building wrapper components:
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
import { variantProps, type VariantPropsOf } from "classname-variants/react";
|
|
313
|
+
|
|
314
|
+
const buttonProps = variantProps({ ... });
|
|
315
|
+
|
|
316
|
+
type ButtonProps = VariantPropsOf<typeof buttonProps>;
|
|
317
|
+
// { intent: "primary" | "secondary"; className?: string }
|
|
318
|
+
```
|
|
319
|
+
|
|
234
320
|
### Styling custom components
|
|
235
321
|
|
|
236
322
|
You can style any custom React/Preact component as long as they accept a `className` prop (or `class` in case of Preact).
|
|
@@ -293,7 +379,24 @@ import { twMerge } from "tailwind-merge";
|
|
|
293
379
|
classNames.combine = twMerge;
|
|
294
380
|
```
|
|
295
381
|
|
|
296
|
-
|
|
382
|
+
## Why classname-variants?
|
|
383
|
+
|
|
384
|
+
### vs clsx / classnames
|
|
385
|
+
|
|
386
|
+
- **Type safety** — full TypeScript inference for variant props instead of manual conditional logic
|
|
387
|
+
- **Variant system** — built-in support for default values, compound variants, and boolean variants
|
|
388
|
+
- **Framework bindings** — `styled()` creates ready-to-use React/Preact components
|
|
389
|
+
|
|
390
|
+
### vs class-variance-authority (cva)
|
|
391
|
+
|
|
392
|
+
- **Zero dependencies** — no external runtime dependencies
|
|
393
|
+
- **Framework integration** — built-in `styled()` API with polymorphic `as` prop, ref forwarding, and `defaultProps`
|
|
394
|
+
- **Prop forwarding** — `forwardProps` maps variant values to DOM attributes (e.g. `disabled`)
|
|
395
|
+
- **TypeScript-first** — designed around type inference rather than requiring manual `VariantProps` extraction
|
|
396
|
+
|
|
397
|
+
If you're coming from cva: `cva()` maps to `variants()`, and `VariantProps<typeof x>` maps to `VariantPropsOf<typeof x>`.
|
|
398
|
+
|
|
399
|
+
## Tailwind IntelliSense
|
|
297
400
|
|
|
298
401
|
In order to get auto-completion for the CSS classes themselves, you can use the [Tailwind CSS IntelliSense](https://github.com/tailwindlabs/tailwindcss-intellisense) plugin for VS Code. In order to make it recognize the strings inside your variants-config, you have to somehow mark them and configure the plugin accordingly.
|
|
299
402
|
|
|
@@ -320,10 +423,10 @@ You can then set the _Tailwind CSS: Class Functions_ option to `tw`.
|
|
|
320
423
|
|
|
321
424
|
In order to get type coverage even for your Tailwind classes, you can use a tool like [tailwind-ts](https://github.com/mathieutu/tailwind-ts).
|
|
322
425
|
|
|
323
|
-
|
|
426
|
+
## For AI Assistants
|
|
324
427
|
|
|
325
428
|
For comprehensive technical documentation optimized for LLMs, see [`llms.txt`](./llms.txt).
|
|
326
429
|
|
|
327
|
-
|
|
430
|
+
## License
|
|
328
431
|
|
|
329
432
|
MIT
|
package/llms.txt
CHANGED
|
@@ -16,18 +16,27 @@ This library provides a variant API for generating class names based on componen
|
|
|
16
16
|
|
|
17
17
|
### Framework Integrations
|
|
18
18
|
- `src/react.ts` - React-specific bindings with `styled()` and `variantProps()` functions, including runtime merging and type inference for `defaultProps`
|
|
19
|
-
- `src/preact.ts` - Preact-specific bindings (similar to React but uses Preact's JSX) with equivalent `defaultProps` support
|
|
19
|
+
- `src/preact.ts` - Preact-specific bindings (similar to React but uses Preact's JSX with `class` prop) with equivalent `defaultProps` support
|
|
20
|
+
|
|
21
|
+
### Class Application Order
|
|
22
|
+
When generating class names, the library combines them in this order:
|
|
23
|
+
1. `base` classes (always applied)
|
|
24
|
+
2. Variant classes (based on selected or default variant values)
|
|
25
|
+
3. Compound variant classes (when all conditions match)
|
|
26
|
+
4. User-supplied `className` prop (React/Preact only, via chaining)
|
|
20
27
|
|
|
21
28
|
### Key Features
|
|
22
29
|
- Type-safe variant definitions with full TypeScript inference
|
|
23
30
|
- Support for required and optional variants
|
|
24
31
|
- Boolean variants (true/false)
|
|
25
|
-
- Default variants
|
|
32
|
+
- Default variants via `defaultVariants` (note: NOT "defaults")
|
|
26
33
|
- Compound variants (combinations of multiple variant states)
|
|
27
34
|
- Forwarding variant values via `forwardProps`
|
|
28
35
|
- Polymorphic components with "as" prop
|
|
29
36
|
- Optional `defaultProps` with strong typing (defaulted props become optional while respecting polymorphic `as`)
|
|
30
|
-
-
|
|
37
|
+
- `className` prop chaining on styled components (user classes merged with variant output)
|
|
38
|
+
- Ref forwarding — all `styled()` components use `forwardRef` internally
|
|
39
|
+
- Works with any CSS framework (Tailwind, CSS modules, etc.) without additional dependencies
|
|
31
40
|
|
|
32
41
|
## File Structure
|
|
33
42
|
|
|
@@ -59,8 +68,8 @@ src/
|
|
|
59
68
|
[optionName]: "class-names"
|
|
60
69
|
}
|
|
61
70
|
},
|
|
62
|
-
defaultVariants?: {...}, //
|
|
63
|
-
defaultProps?: {...}, //
|
|
71
|
+
defaultVariants?: {...}, // IMPORTANT: Use "defaultVariants" for default variant values
|
|
72
|
+
defaultProps?: {...}, // DIFFERENT: Use "defaultProps" for non-variant props (e.g. { type: "button" })
|
|
64
73
|
compoundVariants?: [...] // Conditional class combinations
|
|
65
74
|
forwardProps?: ["disabled", ...] // Variant keys to pass through as real props
|
|
66
75
|
}
|
|
@@ -92,9 +101,9 @@ import { styled, tw } from "classname-variants/react"
|
|
|
92
101
|
// Preact
|
|
93
102
|
import { styled, tw } from "classname-variants/preact"
|
|
94
103
|
|
|
95
|
-
// Low-level API
|
|
96
|
-
import { variantProps } from "classname-variants/react"
|
|
97
|
-
import { variantProps } from "classname-variants/preact"
|
|
104
|
+
// Low-level API and type utilities
|
|
105
|
+
import { variantProps, type VariantPropsOf } from "classname-variants/react"
|
|
106
|
+
import { variantProps, type VariantPropsOf } from "classname-variants/preact"
|
|
98
107
|
```
|
|
99
108
|
|
|
100
109
|
### Styled Components Without Variants
|
|
@@ -139,6 +148,44 @@ const Button = styled("button", {
|
|
|
139
148
|
<Button disabled />;
|
|
140
149
|
```
|
|
141
150
|
|
|
151
|
+
### className Prop Chaining
|
|
152
|
+
Styled components accept an additional `className` prop that gets merged with the variant output:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<Button className="mt-4" size="large">Submit</Button>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The Preact adapter accepts both `class` and `className` — they are merged together, so either works:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<Button class="mt-4" size="large">Submit</Button>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### variantProps() for Headless Components
|
|
165
|
+
The lower-level `variantProps()` function separates variant logic from rendering, useful for headless components or custom wrappers:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { variantProps, type VariantPropsOf } from "classname-variants/react";
|
|
169
|
+
|
|
170
|
+
const buttonProps = variantProps({
|
|
171
|
+
base: "rounded px-4 py-2",
|
|
172
|
+
variants: {
|
|
173
|
+
intent: {
|
|
174
|
+
primary: "bg-teal-500 text-white",
|
|
175
|
+
secondary: "bg-slate-200",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Extract the variant prop types for the wrapper component
|
|
181
|
+
type ButtonProps = VariantPropsOf<typeof buttonProps>;
|
|
182
|
+
|
|
183
|
+
function Button(props: ButtonProps) {
|
|
184
|
+
const { className, ...rest } = buttonProps(props);
|
|
185
|
+
return <button className={className} {...rest} />;
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
142
189
|
### Polymorphic Components with "as" Prop
|
|
143
190
|
Change the underlying element/component while keeping the same styles:
|
|
144
191
|
|
|
@@ -153,8 +200,13 @@ const Button = styled("button", { variants: {...} });
|
|
|
153
200
|
|
|
154
201
|
### Tailwind CSS Integration
|
|
155
202
|
|
|
156
|
-
|
|
203
|
+
The library works with Tailwind CSS out of the box without any additional dependencies.
|
|
204
|
+
|
|
205
|
+
#### OPTIONAL: Using tailwind-merge for Class Conflict Resolution
|
|
206
|
+
**This is completely optional.** Only consider using tailwind-merge if you need to resolve conflicting Tailwind classes (e.g., when allowing className overrides). Most use cases don't need this - design your variants to avoid conflicts instead to reduce complexity and overhead.
|
|
207
|
+
|
|
157
208
|
```ts
|
|
209
|
+
// Only if you really need class merging:
|
|
158
210
|
import { classNames } from "classname-variants";
|
|
159
211
|
import { twMerge } from "tailwind-merge";
|
|
160
212
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "classname-variants",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Variant API for plain class names",
|
|
5
5
|
"author": "Felix Gnass <fgnass@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"lint:fix": "biome check --write --unsafe .",
|
|
42
42
|
"start": "npx vite",
|
|
43
43
|
"test": "vitest",
|
|
44
|
-
"prepublishOnly": "npm run build &&
|
|
44
|
+
"prepublishOnly": "npm run build && vitest --run"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
47
47
|
"tailwind",
|