slot-variants 1.3.0 → 1.5.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
@@ -384,6 +384,21 @@ button({ intent: 'primary', size: 'lg' }); // OK
384
384
  button({ size: 'lg' }); // Throws: Missing required variant: "intent"
385
385
  ```
386
386
 
387
+ Pass `true` to make every variant required, or `false` to require none:
388
+
389
+ ```typescript
390
+ const button = sv('btn', {
391
+ variants: {
392
+ size: { sm: 'text-sm', lg: 'text-lg' },
393
+ intent: { primary: 'bg-blue-500', danger: 'bg-red-500' }
394
+ },
395
+ requiredVariants: true
396
+ });
397
+
398
+ button({ size: 'sm', intent: 'primary' }); // OK
399
+ button({ size: 'sm' }); // Throws: Missing required variant: "intent"
400
+ ```
401
+
387
402
  ### Presets
388
403
 
389
404
  Presets are predefined named combinations of variant values. Use them to create reusable variant shortcuts:
@@ -597,6 +612,58 @@ compoundSlots: [
597
612
  ]
598
613
  ```
599
614
 
615
+ ### Multi Slots
616
+
617
+ By default each slot in the result object is a plain class string. The
618
+ `multiSlots` option turns the listed slots into reconfigurable functions
619
+ instead. A slot function accepts variant prop overrides and a
620
+ `class`/`className` override, and returns that slot's class string.
621
+
622
+ This is designed for cases where a single slot is rendered multiple times
623
+ with different props — for example a list of items where each item needs
624
+ its own variant values — so the same slot can be re-evaluated per use
625
+ without recreating the whole variant function:
626
+
627
+ ```typescript
628
+ const card = sv('border', {
629
+ slots: {
630
+ header: 'font-bold',
631
+ body: 'py-4'
632
+ },
633
+ variants: {
634
+ size: {
635
+ sm: { base: 'p-2', header: 'text-sm' },
636
+ lg: { base: 'p-6', header: 'text-lg' }
637
+ }
638
+ },
639
+ multiSlots: ['header']
640
+ });
641
+
642
+ const result = card({ size: 'sm' });
643
+ // result.base -> 'border p-2' (plain string)
644
+ // result.body -> 'py-4' (plain string)
645
+ // result.header -> function
646
+
647
+ result.header(); // 'font-bold text-sm'
648
+ result.header({ size: 'lg' }); // 'font-bold text-lg'
649
+ result.header({ class: 'mt-2' }); // 'font-bold text-sm mt-2'
650
+ ```
651
+
652
+ Slots not listed in `multiSlots` stay plain strings. Pass `true` to make
653
+ every slot a function, or `false` (the default) to keep them all strings:
654
+
655
+ ```typescript
656
+ const card = sv('border', {
657
+ slots: { header: 'font-bold', body: 'py-4' },
658
+ multiSlots: true
659
+ });
660
+
661
+ const { base, header, body } = card();
662
+ base(); // 'border'
663
+ header(); // 'font-bold'
664
+ body(); // 'py-4'
665
+ ```
666
+
600
667
  ### Class Override at Runtime
601
668
 
602
669
  Append additional classes at call time using `class` or `className`:
@@ -668,7 +735,17 @@ The `postProcess` function is applied to each slot's final class string independ
668
735
 
669
736
  ### Caching
670
737
 
