classname-variants 1.4.1 → 1.6.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.
@@ -0,0 +1,12 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm run build:*)",
5
+ "Bash(npx tsc:*)",
6
+ "Bash(rm:*)",
7
+ "Bash(node:*)"
8
+ ],
9
+ "deny": [],
10
+ "ask": []
11
+ }
12
+ }
package/README.md CHANGED
@@ -7,7 +7,8 @@ Library to create type-safe components that render their class name based on a s
7
7
  - ⚛️ Supports React, Preact and vanilla DOM
8
8
  - 🛡️ Fully type-safe and excellent auto completion support
9
9
  - ✅ Supports both optional and required variants
10
- - 🪶 Light-weight without any dependencies
10
+ - 🔗 Supports custom strategies like [tailwind-merge](https://www.npmjs.com/package/tailwind-merge)
11
+ - 🪶 [Light-weight](https://bundlephobia.com/package/classname-variants) without any dependencies
11
12
 
12
13
  ![npm bundle size](https://img.shields.io/bundlephobia/minzip/classname-variants)
13
14
 
@@ -232,6 +233,23 @@ The component can then be rendered as button or as anchor or even as custom comp
232
233
  </>
233
234
  ```
234
235
 
236
+ ### Using a custom strategy to combine class names
237
+
238
+ The built-in strategy for combining multiple class names into one string is simple and straightforward:
239
+
240
+ ```ts
241
+ (classes) => classes.filter(Boolean).join(" ");
242
+ ```
243
+
244
+ If you [want](https://github.com/dcastil/tailwind-merge/blob/main/docs/when-and-how-to-use-it.md), you can use a custom strategy like tailwind-merge instead:
245
+
246
+ ```ts
247
+ import { classNames } from "classname-variants";
248
+ import { twMerge } from "tailwind-merge";
249
+
250
+ classNames.combine = twMerge;
251
+ ```
252
+
235
253
  # Tailwind IntelliSense
236
254
 
237
255
  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.
@@ -263,6 +281,10 @@ You can then add the following line to your `settings.json`:
263
281
 
264
282
  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).
265
283
 
284
+ # For AI Assistants
285
+
286
+ For comprehensive technical documentation optimized for LLMs, see [`llms.txt`](./llms.txt).
287
+
266
288
  # License
267
289
 
268
290
  MIT
@@ -63,7 +63,7 @@ export function WithErrors() {
63
63
  }
64
64
  export function PreactApp() {
65
65
  return (h("div", { className: "flex justify-center items-center pt-8 gap-4 flex-wrap" },
66
- h(Button, { size: "medium", onClick: console.log }, "Accent"),
66
+ h(Button, { size: "medium", onClick: console.log }, "Neutral"),
67
67
  h(Button, { size: "medium", rounded: true }, "Neutral + Rounded"),
68
68
  h(Button, { size: "medium", color: "accent", outlined: true }, "Accent + Outlined"),
69
69
  h(Button, { size: "medium", color: "accent", disabled: true }, "Disabled"),
@@ -1,4 +1,10 @@
1
1
  import React from "react";
2
+ export declare const StyledWithoutVariants: <As extends React.ElementType<any> = "div">(props: {
3
+ as?: As | undefined;
4
+ } & Omit<React.ComponentProps<As>, "as"> & {} & {}) => React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
5
+ export declare const TestBaseOnly: <As extends React.ElementType<any> = "div">(props: {
6
+ as?: As | undefined;
7
+ } & Omit<React.ComponentProps<As>, "as"> & {} & {}) => React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
2
8
  export declare const ExpectErrors: <As extends React.ElementType<any> = "div">(props: {
3
9
  as?: As | undefined;
4
10
  } & Omit<React.ComponentProps<As>, "as" | "color"> & {
@@ -34,6 +34,12 @@ const Button = styled("button", {
34
34
  color: "neutral",
35
35
  },
36
36
  });
37
+ export const StyledWithoutVariants = styled("div", {
38
+ base: "bg-white",
39
+ });
40
+ export const TestBaseOnly = styled("div", {
41
+ base: "text-red-500 font-bold",
42
+ });
37
43
  export const ExpectErrors = styled("div", {
38
44
  variants: {
39
45
  color: {
@@ -63,7 +69,7 @@ export function WithErrors() {
63
69
  }
64
70
  export function ReactApp() {
65
71
  return (React.createElement("div", { className: "flex justify-center items-center pt-8 gap-4 flex-wrap" },
66
- React.createElement(Button, { size: "medium", onClick: console.log }, "Accent"),
72
+ React.createElement(Button, { size: "medium", onClick: console.log }, "Neutral"),
67
73
  React.createElement(Button, { size: "medium", rounded: true }, "Neutral + Rounded"),
68
74
  React.createElement(Button, { size: "medium", color: "accent", outlined: true }, "Accent + Outlined"),
69
75
  React.createElement(Button, { size: "medium", color: "accent", disabled: true }, "Disabled"),
package/lib/index.d.ts CHANGED
@@ -101,6 +101,9 @@ export type VariantOptions<C extends VariantsConfig<V>, V extends Variants = C["
101
101
  export type Simplify<T> = {
102
102
  [K in keyof T]: T[K];
103
103
  };
104
+ export declare const classNames: {
105
+ combine: (...classes: Array<string | undefined | null | false | 0>) => string;
106
+ };
104
107
  export declare function variants<C extends VariantsConfig<V>, V extends Variants = C["variants"]>(config: Simplify<C>): (props: VariantOptions<C>) => string;
105
108
  /**
106
109
  * No-op function to mark template literals as tailwind strings.
package/lib/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ export const classNames = {
2
+ combine: (...classes) => classes.filter(Boolean).join(" "),
3
+ };
1
4
  export function variants(config) {
2
5
  const { base, variants, compoundVariants, defaultVariants } = config;
3
6
  const isBooleanVariant = (name) => {
@@ -6,7 +9,7 @@ export function variants(config) {
6
9
  };
7
10
  return (props) => {
8
11
  var _a;
9
- const res = [base];
12
+ const classes = base ? [base] : [];
10
13
  const getSelected = (name) => {
11
14
  var _a, _b;
12
15
  return (_b = (_a = props[name]) !== null && _a !== void 0 ? _a : defaultVariants === null || defaultVariants === void 0 ? void 0 : defaultVariants[name]) !== null && _b !== void 0 ? _b : (isBooleanVariant(name) ? false : undefined);
@@ -14,15 +17,15 @@ export function variants(config) {
14
17
  for (let name in variants) {
15
18
  const selected = getSelected(name);
16
19
  if (selected !== undefined)
17
- res.push((_a = variants[name]) === null || _a === void 0 ? void 0 : _a[selected]);
20
+ classes.push((_a = variants[name]) === null || _a === void 0 ? void 0 : _a[selected]);
18
21
  }
19
22
  for (let { variants, className } of compoundVariants !== null && compoundVariants !== void 0 ? compoundVariants : []) {
20
23
  const isSelected = (name) => getSelected(name) === variants[name];
21
24
  if (Object.keys(variants).every(isSelected)) {
22
- res.push(className);
25
+ classes.push(className);
23
26
  }
24
27
  }
25
- return res.filter(Boolean).join(" ");
28
+ return classNames.combine(...classes);
26
29
  };
27
30
  }
28
31
  /**
package/lib/preact.d.ts CHANGED
@@ -20,7 +20,14 @@ type AsProps<T extends ElementType = ElementType> = {
20
20
  as?: T;
21
21
  };
22
22
  type PolymorphicComponentProps<V, T extends ElementType> = AsProps<T> & Omit<ComponentProps<T>, "as" | keyof V> & V;
23
- export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = VariantsOf<C, C["variants"]>>(type: T, config: string | Simplify<C>): <As extends ElementType<any> = T>(props: PolymorphicComponentProps<VariantOptions<C, C["variants"]>, As>) => VNode | null;
23
+ export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = VariantsOf<C, C["variants"]>>(type: T, config: string | {
24
+ base: string;
25
+ } | Simplify<C>): <As extends ElementType = T>(props: PolymorphicComponentProps<typeof config extends string ? {} : typeof config extends {
26
+ base: string;
27
+ variants?: undefined;
28
+ compoundVariants?: undefined;
29
+ defaultVariants?: undefined;
30
+ } ? {} : VariantOptions<C>, As>) => VNode | null;
24
31
  /**
25
32
  * No-op function to mark template literals as tailwind strings.
26
33
  */
package/lib/preact.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { h } from "preact";
2
2
  import { forwardRef } from "preact/compat";
3
- import { variants, } from "./index.js";
3
+ import { variants, classNames, } from "./index.js";
4
4
  export function variantProps(config) {
5
5
  const variantClassName = variants(config);
6
6
  return (props) => {
@@ -12,16 +12,16 @@ export function variantProps(config) {
12
12
  }
13
13
  }
14
14
  // Add the optionally passed class/className prop for chaining
15
- result.class = [variantClassName(props), props.class, props.className]
16
- .filter(Boolean)
17
- .join(" ");
15
+ result.class = classNames.combine(variantClassName(props), props.class, props.className);
18
16
  return result;
19
17
  };
20
18
  }
21
19
  export function styled(type, config) {
22
20
  const styledProps = typeof config === "string"
23
21
  ? variantProps({ base: config, variants: {} })
24
- : variantProps(config);
22
+ : "variants" in config
23
+ ? variantProps(config)
24
+ : variantProps({ base: config.base, variants: {} });
25
25
  const Component = forwardRef(({ as, ...props }, ref) => {
26
26
  return h(as !== null && as !== void 0 ? as : type, { ...styledProps(props), ref });
27
27
  });
package/lib/react.d.ts CHANGED
@@ -18,7 +18,14 @@ type AsProps<T extends ElementType = ElementType> = {
18
18
  as?: T;
19
19
  };
20
20
  type PolymorphicComponentProps<V, T extends ElementType> = AsProps<T> & Omit<ComponentProps<T>, "as" | keyof V> & V;
21
- export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = VariantsOf<C, C["variants"]>>(type: T, config: string | Simplify<C>): <As extends ElementType<any> = T>(props: PolymorphicComponentProps<VariantOptions<C, C["variants"]>, As>) => ReactElement | null;
21
+ export declare function styled<T extends ElementType, C extends VariantsConfig<V>, V extends Variants = VariantsOf<C, C["variants"]>>(type: T, config: string | {
22
+ base: string;
23
+ } | Simplify<C>): <As extends ElementType = T>(props: PolymorphicComponentProps<typeof config extends string ? {} : typeof config extends {
24
+ base: string;
25
+ variants?: undefined;
26
+ compoundVariants?: undefined;
27
+ defaultVariants?: undefined;
28
+ } ? {} : VariantOptions<C>, As>) => ReactElement | null;
22
29
  /**
23
30
  * No-op function to mark template literals as tailwind strings.
24
31
  */
package/lib/react.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createElement, forwardRef, } from "react";
2
- import { variants, } from "./index.js";
2
+ import { variants, classNames, } from "./index.js";
3
3
  export function variantProps(config) {
4
4
  const variantClassName = variants(config);
5
5
  return (props) => {
@@ -11,16 +11,16 @@ export function variantProps(config) {
11
11
  }
12
12
  }
13
13
  // Add the optionally passed className prop for chaining
14
- result.className = [variantClassName(props), props.className]
15
- .filter(Boolean)
16
- .join(" ");
14
+ result.className = classNames.combine(variantClassName(props), props.className);
17
15
  return result;
18
16
  };
19
17
  }
20
18
  export function styled(type, config) {
21
19
  const styledProps = typeof config === "string"
22
20
  ? variantProps({ base: config, variants: {} })
23
- : variantProps(config);
21
+ : "variants" in config
22
+ ? variantProps(config)
23
+ : variantProps({ base: config.base, variants: {} });
24
24
  const Component = forwardRef(({ as, ...props }, ref) => {
25
25
  return createElement(as !== null && as !== void 0 ? as : type, { ...styledProps(props), ref });
26
26
  });
package/llms.txt ADDED
@@ -0,0 +1,163 @@
1
+ # classname-variants
2
+
3
+ A TypeScript library for creating type-safe variant-based className generators for React, Preact, and vanilla DOM.
4
+
5
+ ## Project Overview
6
+
7
+ This library provides a variant API for generating class names based on component props. It's designed to work with CSS frameworks like Tailwind CSS but supports any class naming system. The core concept is defining variants (different states/styles) and having the library generate the appropriate class names based on provided props.
8
+
9
+ ## Architecture
10
+
11
+ ### Core Module (src/index.ts)
12
+ - `variants()` - Core function that creates a className generator based on variant configuration
13
+ - `VariantsConfig` - TypeScript interface defining the structure of variant configurations
14
+ - `classNames.combine` - Default strategy for combining class names (can be overridden with tools like tailwind-merge)
15
+ - `tw` - Tagged template literal helper for Tailwind CSS IntelliSense
16
+
17
+ ### Framework Integrations
18
+ - `src/react.ts` - React-specific bindings with `styled()` and `variantProps()` functions
19
+ - `src/preact.ts` - Preact-specific bindings (similar to React but uses Preact's JSX)
20
+
21
+ ### Key Features
22
+ - Type-safe variant definitions with full TypeScript inference
23
+ - Support for required and optional variants
24
+ - Boolean variants (true/false)
25
+ - Default variants
26
+ - Compound variants (combinations of multiple variant states)
27
+ - Polymorphic components with "as" prop
28
+ - Custom class name combination strategies
29
+
30
+ ## File Structure
31
+
32
+ ```
33
+ src/
34
+ ├── index.ts # Core variant logic and types
35
+ ├── react.ts # React bindings (styled, variantProps)
36
+ ├── preact.ts # Preact bindings (similar to React)
37
+ └── example/ # Example implementations
38
+ ├── index.ts # Vanilla example
39
+ ├── react.tsx # React example
40
+ └── preact.tsx # Preact example
41
+ ```
42
+
43
+ ## Package Structure
44
+
45
+ - ESM modules only
46
+ - Multiple entry points: main, react, preact
47
+ - Fully typed with TypeScript declarations
48
+
49
+ ## Key Concepts
50
+
51
+ ### Variant Configuration
52
+ ```ts
53
+ {
54
+ base?: string, // Always applied classes
55
+ variants: { // Variant definitions
56
+ [variantName]: {
57
+ [optionName]: "class-names"
58
+ }
59
+ },
60
+ defaultVariants?: {...}, // Default values
61
+ compoundVariants?: [...] // Conditional class combinations
62
+ }
63
+ ```
64
+
65
+ ### TypeScript Magic
66
+ The library uses advanced TypeScript features to:
67
+ - Infer variant prop types from configuration
68
+ - Make variants required unless they have defaults or are boolean
69
+ - Convert "true"/"false" keys to boolean props
70
+ - Provide full autocomplete and type checking
71
+
72
+ ## Usage Examples
73
+
74
+ ### NPM Installation & Imports
75
+ ```bash
76
+ npm install classname-variants
77
+ ```
78
+
79
+ ```ts
80
+ // Vanilla/core
81
+ import { variants, tw } from "classname-variants"
82
+
83
+ // React
84
+ import { styled, tw } from "classname-variants/react"
85
+
86
+ // Preact
87
+ import { styled, tw } from "classname-variants/preact"
88
+
89
+ // Low-level API (rarely used directly)
90
+ import { variantProps } from "classname-variants/react"
91
+ import { variantProps } from "classname-variants/preact"
92
+ ```
93
+
94
+ ### Styled Components Without Variants
95
+ Simple string-based styling for components that don't need variants:
96
+
97
+ ```tsx
98
+ const Card = styled("div", "bg-white p-4 border-2 rounded-lg");
99
+ const CustomCard = styled(CustomComponent, "bg-white p-4 border-2 rounded-lg");
100
+ ```
101
+
102
+ ### Polymorphic Components with "as" Prop
103
+ Change the underlying element/component while keeping the same styles:
104
+
105
+ ```tsx
106
+ const Button = styled("button", { variants: {...} });
107
+
108
+ // Usage
109
+ <Button>I'm a button</Button>
110
+ <Button as="a" href="/">I'm a link!</Button>
111
+ <Button as={Link} to="/">I'm a router Link</Button>
112
+ ```
113
+
114
+ ### Tailwind CSS Integration
115
+
116
+ #### Using tailwind-merge for Conflict Resolution
117
+ ```ts
118
+ import { classNames } from "classname-variants";
119
+ import { twMerge } from "tailwind-merge";
120
+
121
+ // Override default combination strategy
122
+ classNames.combine = twMerge;
123
+ ```
124
+
125
+ #### IDE Support with Tagged Templates
126
+ ```ts
127
+ import { variants, tw } from "classname-variants";
128
+
129
+ const button = variants({
130
+ base: tw`px-5 py-2 text-white`,
131
+ variants: {
132
+ color: {
133
+ neutral: tw`bg-slate-500 hover:bg-slate-400`,
134
+ accent: tw`bg-teal-500 hover:bg-teal-400`,
135
+ },
136
+ },
137
+ });
138
+ ```
139
+
140
+ Add to VS Code settings.json:
141
+ ```json
142
+ "tailwindCSS.experimental.classRegex": ["tw`(.+?)`"]
143
+ ```
144
+
145
+ ## Advantages vs Alternatives
146
+
147
+ ### vs clsx/classnames
148
+ - **Type Safety**: Full TypeScript inference for variant props
149
+ - **Variant System**: Built-in support for component variants vs manual conditional logic
150
+ - **Default Values**: Automatic handling of default variants
151
+ - **Compound Variants**: Built-in support for variant combinations
152
+
153
+ ### vs class-variance-authority (cva)
154
+ - **Zero Dependencies**: No external dependencies vs cva's clsx dependency
155
+ - **Framework Integration**: Built-in React/Preact components with `styled()` API
156
+ - **Polymorphic Support**: Native "as" prop support for component flexibility
157
+ - **TypeScript Focus**: Designed TypeScript-first with advanced type inference
158
+
159
+ ## Dependencies
160
+
161
+ - Zero runtime dependencies
162
+ - Development dependencies: React, Preact, TypeScript
163
+ - Designed to work with any CSS framework (Tailwind, CSS modules, etc.)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "classname-variants",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "description": "Variant API for plain class names",
5
5
  "author": "Felix Gnass <fgnass@gmail.com>",
6
6
  "license": "MIT",