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.
Files changed (4) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +112 -9
  3. package/llms.txt +61 -9
  4. 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
  ![npm bundle size](https://img.shields.io/bundlephobia/minzip/classname-variants)
14
14
 
15
- # Examples
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
- [![Edit classname-variants/react](https://codesandbox.io/static/img/play-codesandbox.svg)](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
- ## Preact + CSS modules
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
- ## Vanilla DOM
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
- # API
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
- # Tailwind IntelliSense
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
- # For AI Assistants
426
+ ## For AI Assistants
324
427
 
325
428
  For comprehensive technical documentation optimized for LLMs, see [`llms.txt`](./llms.txt).
326
429
 
327
- # License
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
- - Custom class name combination strategies
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?: {...}, // Default values
63
- defaultProps?: {...}, // Defaulted non-variant props (e.g. { type: "button" })
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 (rarely used directly)
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
- #### Using tailwind-merge for Conflict Resolution
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.0",
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 && npm test"
44
+ "prepublishOnly": "npm run build && vitest --run"
45
45
  },
46
46
  "keywords": [
47
47
  "tailwind",