671
- Results are cached automatically for performance. The default cache size is **256** entries. Calls with `class` or `className` props bypass the cache.
738
+ Results are cached automatically for performance. The default cache size is **256** entries.
739
+
740
+ Each cache entry corresponds to one distinct combination of resolved variant values. The largest number of combinations a config can produce is the product, over every variant, of its value count plus one — the `+ 1` counts the variant being left unset:
741
+
742
+ ```
743
+ maxEntries = (values₁ + 1) × (values₂ + 1) × ... × (valuesₙ + 1)
744
+ ```
745
+
746
+ For example, four variants with three values each yield `(3 + 1) ** 4 = 256` combinations — exactly the default. A config whose `maxEntries` is at or below its `cacheSize` never evicts, so raising `cacheSize` past that point has no effect.
747
+
748
+ The `+ 1` is dropped for any variant that cannot actually be left unset — one listed in `requiredVariants` (or all of them when `requiredVariants` is `true`), or one with a static `defaultVariants` value that always fills it in. A function-based default keeps the `+ 1`, since it may return `undefined`. The `getMaxEntries()` introspection method computes this exact count for a given config — see [Introspection](#introspection).
672
749
 
673
750
  ```typescript
674
751
  const button = sv('btn', {
@@ -719,10 +796,12 @@ button.slotKeys; // ['base', 'icon']
719
796
  button.slots; // { icon: 'w-4 h-4' }
720
797
  button.defaultVariants; // { size: 'sm' }
721
798
  button.requiredVariants; // ['intent']
799
+ button.multiSlots; // [] (slot names exposed as functions)
722
800
  button.presetKeys; // ['cta']
723
801
  button.presets; // { cta: { size: 'lg', intent: 'primary' } }
724
802
  button.getVariantValues('size'); // ['sm', 'lg']
725
803
  button.getVariantValues('intent'); // ['primary', 'danger']
804
+ button.getMaxEntries(); // 4 — distinct variant combinations
726
805
  button.getCacheSize(); // current number of cached entries
727
806
  button.clearCache(); // clear all cached entries
728
807
  ```
@@ -884,7 +963,8 @@ Class values inside the config (`base`, `variants`, `slots`, and `compound*` `cl
884
963
  | `compoundVariants` | `Array` | Additional classes applied when multiple variant conditions match |
885
964
  | `compoundSlots` | `Array` | Classes applied to multiple slots based on variant conditions |
886
965
  | `defaultVariants` | `Object` | Default values for variants (static values or functions) |
887
- | `requiredVariants` | `string[]` | Variant names that must be provided at call time |
966
+ | `requiredVariants` | `string[] \| boolean` | Variant names that must be provided at call time; `true` makes every variant required, `false` none |
967
+ | `multiSlots` | `string[] \| boolean` | Slot names exposed as reconfigurable functions instead of strings; `true` makes every slot a function, `false` none |
888
968
  | `presets` | `Record<string, Partial<VariantProps>>` | Named combinations of variant values selectable via `preset` prop |
889
969
  | `postProcess` | `(className: string) => string` | Custom transformation applied to final class strings |
890
970
  | `cacheSize` | `number` | Maximum number of cached results (default: `256`) |
@@ -896,20 +976,30 @@ Class values inside the config (`base`, `variants`, `slots`, and `compound*` `cl
896
976
 
897
977
  ### Rules
898
978
 
899
- - **`slot-variants/no-duplicate-classes`** — flags class name tokens that will appear more than once in the output of an `sv()` or `cn()` call. For `sv()`, detects duplicates within `base`, across different variant keys, inside compound variants and compound slots, between `base` and a variant value, and within a single literal. For `cn()` (and the cn-style calling convention of `sv()` without a config), flags any token that appears in more than one always-present source — across args, inside arrays, template literals, or within a single literal.
979
+ - **`slot-variants/no-conflicting-classes`** — flags class tokens that collide within the output of an `sv()` or `cn()` call: both exact-duplicate tokens that will appear more than once and distinct tokens that target the same Tailwind-style utility namespace (e.g. `w-100` and `w-200`). For `sv()`, detects collisions within `base`, across different variant keys, inside compound variants and compound slots, between `base` and a variant value, and within a single literal. For `cn()` (and the cn-style calling convention of `sv()` without a config), flags collisions across args, inside arrays, template literals, or within a single literal. Tokens with different variant prefixes (`w-100` vs `hover:w-200`) don't conflict, a leading or trailing `!` important marker (`!w-100`, `w-100!`) is ignored when computing the namespace, and tokens that only co-occur across mutually-exclusive variant values are skipped.
900
980
 
901
- - **`slot-variants/no-dynamic-classes`** — flags class-bearing positions in `sv()` and `cn()` calls that aren't statically inferrable. Only string literals, template literals without expressions, flat arrays of those in config, and explicit `undefined` config class values are accepted, and config objects must use static keys (no spreads, no computed keys). Identifiers, member access, calls, spreads, non-string literals, templates with expressions, nested config arrays, and runtime conditional records are reported. Non-class-bearing config keys (`defaultVariants`, `presets`, `requiredVariants`, `cacheSize`, `postProcess`, `introspection`) are not validated, and runtime variant matchers inside compound entries are left alone — only the `class`/`className` value (and the `slots` array of `compoundSlots`) is checked.
981
+ - **`slot-variants/no-dynamic-classes`** — flags class-bearing positions in `sv()` and `cn()` calls that aren't statically inferrable. Only string literals, template literals without expressions, flat arrays of those in config, and explicit `undefined` config class values are accepted, and config objects must use static keys (no spreads, no computed keys). Identifiers, member access, calls, spreads, non-string literals, templates with expressions, nested config arrays, and runtime conditional records are reported. Non-class-bearing config keys (`defaultVariants`, `presets`, `requiredVariants`, `multiSlots`, `cacheSize`, `postProcess`, `introspection`) are not validated, and runtime variant matchers inside compound entries are left alone — only the `class`/`className` value (and the `slots` array of `compoundSlots`) is checked.
902
982
 
903
- - **`slot-variants/no-empty-classes`** — flags empty class values — empty strings, empty arrays, and empty objects — at any class-bearing position reachable from an `sv()` or `cn()` call, plus zero-argument `sv()` / `cn()` calls themselves (which always produce an empty class string). Reports empties in positional arguments (and inside arrays nested in those), in `base`, in variants including slot-keyed variant branches, in the `class`/`className` of `compoundVariants` and `compoundSlots` entries, and in the top-level `slots`, `variants`, `compoundVariants`, and `compoundSlots` containers themselves. Inside an `sv()` config, an empty string is still allowed as a direct `slots[key]` value — declaring a slot with no default classes is a valid use case.
983
+ - **`slot-variants/no-empty-classes`** — flags empty class values — empty strings, empty arrays, and empty objects — at any class-bearing position reachable from an `sv()` or `cn()` call, plus zero-argument `sv()` / `cn()` calls themselves (which always produce an empty class string). Reports empties in positional arguments (and inside arrays nested in those), in `base`, in variants including slot-keyed variant branches, in the `class`/`className` of `compoundVariants` and `compoundSlots` entries, and in the top-level `slots`, `variants`, `compoundVariants`, and `compoundSlots` containers themselves. Inside an `sv()` config, an empty string is still allowed as a direct `slots[key]` value — declaring a slot with no default classes is a valid use case. **Partially auto-fixable**: `eslint --fix` removes an empty positional argument, an empty class-array element, or an empty top-level config property (`base`, `slots`, `variants`, `compoundVariants`, `compoundSlots`), along with its comma, when other items remain in that list or config; empties at other positions are reported without a fix.
904
984
 
905
- - **`slot-variants/no-redundant-spaces`** — flags class strings whose whitespace isn't canonical. Inside a class string, whitespace is canonical only as a single ASCII space between two non-whitespace tokens, so leading or trailing whitespace, repeated spaces, and non-space whitespace (tabs, newlines, etc.) are reported. The rule walks every string and expressionless template literal reachable from a call's arguments — including values nested inside arrays and objects — and bails silently on dynamic expressions.
985
+ - **`slot-variants/no-redundant-spaces`** — flags class strings whose whitespace isn't canonical. Inside a class string, whitespace is canonical only as a single ASCII space between two non-whitespace tokens, so leading or trailing whitespace, repeated spaces, and non-space whitespace (tabs, newlines, etc.) are reported. The rule walks every string and expressionless template literal reachable from a call's arguments — including values nested inside arrays and objects — and bails silently on dynamic expressions. **Auto-fixable**: `eslint --fix` rewrites each offending literal in place, preserving its original quote style.
906
986
 
907
- - **`slot-variants/no-shared-tokens`** — flags class tokens that appear in every value of an exhaustively-covered variant, where “exhaustive” means the variant has a statically defined default value or is listed in `requiredVariants`. Those tokens are constant in the rendered output, so they belong in `base` or the corresponding `slots[slot]` entry rather than being repeated in every variant value. The rule only analyzes `sv()` calls with a config, compares tokens per-slot, skips non-exhaustive variants, single-value variants, boolean shorthand, undefined or dynamic defaults, and dynamic or partially-analyzable variant value records, and reports every repeated occurrence that should be lifted out.
987
+ - **`slot-variants/no-shared-tokens`** — flags class tokens that appear in every value of an exhaustively-covered variant, where “exhaustive” means the variant has a statically defined default value, is listed in `requiredVariants`, or `requiredVariants` is `true` (every variant required). Those tokens are constant in the rendered output, so they belong in `base` or the corresponding `slots[slot]` entry rather than being repeated in every variant value. The rule only analyzes `sv()` calls with a config, compares tokens per-slot, skips non-exhaustive variants, single-value variants, boolean shorthand, undefined or dynamic defaults, and dynamic or partially-analyzable variant value records, and reports every repeated occurrence that should be lifted out.
908
988
 
909
- Only calls where `sv` or `cn` is a named import from `'slot-variants'` are analyzed. `no-duplicate-classes` skips dynamic inputs silently to avoid false positives; `no-dynamic-classes` is the opposite — it flags exactly those positions so the static analyzer can fully reason about every call. `no-shared-tokens` sits between them: it needs a fully statically analyzable, exhaustive variant before it can prove a token is constant across every value. `no-empty-classes` and `no-redundant-spaces` are independent and complement the structural rules: they cover empty and badly-shaped literals reachable from a call's arguments, regardless of whether the surrounding call is fully static.
989
+ Only calls where `sv` or `cn` is a named import from `'slot-variants'` are analyzed. `no-conflicting-classes` skips dynamic inputs silently to avoid false positives; `no-dynamic-classes` is the opposite — it flags exactly those positions so the static analyzer can fully reason about every call. `no-shared-tokens` sits between them: it needs a fully statically analyzable, exhaustive variant before it can prove a token is constant across every value. `no-empty-classes` and `no-redundant-spaces` are independent and complement the structural rules: they cover empty and badly-shaped literals reachable from a call's arguments, regardless of whether the surrounding call is fully static.
910
990
 
911
991
  ### ESLint (flat config)
912
992
 
993
+ Use the `recommended` preset to enable every rule at `error` in one line:
994
+
995
+ ```js
996
+ import svPlugin from 'slot-variants/eslint-plugin';
997
+
998
+ export default [svPlugin.configs.recommended];
999
+ ```
1000
+
1001
+ Or wire each rule by hand if you want per-rule control:
1002
+
913
1003
  ```js
914
1004
  import svPlugin from 'slot-variants/eslint-plugin';
915
1005
 
@@ -917,7 +1007,7 @@ export default [
917
1007
  {
918
1008
  plugins: { 'slot-variants': svPlugin },
919
1009
  rules: {
920
- 'slot-variants/no-duplicate-classes': 'error',
1010
+ 'slot-variants/no-conflicting-classes': 'error',
921
1011
  'slot-variants/no-dynamic-classes': 'error',
922
1012
  'slot-variants/no-empty-classes': 'error',
923
1013
  'slot-variants/no-redundant-spaces': 'error',
@@ -933,7 +1023,7 @@ export default [
933
1023
  {
934
1024
  "jsPlugins": ["slot-variants/eslint-plugin"],
935
1025
  "rules": {
936
- "slot-variants/no-duplicate-classes": "error",
1026
+ "slot-variants/no-conflicting-classes": "error",
937
1027
  "slot-variants/no-dynamic-classes": "error",
938
1028
  "slot-variants/no-empty-classes": "error",
939
1029
  "slot-variants/no-redundant-spaces": "error",
@@ -960,7 +1050,7 @@ const button = sv({
960
1050
  cn('flex items-center', 'flex'); // 'flex' duplicated across args
961
1051
  ```
962
1052
 
963
- `no-duplicate-classes` reports `flex` on the `base` literal and on both variant values; for the `cn()` call, both occurrences of `'flex'` are flagged. Move the shared class into `base` — or use compound variants — so each class has a single source.
1053
+ `no-conflicting-classes` reports `flex` on the `base` literal and on both variant values; for the `cn()` call, both occurrences of `'flex'` are flagged. Move the shared class into `base` — or use compound variants — so each class has a single source.
964
1054
 
965
1055
  ```typescript
966
1056
  import { sv, cn } from 'slot-variants';
@@ -1000,7 +1090,7 @@ sv({ slots: { body: 'p-4 ' } }); // trailing space
1000
1090
  cn(`flex\titems-center`); // tab between tokens
1001
1091
  ```
1002
1092
 
1003
- `no-redundant-spaces` reports each literal whose whitespace deviates from the canonical "tokens separated by exactly one space" form. Trim and collapse the strings — or split them into array entries — so the stored class output is byte-stable and easy to scan in diffs.
1093
+ `no-redundant-spaces` reports each literal whose whitespace deviates from the canonical "tokens separated by exactly one space" form. Trim and collapse the strings — or split them into array entries — so the stored class output is byte-stable and easy to scan in diffs. Run `eslint --fix` to apply the canonical form automatically; the fixer rewrites the literal in place using the original quote style.
1004
1094
 
1005
1095
  ```typescript
1006
1096
  import { sv } from 'slot-variants';
@@ -1070,7 +1160,7 @@ module.exports = {
1070
1160
  size: { sm: 'text-sm', lg: 'text-lg' },
1071
1161
  intent: { primary: 'bg-blue-500', danger: 'bg-red-500' }
1072
1162
  },
1073
- defaultVariants: { size: 'md' },
1163
+ defaultVariants: { size: 'sm' },
1074
1164
  compoundVariants: [
1075
1165
  { size: 'lg', intent: 'primary', class: 'uppercase' }
1076
1166
  ]
@@ -1093,7 +1183,7 @@ Everything else works identically — the config shape, `class`/`className` over
1093
1183
  variants: {
1094
1184
  size: { sm: 'text-sm', lg: 'text-lg' }
1095
1185
  },
1096
- defaultVariants: { size: 'md' }
1186
+ defaultVariants: { size: 'sm' }
1097
1187
  });
1098
1188
  ```
1099
1189
 
@@ -1101,11 +1191,11 @@ Key differences to be aware of:
1101
1191
 
1102
1192
  | Feature | tailwind-variants | slot-variants |
1103
1193
  | --- | --- | --- |
1104
- | Slot return type | Functions: `slot({ class: '...' })` | Strings: `slot` is already a `string` |
1194
+ | Slot return type | Always functions: `slot({ class: '...' })` | Strings by default; functions for slots listed in `multiSlots` |
1105
1195
  | `extend` (composition) | Supported | Not supported |
1106
1196
  | Built-in `twMerge` | Enabled by default | Use `postProcess: twMerge` |
1107
1197
 
1108
- **Slot return type** is the most significant difference. In `tv()`, each slot returns a function that can accept additional props. In `sv()`, slots resolve to strings directly — use the `class` prop with a slot object for per-slot overrides:
1198
+ **Slot return type** is the most significant difference. In `tv()`, each slot returns a function that can accept additional props. In `sv()`, slots resolve to strings directly — use the `class` prop with a slot object for per-slot overrides, or list a slot in [`multiSlots`](#multi-slots) to expose it as a `tv`-style reconfigurable function:
1109
1199
 
1110
1200
  ```typescript
1111
1201
  // tailwind-variants
package/SKILL.md ADDED
@@ -0,0 +1,200 @@
1
+ # slot-variants — AI Agent Guide
2
+
3
+ ## Overview
4
+
5
+ `slot-variants` is a lightweight, zero-dependency, type-safe library for managing CSS class name variants with slots support.
6
+
7
+ ```typescript
8
+ import { sv, cn, type VariantProps } from 'slot-variants';
9
+ ```
10
+
11
+ `sv()` is a drop-in replacement for CVA (`cva` → `sv`) and covers the core feature set of tailwind-variants (`tv`) with a simpler API. Slots return strings directly by default, or reconfigurable functions (like in `tv`) when listed in `multiSlots`. Features not in CVA/TV: `requiredVariants`, `presets`, `cacheSize`, `postProcess`, function-based `defaultVariants`, and variadic base args.
12
+
13
+ ## Calling Conventions
14
+
15
+ `sv()` supports three calling conventions:
16
+
17
+ ```typescript
18
+ // 1. Config-only
19
+ sv({ base: 'btn', variants: { size: { sm: 'text-sm', lg: 'text-lg' } } });
20
+
21
+ // 2. Base + config
22
+ sv('btn', { variants: { size: { sm: 'text-sm', lg: 'text-lg' } } });
23
+
24
+ // 3. Class name merging (like cn())
25
+ sv('flex', 'items-center', 'gap-2'); // 'flex items-center gap-2'
26
+ ```
27
+
28
+ The `base` config field merges with base arguments and `slots.base`: `cn(baseArgs..., config.base, slots.base)`.
29
+
30
+ ## Best Practices
31
+
32
+ ### 1. Keep CSS Classes Mutually Exclusive
33
+
34
+ Define classes in base and variants so they don't overlap; use compound variants for intersecting classes. Don't repeat a token across every variant value — lift it into `base`.
35
+
36
+ ### 2. Use Boolean Shorthand for Simple States
37
+
38
+ For boolean variants (on/off), use shorthand instead of a verbose `{ true, false }` record:
39
+
40
+ ```typescript
41
+ const button = sv('btn', {
42
+ variants: { disabled: 'opacity-50 cursor-not-allowed' }
43
+ });
44
+
45
+ button({ disabled: true });
46
+ ```
47
+
48
+ ### 3. Use Required Variants for Mandatory Props
49
+
50
+ List a variant in `requiredVariants: ['intent']` to make it mandatory — `button()` throws when it is missing.
51
+
52
+ ### 4. Use Slots for Multi-Element Components
53
+
54
+ For components with multiple elements (card, dialog, etc.), use slots:
55
+
56
+ ```typescript
57
+ const card = sv('border rounded-lg', {
58
+ slots: { header: 'font-semibold px-4', body: 'px-4 py-4', footer: 'px-4 border-t' }
59
+ });
60
+
61
+ const { base, header, body, footer } = card();
62
+ ```
63
+
64
+ Variant values can target slots: `size: { sm: { base: 'p-2', header: 'text-sm' } }`. Use `compoundSlots` to apply classes to multiple slots at once.
65
+
66
+ ### 5. Use VariantProps Type for Component Props
67
+
68
+ ```typescript
69
+ type ButtonProps = VariantProps<typeof button>;
70
+ // Exclude internal variants from props:
71
+ type InternalButtonProps = VariantProps<typeof button, 'internalState'>;
72
+ ```
73
+
74
+ ### 6. Use Compound Variants for Conditional Combinations
75
+
76
+ Apply classes when multiple conditions are met:
77
+
78
+ ```typescript
79
+ compoundVariants: [
80
+ { size: 'lg', intent: 'primary', class: 'font-bold uppercase' }
81
+ ]
82
+ ```
83
+
84
+ ### 7. Use Function-Based Default Variants for Dynamic Defaults
85
+
86
+ A default can be a function of the other variant values:
87
+
88
+ ```typescript
89
+ defaultVariants: {
90
+ size: 'sm',
91
+ intent: (props) => {
92
+ if (props.size === 'lg') return 'danger';
93
+ return 'primary';
94
+ }
95
+ }
96
+ ```
97
+
98
+ Prefer static defaults — function-based defaults run on every invocation.
99
+
100
+ ### 8. Use Post-Processing with tailwind-merge
101
+
102
+ For Tailwind projects, pass `postProcess` to resolve class conflicts:
103
+
104
+ ```typescript
105
+ import { twMerge } from 'tailwind-merge';
106
+
107
+ sv('px-4 py-2', { variants: { size: { lg: 'px-6 py-3' } }, postProcess: twMerge });
108
+ ```
109
+
110
+ ### 9. Leverage Caching for Performance
111
+
112
+ The library caches results automatically (default 256 entries). Each cache entry is one distinct combination of resolved variant values:
113
+
114
+ ```
115
+ maxEntries = (values₁ + 1) × (values₂ + 1) × ... × (valuesₙ + 1)
116
+ ```
117
+
118
+ The `+ 1` counts the variant being left unset. Raise `cacheSize` only when `maxEntries` exceeds 256 — below that the cache never evicts. Cache inspection methods (`getCacheSize`, `clearCache`) are only exposed when `introspection: true`.
119
+
120
+ ### 10. Use Presets for Reusable Variant Combinations
121
+
122
+ ```typescript
123
+ const button = sv('btn', {
124
+ variants: { size: { sm: 'text-sm', lg: 'text-lg' }, intent: { primary: 'bg-blue-500', danger: 'bg-red-500' } },
125
+ presets: { cta: { size: 'lg', intent: 'primary' } }
126
+ });
127
+
128
+ button({ preset: 'cta' }); // applies size: 'lg', intent: 'primary'
129
+ ```
130
+
131
+ ### 11. Use Introspection for Single Source of Truth
132
+
133
+ Set `introspection: true` to expose configuration and cache members on the returned function (off by default): `variantKeys`, `variants`, `slotKeys`, `slots`, `defaultVariants`, `requiredVariants`, `presetKeys`, `presets`, `getVariantValues(key)`, `getCacheSize()`, and `clearCache()`.
134
+
135
+ Without `introspection: true`, accessing these is a type error. Use it to centralize variant/slot definitions and reuse them across the codebase.
136
+
137
+ ## Common Patterns
138
+
139
+ ### React Component Pattern
140
+
141
+ ```typescript
142
+ import { sv, type VariantProps } from 'slot-variants';
143
+
144
+ const button = sv('btn font-medium rounded-lg', {
145
+ variants: {
146
+ size: { sm: 'text-sm px-3', md: 'text-base px-4', lg: 'text-lg px-6' },
147
+ intent: { primary: 'bg-blue-500 text-white', danger: 'bg-red-500 text-white' }
148
+ },
149
+ defaultVariants: { size: 'md', intent: 'primary' }
150
+ });
151
+
152
+ export type ButtonProps = VariantProps<typeof button>;
153
+
154
+ export const Button = ({ class: className, ...props }: ButtonProps & { class?: string }) => {
155
+ return <button className={button({ ...props, class: className })} />;
156
+ };
157
+ ```
158
+
159
+ ### Multi-Element Component Pattern
160
+
161
+ ```typescript
162
+ const card = sv('border rounded-lg', {
163
+ slots: { header: 'font-semibold px-4 border-b', body: 'px-4 py-4', footer: 'px-4 border-t' },
164
+ variants: {
165
+ size: { sm: { base: 'p-2', header: 'text-sm' }, md: { base: 'p-4', header: 'text-base' } },
166
+ elevated: { true: { base: 'shadow-lg' }, false: { base: 'shadow-sm' } }
167
+ },
168
+ compoundSlots: [{ slots: ['header', 'footer'], class: 'text-gray-600' }]
169
+ });
170
+
171
+ const { base, header, body, footer } = card({ size: 'md' });
172
+ ```
173
+
174
+ ## Configuration Reference
175
+
176
+ Class values inside the config (`base`, `variants` values, `slots` values, and `compound*` `class`/`className`) accept only `string`, `string[]`, or `undefined`. Dynamic class values (objects, booleans, nested arrays) belong on the `class`/`className` runtime prop, not in the config.
177
+
178
+ | Option | Type | Description |
179
+ | ------------------ | ------------------------------------ | --------------------------------- |
180
+ | `base` | `string \| string[]` | Additional base classes |
181
+ | `variants` | `Record<string, VariantConfig>` | Variant definitions |
182
+ | `slots` | `Record<string, string \| string[]>` | Named slot definitions |
183
+ | `compoundVariants` | `CompoundVariant[]` | Conditional class combinations |
184
+ | `compoundSlots` | `CompoundSlot[]` | Multi-slot conditional classes |
185
+ | `defaultVariants` | `Record<string, Value>` | Static or function-based defaults |
186
+ | `requiredVariants` | `string[] \| boolean` | Mandatory variant names |
187
+ | `multiSlots` | `string[] \| boolean` | Slots exposed as reconfigurable functions |
188
+ | `presets` | `Record<string, Partial<Props>>` | Named preset combinations |
189
+ | `postProcess` | `(className: string) => string` | Class transformation |
190
+ | `cacheSize` | `number` | Cache size (default: 256) |
191
+ | `introspection` | `boolean` | Expose introspection and cache methods (default: false) |
192
+
193
+ ## Exported Types
194
+
195
+ - `ClassValue` — Valid input for `cn()` (string, array, object, boolean, null, undefined)
196
+ - `VariantProps<T, E>` — Extract variant props from an `sv()` return, optionally excluding keys
197
+ - `VariantValue<T, K>` — Extract the value union for a single variant key, without `undefined`
198
+ - `SlotClassProps<T>` — Extract the per-slot class injection shape from an `sv()` return type
199
+
200
+ Functions are imported as named values; types via `import type { ... } from 'slot-variants'`.