@svelte-atoms/core 1.0.0-alpha.27 → 1.0.0-alpha.28

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 (163) hide show
  1. package/README.md +856 -645
  2. package/dist/components/accordion/accordion-root.svelte +61 -61
  3. package/dist/components/accordion/item/accordion-item-body.svelte +42 -42
  4. package/dist/components/accordion/item/accordion-item-header.svelte +50 -50
  5. package/dist/components/accordion/item/accordion-item-indicator.svelte +50 -50
  6. package/dist/components/accordion/item/accordion-item-root.svelte +65 -65
  7. package/dist/components/alert/alert-actions.svelte +2 -1
  8. package/dist/components/alert/alert-close-button.svelte +18 -20
  9. package/dist/components/alert/alert-content.svelte +2 -1
  10. package/dist/components/alert/alert-description.svelte +2 -1
  11. package/dist/components/alert/alert-icon.svelte +2 -1
  12. package/dist/components/alert/alert-root.svelte +3 -2
  13. package/dist/components/alert/alert-title.svelte +2 -1
  14. package/dist/components/alert/alert.stories.svelte +401 -40
  15. package/dist/components/alert/alert.stories.svelte.d.ts +2 -5
  16. package/dist/components/atom/html-atom.svelte +205 -201
  17. package/dist/components/atom/snippet-renderer.svelte +5 -0
  18. package/dist/components/atom/snippet-renderer.svelte.d.ts +5 -0
  19. package/dist/components/avatar/avatar.stories.svelte.d.ts +1 -1
  20. package/dist/components/badge/badge.stories.svelte.d.ts +1 -1
  21. package/dist/components/breadcrumb/breadcrumb.stories.svelte.d.ts +1 -1
  22. package/dist/components/button/button.stories.svelte +60 -57
  23. package/dist/components/calendar/atoms.d.ts +5 -0
  24. package/dist/components/calendar/atoms.js +5 -0
  25. package/dist/components/calendar/bond.svelte.d.ts +72 -0
  26. package/dist/components/calendar/bond.svelte.js +132 -0
  27. package/dist/components/calendar/calendar-body.svelte +107 -0
  28. package/dist/components/calendar/calendar-body.svelte.d.ts +8 -0
  29. package/dist/components/calendar/calendar-day.svelte +97 -0
  30. package/dist/components/calendar/calendar-day.svelte.d.ts +4 -0
  31. package/dist/components/calendar/calendar-header.svelte +33 -0
  32. package/dist/components/calendar/calendar-header.svelte.d.ts +7 -0
  33. package/dist/components/calendar/calendar-root.svelte +208 -0
  34. package/dist/components/calendar/calendar-root.svelte.d.ts +8 -0
  35. package/dist/components/calendar/calendar-week-day.svelte +34 -0
  36. package/dist/components/calendar/calendar-week-day.svelte.d.ts +9 -0
  37. package/dist/components/calendar/calendar.css +26 -0
  38. package/dist/components/calendar/calendar.stories.svelte +36 -0
  39. package/dist/components/calendar/calendar.stories.svelte.d.ts +6 -0
  40. package/dist/components/calendar/index.d.ts +4 -0
  41. package/dist/components/calendar/index.js +4 -0
  42. package/dist/components/calendar/runes.svelte.d.ts +3 -0
  43. package/dist/components/calendar/runes.svelte.js +25 -0
  44. package/dist/components/calendar/types.d.ts +62 -0
  45. package/dist/components/calendar/types.js +1 -0
  46. package/dist/components/card/card-body.svelte +39 -39
  47. package/dist/components/card/card-description.svelte +41 -41
  48. package/dist/components/card/card-footer.svelte +41 -41
  49. package/dist/components/card/card-header.svelte +41 -41
  50. package/dist/components/card/card-media.svelte +41 -41
  51. package/dist/components/card/card-root.svelte +91 -91
  52. package/dist/components/card/card-subtitle.svelte +41 -41
  53. package/dist/components/card/card-title.svelte +45 -45
  54. package/dist/components/collapsible/collapsible-body.svelte +39 -39
  55. package/dist/components/collapsible/collapsible-header.svelte +39 -39
  56. package/dist/components/collapsible/collapsible-indicator.svelte +50 -50
  57. package/dist/components/collapsible/collapsible-root.svelte +66 -66
  58. package/dist/components/combobox/combobox-root.svelte +65 -65
  59. package/dist/components/container/container.stories.svelte.d.ts +1 -1
  60. package/dist/components/contextmenu/contextmenu-trigger.svelte.d.ts +1 -1
  61. package/dist/components/datagrid/bond.svelte.d.ts +2 -2
  62. package/dist/components/datagrid/datagrid-body.svelte +37 -37
  63. package/dist/components/datagrid/datagrid-checkbox.svelte +101 -101
  64. package/dist/components/datagrid/datagrid-footer.svelte +34 -34
  65. package/dist/components/datagrid/datagrid-header.svelte +49 -49
  66. package/dist/components/datagrid/datagrid-root.svelte +59 -59
  67. package/dist/components/datagrid/td/datagrid-td.svelte +66 -66
  68. package/dist/components/datagrid/th/datagrid-th.svelte +106 -106
  69. package/dist/components/datagrid/tr/datagrid-tr.svelte +88 -88
  70. package/dist/components/date-picker/atoms.d.ts +7 -0
  71. package/dist/components/date-picker/atoms.js +7 -0
  72. package/dist/components/date-picker/bond.svelte.d.ts +67 -0
  73. package/dist/components/date-picker/bond.svelte.js +174 -0
  74. package/dist/components/date-picker/date-picker-calendar.svelte +42 -0
  75. package/dist/components/date-picker/date-picker-calendar.svelte.d.ts +7 -0
  76. package/dist/components/date-picker/date-picker-header.svelte +105 -0
  77. package/dist/components/date-picker/date-picker-header.svelte.d.ts +7 -0
  78. package/dist/components/date-picker/date-picker-months.svelte +150 -0
  79. package/dist/components/date-picker/date-picker-months.svelte.d.ts +7 -0
  80. package/dist/components/date-picker/date-picker-root.svelte +94 -0
  81. package/dist/components/date-picker/date-picker-root.svelte.d.ts +17 -0
  82. package/dist/components/date-picker/date-picker-years.svelte +214 -0
  83. package/dist/components/date-picker/date-picker-years.svelte.d.ts +7 -0
  84. package/dist/components/date-picker/date-picker.stories.svelte +51 -0
  85. package/dist/components/date-picker/date-picker.stories.svelte.d.ts +3 -0
  86. package/dist/components/date-picker/index.d.ts +3 -0
  87. package/dist/components/date-picker/index.js +3 -0
  88. package/dist/components/date-picker/types.d.ts +1 -0
  89. package/dist/components/date-picker/types.js +1 -0
  90. package/dist/components/dialog/dialog-body.svelte +39 -39
  91. package/dist/components/dialog/dialog-close-button.svelte +58 -58
  92. package/dist/components/dialog/dialog-content.svelte +62 -62
  93. package/dist/components/dialog/dialog-description.svelte +40 -40
  94. package/dist/components/dialog/dialog-footer.svelte +39 -39
  95. package/dist/components/dialog/dialog-header.svelte +39 -39
  96. package/dist/components/dialog/dialog-root.svelte +110 -110
  97. package/dist/components/dialog/dialog-title.svelte +41 -41
  98. package/dist/components/drawer/drawer-backdrop.svelte +38 -38
  99. package/dist/components/drawer/drawer-body.svelte +42 -42
  100. package/dist/components/drawer/drawer-content.svelte +42 -42
  101. package/dist/components/drawer/drawer-description.svelte +44 -44
  102. package/dist/components/drawer/drawer-footer.svelte +41 -41
  103. package/dist/components/drawer/drawer-header.svelte +43 -43
  104. package/dist/components/drawer/drawer-root.svelte +93 -93
  105. package/dist/components/drawer/drawer-title.svelte +44 -44
  106. package/dist/components/dropdown/dropdown-query.svelte +54 -54
  107. package/dist/components/dropdown/dropdown-root.svelte +59 -59
  108. package/dist/components/dropdown/dropdown-trigger.svelte +41 -41
  109. package/dist/components/dropdown/dropdown-value.svelte +60 -60
  110. package/dist/components/element/html-element.svelte +85 -85
  111. package/dist/components/form/bond.svelte.d.ts +1 -1
  112. package/dist/components/form/field/field-control.svelte +48 -48
  113. package/dist/components/form/field/field-label.svelte +24 -24
  114. package/dist/components/form/field/field-root.svelte +59 -59
  115. package/dist/components/icon/icon.svelte +44 -44
  116. package/dist/components/image/image.stories.svelte.d.ts +1 -1
  117. package/dist/components/index.d.ts +3 -0
  118. package/dist/components/index.js +3 -0
  119. package/dist/components/input/input-control.svelte +103 -103
  120. package/dist/components/label/label.svelte +25 -25
  121. package/dist/components/popover/popover-arrow.svelte +111 -111
  122. package/dist/components/popover/popover-content.svelte +46 -7
  123. package/dist/components/popover/popover-root.svelte +48 -49
  124. package/dist/components/popover/popover.stories.svelte +52 -67
  125. package/dist/components/portal/portal-root.svelte +83 -83
  126. package/dist/components/portal/teleport.svelte +50 -50
  127. package/dist/components/qr-code/index.d.ts +1 -0
  128. package/dist/components/qr-code/index.js +1 -0
  129. package/dist/components/qr-code/qr-code.stories.svelte +24 -0
  130. package/dist/components/qr-code/qr-code.stories.svelte.d.ts +26 -0
  131. package/dist/components/qr-code/qr-code.svelte +25 -0
  132. package/dist/components/qr-code/qr-code.svelte.d.ts +6 -0
  133. package/dist/components/radio/radio.svelte +109 -109
  134. package/dist/components/radio/types.svelte.d.ts +1 -1
  135. package/dist/components/scrollable/scrollable-container.svelte +82 -82
  136. package/dist/components/scrollable/scrollable-content.svelte +41 -41
  137. package/dist/components/scrollable/scrollable-root.svelte +100 -100
  138. package/dist/components/scrollable/scrollable-thumb.svelte +75 -75
  139. package/dist/components/scrollable/scrollable-track.svelte +59 -59
  140. package/dist/components/scrollable/scrollable.stories.svelte.d.ts +1 -1
  141. package/dist/components/tabs/tab/tab-body.svelte +52 -52
  142. package/dist/components/tabs/tab/tab-description.svelte +41 -41
  143. package/dist/components/tabs/tab/tab-header.svelte +71 -71
  144. package/dist/components/tabs/tab/tab-root.svelte +86 -86
  145. package/dist/components/toast/toast-description.svelte +38 -38
  146. package/dist/components/toast/toast-root.svelte +61 -61
  147. package/dist/components/toast/toast-title.svelte +35 -35
  148. package/dist/components/tree/tree-body.svelte +39 -39
  149. package/dist/components/tree/tree-header.svelte +54 -54
  150. package/dist/components/tree/tree-indicator.svelte +40 -40
  151. package/dist/components/tree/tree-root.svelte +65 -65
  152. package/dist/components/virtual/virtual-root.svelte +239 -239
  153. package/dist/context/preset.svelte.d.ts +1 -1
  154. package/dist/icons/icon-arrow-down.svelte.d.ts +1 -1
  155. package/dist/icons/icon-checkmark.svelte.d.ts +1 -1
  156. package/dist/icons/icon-close.svelte.d.ts +1 -1
  157. package/dist/icons/icon-more-vert.svelte.d.ts +1 -1
  158. package/dist/runes/container.svelte.d.ts +2 -2
  159. package/dist/shared/bond.svelte.d.ts +1 -1
  160. package/dist/utils/state.d.ts +1 -1
  161. package/dist/utils/state.js +2 -1
  162. package/llm/variants.md +1261 -712
  163. package/package.json +464 -437
package/llm/variants.md CHANGED
@@ -1,712 +1,1261 @@
1
- # Variant System
2
-
3
- ## Overview
4
-
5
- The variant system in @svelte-atoms/core provides a powerful way to define component styling variations. It's deeply integrated with the preset system, allowing both global theming and local customization.
6
-
7
- ## Problem Statement
8
-
9
- Currently, creating component variants (size, variant, appearance, etc.) requires:
10
-
11
- ```svelte
12
- <script>
13
- let { variant = 'primary', size = 'md' } = $props();
14
-
15
- const variantClasses = {
16
- primary: 'bg-blue-500 text-white hover:bg-blue-600',
17
- secondary: 'bg-gray-500 text-white hover:bg-gray-600',
18
- danger: 'bg-red-500 text-white hover:bg-red-600'
19
- };
20
-
21
- const sizeClasses = {
22
- sm: 'px-2 py-1 text-sm',
23
- md: 'px-4 py-2 text-base',
24
- lg: 'px-6 py-3 text-lg'
25
- };
26
- </script>
27
-
28
- <HtmlAtom class={`${variantClasses[variant]} ${sizeClasses[size]}`} />
29
- ```
30
-
31
- **Pain points:**
32
-
33
- - Repeated boilerplate in every component
34
- - No access to component state (bond) for reactive variants
35
- - Can't return attributes, only classes
36
- - Not type-safe
37
-
38
- ## Integration with Preset System
39
-
40
- **Key Feature:** Variants are now integrated with the preset system, enabling global variant definitions that can be overridden locally.
41
-
42
- ### Preset Structure
43
-
44
- Presets support the full variant structure for comprehensive theming:
45
-
46
- ```typescript
47
- // Preset: Full variant support
48
- {
49
- class: 'base-classes',
50
- variants: {
51
- variant: { primary: '...', secondary: '...' },
52
- size: { sm: '...', md: '...', lg: '...' }
53
- },
54
- compounds: [
55
- { variant: 'primary', size: 'lg', class: 'shadow-lg' }
56
- ],
57
- defaults: {
58
- variant: 'primary',
59
- size: 'md'
60
- }
61
- }
62
-
63
- // Component: Same structure, overrides/extends preset
64
- {
65
- class: 'component-base',
66
- variants: { ... },
67
- compoundss: [...],
68
- defaults: { ... }
69
- }
70
- ```
71
-
72
- **Merge Behavior:**
73
-
74
- - `variants`: Deep merged (component extends preset)
75
- - `compounds`: Concatenated (preset first, then component compounds)
76
- - `defaults`: Deep merged (component overrides preset)
77
-
78
- ### Architecture
79
-
80
- ```
81
- Preset (Global Theme)
82
- ├─ class: ClassValue (base styling)
83
- ├─ variants: Record<string, Record<string, ClassValue>>
84
- │ ├─ variant: { primary: '...', secondary: '...' }
85
- │ └─ size: { sm: '...', md: '...', lg: '...' }
86
- ├─ compounds: Array<CompoundVariant> (conditional styling)
87
- ├─ defaults: Record<string, string> (default values)
88
- ├─ base: Component (component override)
89
- ├─ as: string (element type)
90
- └─ ...other props
91
-
92
- Component (Local)
93
- ├─ variants: VariantDefinition (extends/overrides preset)
94
- │ ├─ class: ClassValue (component-specific base)
95
- │ ├─ variants: { ... } (extends/overrides preset variants)
96
- │ ├─ compounds: [...] (appended to preset)
97
- │ └─ defaults: {...} (overrides preset defaults)
98
- └─ variant props (size, variant, etc.)
99
-
100
- Final Output = merge(preset, component)
101
- - variants: deep merged
102
- - compounds: concatenated (preset + component)
103
- - defaults: deep merged (component overrides)
104
- ```
105
-
106
- ### Hierarchy
107
-
108
- The system merges variants in this order (later overrides earlier):
109
-
110
- 1. **Preset variants** - Global theme variants
111
- 2. **Component variants** - Local component-specific variants
112
- 3. **Props** - Runtime variant prop values
113
-
114
- ### Example: Global + Local Variants
115
-
116
- ```typescript
117
- // +layout.svelte - Global theme
118
- import { setPreset } from '@svelte-atoms/core/context';
119
-
120
- setPreset({
121
- button: () => ({
122
- class: 'font-medium transition-colors rounded-md focus:outline-none focus:ring-2',
123
- variants: {
124
- variant: {
125
- primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
126
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/90',
127
- destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90'
128
- },
129
- size: {
130
- sm: 'h-8 px-3 text-xs',
131
- md: 'h-10 px-4 text-sm',
132
- lg: 'h-12 px-6 text-base'
133
- }
134
- },
135
- compounds: [
136
- {
137
- variant: 'primary',
138
- size: 'lg',
139
- class: 'shadow-lg font-semibold'
140
- }
141
- ],
142
- defaults: {
143
- variant: 'primary',
144
- size: 'md'
145
- }
146
- })
147
- });
148
- ```
149
-
150
- ```svelte
151
- <!-- Button.svelte - Local override -->
152
- <script>
153
- import { HtmlAtom } from '@svelte-atoms/core';
154
- import { defineVariants } from '@svelte-atoms/core/utils';
155
-
156
- // Extend preset variants with additional local variants
157
- const localVariants = defineVariants({
158
- variants: {
159
- variant: {
160
- // Add new variant not in preset
161
- ghost: 'hover:bg-accent hover:text-accent-foreground'
162
- },
163
- // Add new variant key
164
- loading: {
165
- true: 'opacity-50 cursor-wait',
166
- false: ''
167
- }
168
- }
169
- });
170
-
171
- let {
172
- variant = 'primary',
173
- size = 'md',
174
- loading = false,
175
- ...props
176
- } = $props();
177
- </script>
178
-
179
- <HtmlAtom
180
- preset="button" <!-- Uses global preset -->
181
- variants={localVariants} <!-- Merges with preset -->
182
- {variant}
183
- {size}
184
- {loading}
185
- {...props}
186
- >
187
- {@render children?.()}
188
- </HtmlAtom>
189
- ```
190
-
191
- **Result:** The button will have:
192
-
193
- - Base classes from preset
194
- - Preset variant classes (primary, sm/md/lg)
195
- - Local variant classes (ghost, loading)
196
- - Merged intelligently - local overrides preset
197
-
198
- ```typescript
199
- import { defineVariants } from '@svelte-atoms/core/utils';
200
-
201
- const buttonVariants = defineVariants({
202
- class: 'rounded-md font-medium transition-colors',
203
- variants: {
204
- variant: {
205
- primary: 'bg-blue-500 text-white hover:bg-blue-600',
206
- secondary: 'bg-gray-500 text-white hover:bg-gray-600',
207
- danger: 'bg-red-500 text-white hover:bg-red-600'
208
- },
209
- size: {
210
- sm: 'px-2 py-1 text-sm',
211
- md: 'px-4 py-2 text-base',
212
- lg: 'px-6 py-3 text-lg'
213
- }
214
- },
215
- compounds: [
216
- {
217
- variant: 'primary',
218
- size: 'lg',
219
- class: 'shadow-lg' // Applied when both conditions match
220
- }
221
- ],
222
- defaults: {
223
- variant: 'primary',
224
- size: 'md'
225
- }
226
- });
227
- ```
228
-
229
- ```svelte
230
- <script>
231
- import { buttonVariants } from './variants';
232
-
233
- let { variant, size, ...props } = $props();
234
-
235
- const variantProps = buttonVariants({ variant, size });
236
- </script>
237
-
238
- <HtmlAtom {...variantProps} {...props}>
239
- {@render children?.()}
240
- </HtmlAtom>
241
- ```
242
-
243
- ## Key Features
244
-
245
- ### 1. Access to Component State (Bond)
246
-
247
- Variant values can be functions that receive the component's bond for reactive styling:
248
-
249
- ```typescript
250
- const accordionVariants = defineVariants({
251
- class: 'border rounded-md transition-all',
252
- variants: {
253
- state: {
254
- open: (bond) => ({
255
- class: bond?.state?.isOpen ? 'bg-blue-50 border-blue-200' : 'bg-white',
256
- 'aria-expanded': bond?.state?.isOpen,
257
- 'data-state': bond?.state?.isOpen ? 'open' : 'closed'
258
- }),
259
- disabled: (bond) => ({
260
- class: bond?.state?.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
261
- 'aria-disabled': bond?.state?.disabled
262
- })
263
- }
264
- }
265
- });
266
-
267
- // Usage:
268
- const bond = AccordionBond.get();
269
- const variantProps = accordionVariants(bond, { state: 'open' });
270
- ```
271
-
272
- ### 2. Return Both Classes and Attributes
273
-
274
- Variants can return not just classes, but any HTML attributes:
275
-
276
- ```typescript
277
- const buttonVariants = defineVariants({
278
- variants: {
279
- variant: {
280
- primary: {
281
- class: 'bg-blue-500 text-white',
282
- 'aria-label': 'Primary action',
283
- 'data-variant': 'primary'
284
- },
285
- danger: (bond) => ({
286
- class: bond?.state?.disabled ? 'bg-red-300' : 'bg-red-500',
287
- 'aria-disabled': bond?.state?.disabled,
288
- role: 'button'
289
- })
290
- }
291
- }
292
- });
293
-
294
- // Returns: { class: '...', 'aria-label': '...', 'data-variant': '...', ... }
295
- ```
296
-
297
- ### 3. Compound Variants
298
-
299
- Apply additional styling when multiple conditions match:
300
-
301
- ```typescript
302
- const alertVariants = defineVariants({
303
- class: 'rounded-lg p-4 border',
304
- variants: {
305
- variant: {
306
- error: 'bg-red-50 border-red-200 text-red-900'
307
- },
308
- size: {
309
- lg: 'text-lg'
310
- }
311
- },
312
- compounds: [
313
- {
314
- variant: 'error',
315
- size: 'lg',
316
- class: 'font-bold', // Only applied when both variant=error AND size=lg
317
- role: 'alert'
318
- }
319
- ]
320
- });
321
- ```
322
-
323
- ### 4. Type Safety
324
-
325
- Full TypeScript support with automatic type inference:
326
-
327
- ```typescript
328
- type ButtonVariants = VariantPropsType<typeof buttonVariants>;
329
- // Inferred type: { variant?: 'primary' | 'secondary' | 'danger'; size?: 'sm' | 'md' | 'lg' }
330
- ```
331
-
332
- ### 5. Default Variants
333
-
334
- Specify default values that are used when no variant is provided:
335
-
336
- ```typescript
337
- const buttonVariants = defineVariants({
338
- class: 'rounded-md',
339
- variants: {
340
- variant: {
341
- primary: 'bg-blue-500',
342
- secondary: 'bg-gray-500'
343
- },
344
- size: {
345
- sm: 'text-sm',
346
- md: 'text-base',
347
- lg: 'text-lg'
348
- }
349
- },
350
- defaults: {
351
- variant: 'primary', // Used if variant prop is undefined
352
- size: 'md' // Used if size prop is undefined
353
- }
354
- });
355
-
356
- // Call without props - uses defaults
357
- buttonVariants(null); // Returns variant='primary', size='md'
358
- ```
359
-
360
- ## Complete Examples
361
-
362
- ### Example 1: Preset-based Button (Recommended)
363
-
364
- ```typescript
365
- // Theme.svelte - Define global button variants
366
- import { setPreset } from '@svelte-atoms/core/context';
367
-
368
- setPreset({
369
- button: () => ({
370
- class:
371
- 'inline-flex items-center justify-center font-medium rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2',
372
- variants: {
373
- variant: {
374
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
375
- destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
376
- outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
377
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
378
- ghost: 'hover:bg-accent hover:text-accent-foreground',
379
- link: 'text-primary underline-offset-4 hover:underline'
380
- },
381
- size: {
382
- default: 'h-10 px-4 py-2',
383
- sm: 'h-9 rounded-md px-3',
384
- lg: 'h-11 rounded-md px-8',
385
- icon: 'h-10 w-10'
386
- }
387
- },
388
- compounds: [
389
- {
390
- variant: 'default',
391
- size: 'lg',
392
- class: 'text-base font-semibold'
393
- }
394
- ],
395
- defaults: {
396
- variant: 'default',
397
- size: 'default'
398
- }
399
- })
400
- });
401
- ```
402
-
403
- ```svelte
404
- <!-- Button.svelte - Use preset variants -->
405
- <script lang="ts">
406
- import { HtmlAtom } from '@svelte-atoms/core';
407
-
408
- type ButtonProps = {
409
- variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
410
- size?: 'default' | 'sm' | 'lg' | 'icon';
411
- disabled?: boolean;
412
- };
413
-
414
- let { variant, size, disabled = false, class: klass = '', ...props }: ButtonProps = $props();
415
- </script>
416
-
417
- <HtmlAtom preset="button" as="button" {variant} {size} {disabled} class={klass} {...props}>
418
- {@render children?.()}
419
- </HtmlAtom>
420
- ```
421
-
422
- ### Example 2: Local Variants Only
423
-
424
- ````svelte
425
- ### Example 3: Extending Preset with Local Variants
426
-
427
- Combine global preset variants with component-specific variants:
428
-
429
- ```svelte
430
- <!-- special-button.svelte -->
431
- <script>
432
- import { HtmlAtom } from '@svelte-atoms/core';
433
- import { defineVariants } from '@svelte-atoms/core/utils';
434
-
435
- // Local variants that extend/override preset
436
- const localVariants = defineVariants({
437
- variants: {
438
- variant: {
439
- // Add new variants not in preset
440
- gradient: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white',
441
- neon: 'bg-black text-neon-green border-2 border-neon-green'
442
- },
443
- // Add completely new variant key
444
- animated: {
445
- true: 'animate-pulse',
446
- false: ''
447
- }
448
- }
449
- });
450
-
451
- let {
452
- variant,
453
- size,
454
- animated = false,
455
- ...props
456
- } = $props();
457
- </script>
458
-
459
- <HtmlAtom
460
- preset="button" <!-- Gets base variants -->
461
- variants={localVariants} <!-- Merges/extends -->
462
- {variant}
463
- {size}
464
- {animated}
465
- {...props}
466
- >
467
- {@render children?.()}
468
- </HtmlAtom>
469
- ````
470
-
471
- ### Example 4: Reactive Variants with Bond State
472
-
473
- <script>
474
- import { HtmlAtom } from '@svelte-atoms/core';
475
- import { defineVariants } from '@svelte-atoms/core/utils';
476
-
477
- const alertVariants = defineVariants({
478
- class: 'rounded-lg p-4 border',
479
- variants: {
480
- variant: {
481
- info: 'bg-blue-50 border-blue-200 text-blue-900',
482
- success: 'bg-green-50 border-green-200 text-green-900',
483
- warning: 'bg-yellow-50 border-yellow-200 text-yellow-900',
484
- error: 'bg-red-50 border-red-200 text-red-900'
485
- },
486
- size: {
487
- sm: 'text-sm p-2',
488
- md: 'text-base p-4',
489
- lg: 'text-lg p-6'
490
- }
491
- },
492
- compounds: [
493
- {
494
- variant: 'error',
495
- size: 'lg',
496
- class: 'font-bold',
497
- role: 'alert',
498
- 'aria-live': 'assertive'
499
- }
500
- ],
501
- defaults: {
502
- variant: 'info',
503
- size: 'md'
504
- }
505
- });
506
-
507
- let { variant, size, ...props } = $props();
508
-
509
- const bond = null; // or get from context if needed
510
- const variantProps = alertVariants(bond, { variant, size });
511
- </script>
512
-
513
- <HtmlAtom {...variantProps} {...props}>
514
- {@render children?.()}
515
- </HtmlAtom>
516
-
517
- ````
518
-
519
- ### Example 3: Accordion with Reactive Bond State
520
-
521
- ```svelte
522
- <!-- accordion-item.svelte -->
523
- <script>
524
- import { HtmlAtom } from '@svelte-atoms/core';
525
- import { defineVariants } from '@svelte-atoms/core/utils';
526
- import { AccordionBond } from './bond.svelte';
527
-
528
- const accordionVariants = defineVariants({
529
- class: 'border rounded-md transition-all duration-200',
530
- variants: {
531
- state: {
532
- // Reactive: changes when bond.state.isOpen changes
533
- open: (bond) => ({
534
- class: bond?.state?.isOpen
535
- ? 'bg-blue-50 border-blue-200'
536
- : 'bg-white border-gray-200',
537
- 'aria-expanded': bond?.state?.isOpen,
538
- 'data-state': bond?.state?.isOpen ? 'open' : 'closed'
539
- }),
540
- disabled: (bond) => ({
541
- class: bond?.state?.disabled
542
- ? 'opacity-50 cursor-not-allowed'
543
- : 'cursor-pointer',
544
- 'aria-disabled': bond?.state?.disabled
545
- })
546
- }
547
- }
548
- });
549
-
550
- const bond = AccordionBond.get();
551
-
552
- // Automatically reactive - updates when bond state changes
553
- const variantProps = $derived(accordionVariants(bond, { state: 'open' }));
554
- </script>
555
-
556
- <HtmlAtom {...variantProps}>
557
- {@render children?.({ accordion: bond })}
558
- </HtmlAtom>
559
- ````
560
-
561
- ## Migration Guide
562
-
563
- ### Before: Manual Variant Management
564
-
565
- ```svelte
566
- <script>
567
- let { variant = 'primary', size = 'md' } = $props();
568
-
569
- const variantClasses = {
570
- primary: 'bg-blue-500 text-white hover:bg-blue-600',
571
- secondary: 'bg-gray-500 text-white hover:bg-gray-600',
572
- danger: 'bg-red-500 text-white hover:bg-red-600'
573
- };
574
-
575
- const sizeClasses = {
576
- sm: 'px-2 py-1 text-sm',
577
- md: 'px-4 py-2 text-base',
578
- lg: 'px-6 py-3 text-lg'
579
- };
580
-
581
- const classes = `rounded-md ${variantClasses[variant]} ${sizeClasses[size]}`;
582
- </script>
583
-
584
- <button class={classes}> Click me </button>
585
- ```
586
-
587
- ### After: defineVariants
588
-
589
- ```svelte
590
- <script>
591
- import { HtmlAtom } from '@svelte-atoms/core';
592
- import { defineVariants } from '@svelte-atoms/core/utils';
593
-
594
- const buttonVariants = defineVariants({
595
- class: 'rounded-md',
596
- variants: {
597
- variant: {
598
- primary: 'bg-blue-500 text-white hover:bg-blue-600',
599
- secondary: 'bg-gray-500 text-white hover:bg-gray-600',
600
- danger: 'bg-red-500 text-white hover:bg-red-600'
601
- },
602
- size: {
603
- sm: 'px-2 py-1 text-sm',
604
- md: 'px-4 py-2 text-base',
605
- lg: 'px-6 py-3 text-lg'
606
- }
607
- },
608
- defaults: {
609
- variant: 'primary',
610
- size: 'md'
611
- }
612
- });
613
-
614
- let { variant, size, ...props } = $props();
615
-
616
- const bond = null; // or get from context if needed
617
- const variantProps = buttonVariants(bond, { variant, size });
618
- </script>
619
-
620
- <HtmlAtom as="button" {...variantProps} {...props}>Click me</HtmlAtom>
621
- ```
622
-
623
- ### Benefits After Migration
624
-
625
- **Less boilerplate** - Define once, use everywhere
626
- **Type safety** - Automatic type inference
627
- **Compound variants** - Complex styling combinations
628
- ✅ **Default values** - No need for fallback logic
629
- ✅ **Bond integration** - Access component state for reactive variants
630
- **Return attributes** - Not just classes, any HTML attributes
631
-
632
- ## Best Practices
633
-
634
- ### 1. Organize Variants in Separate Files
635
-
636
- ```typescript
637
- // variants.ts
638
- import { defineVariants } from '@svelte-atoms/core/utils';
639
-
640
- export const buttonVariants = defineVariants({
641
- class: 'rounded-md font-medium transition-colors',
642
- variants: {
643
- /* ... */
644
- },
645
- defaults: {
646
- /* ... */
647
- }
648
- });
649
-
650
- export const cardVariants = defineVariants({
651
- class: 'rounded-lg border',
652
- variants: {
653
- /* ... */
654
- },
655
- defaults: {
656
- /* ... */
657
- }
658
- });
659
- ```
660
-
661
- ### 2. Use $derived for Reactive Variants
662
-
663
- When using bond state, wrap the variant call in `$derived`:
664
-
665
- ```svelte
666
- <script>
667
- const bond = AccordionBond.get();
668
-
669
- // Reactive - updates when bond state changes
670
- const variantProps = $derived(accordionVariants(bond, { state: 'open' }));
671
- </script>
672
- ```
673
-
674
- ### 3. Extend with Additional Classes
675
-
676
- Merge variant classes with custom classes:
677
-
678
- ```svelte
679
- <script>
680
- const bond = null; // or get from context if needed
681
- const variantProps = buttonVariants(bond, { variant, size });
682
- </script>
683
-
684
- <HtmlAtom class={[variantProps.class, 'custom-class']} {...variantProps} />
685
- ```
686
-
687
- ### 4. Type Props from Variants
688
-
689
- Extract variant types for component props:
690
-
691
- ```typescript
692
- import { VariantPropsType } from '@svelte-atoms/core/utils';
693
- import { buttonVariants } from './variants';
694
-
695
- type ButtonProps = VariantPropsType<typeof buttonVariants> & {
696
- disabled?: boolean;
697
- // other props...
698
- };
699
- ```
700
-
701
- ## Summary
702
-
703
- `defineVariants()` provides:
704
-
705
- **Single function** - One API for all variant needs
706
- ✅ **Type-safe** - Automatic TypeScript inference
707
- ✅ **Reactive** - Access bond state for dynamic styling
708
- ✅ **Powerful** - Base classes, compound variants, defaults
709
- ✅ **Flexible** - Return both classes and attributes
710
- **Clean** - No manual object merging or conditionals
711
-
712
- Inspired by Class Variance Authority but integrated with @svelte-atoms/core's bond system.
1
+ # Variant System
2
+
3
+ ## Overview
4
+
5
+ The variant system in @svelte-atoms/core provides a powerful way to define component styling variations. It's deeply integrated with the preset system, allowing both global theming and local customization.
6
+
7
+ ## Problem Statement
8
+
9
+ Currently, creating component variants (size, variant, appearance, etc.) requires:
10
+
11
+ ```svelte
12
+ <script>
13
+ let { variant = 'primary', size = 'md' } = $props();
14
+
15
+ const variantClasses = {
16
+ primary: 'bg-blue-500 text-white hover:bg-blue-600',
17
+ secondary: 'bg-gray-500 text-white hover:bg-gray-600',
18
+ danger: 'bg-red-500 text-white hover:bg-red-600'
19
+ };
20
+
21
+ const sizeClasses = {
22
+ sm: 'px-2 py-1 text-sm',
23
+ md: 'px-4 py-2 text-base',
24
+ lg: 'px-6 py-3 text-lg'
25
+ };
26
+ </script>
27
+
28
+ <HtmlAtom class={`${variantClasses[variant]} ${sizeClasses[size]}`} />
29
+ ```
30
+
31
+ **Pain points:**
32
+
33
+ - Repeated boilerplate in every component
34
+ - No access to component state (bond) for reactive variants
35
+ - Can't return attributes, only classes
36
+ - Not type-safe
37
+
38
+ ## Integration with Preset System
39
+
40
+ **Key Feature:** Variants are now integrated with the preset system, enabling global variant definitions that can be overridden locally.
41
+
42
+ ### Preset Structure
43
+
44
+ Presets support the full variant structure for comprehensive theming:
45
+
46
+ ```typescript
47
+ // Preset: Full variant support
48
+ {
49
+ class: 'base-classes',
50
+ variants: {
51
+ variant: { primary: '...', secondary: '...' },
52
+ size: { sm: '...', md: '...', lg: '...' }
53
+ },
54
+ compounds: [
55
+ { variant: 'primary', size: 'lg', class: 'shadow-lg' }
56
+ ],
57
+ defaults: {
58
+ variant: 'primary',
59
+ size: 'md'
60
+ }
61
+ }
62
+
63
+ // Component: Same structure, overrides/extends preset
64
+ {
65
+ class: 'component-base',
66
+ variants: { ... },
67
+ compoundss: [...],
68
+ defaults: { ... }
69
+ }
70
+ ```
71
+
72
+ **Merge Behavior:**
73
+
74
+ - `variants`: Deep merged (component extends preset)
75
+ - `compounds`: Concatenated (preset first, then component compounds)
76
+ - `defaults`: Deep merged (component overrides preset)
77
+
78
+ ### Architecture
79
+
80
+ ```
81
+ Preset (Global Theme)
82
+ ├─ class: ClassValue (base styling)
83
+ ├─ variants: Record<string, Record<string, ClassValue>>
84
+ │ ├─ variant: { primary: '...', secondary: '...' }
85
+ │ └─ size: { sm: '...', md: '...', lg: '...' }
86
+ ├─ compounds: Array<CompoundVariant> (conditional styling)
87
+ ├─ defaults: Record<string, string> (default values)
88
+ ├─ base: Component (component override)
89
+ ├─ as: string (element type)
90
+ └─ ...other props
91
+
92
+ Component (Local)
93
+ ├─ variants: VariantDefinition (extends/overrides preset)
94
+ │ ├─ class: ClassValue (component-specific base)
95
+ │ ├─ variants: { ... } (extends/overrides preset variants)
96
+ │ ├─ compounds: [...] (appended to preset)
97
+ │ └─ defaults: {...} (overrides preset defaults)
98
+ └─ variant props (size, variant, etc.)
99
+
100
+ Final Output = merge(preset, component)
101
+ - variants: deep merged
102
+ - compounds: concatenated (preset + component)
103
+ - defaults: deep merged (component overrides)
104
+ ```
105
+
106
+ ### Hierarchy
107
+
108
+ The system merges variants in this order (later overrides earlier):
109
+
110
+ 1. **Preset variants** - Global theme variants
111
+ 2. **Component variants** - Local component-specific variants
112
+ 3. **Props** - Runtime variant prop values
113
+
114
+ ### Example: Global + Local Variants
115
+
116
+ ```typescript
117
+ // +layout.svelte - Global theme
118
+ import { setPreset } from '@svelte-atoms/core/context';
119
+
120
+ setPreset({
121
+ button: () => ({
122
+ class: 'font-medium transition-colors rounded-md focus:outline-none focus:ring-2',
123
+ variants: {
124
+ variant: {
125
+ primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
126
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/90',
127
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90'
128
+ },
129
+ size: {
130
+ sm: 'h-8 px-3 text-xs',
131
+ md: 'h-10 px-4 text-sm',
132
+ lg: 'h-12 px-6 text-base'
133
+ }
134
+ },
135
+ compounds: [
136
+ {
137
+ variant: 'primary',
138
+ size: 'lg',
139
+ class: 'shadow-lg font-semibold'
140
+ }
141
+ ],
142
+ defaults: {
143
+ variant: 'primary',
144
+ size: 'md'
145
+ }
146
+ })
147
+ });
148
+ ```
149
+
150
+ ```svelte
151
+ <!-- Button.svelte - Local override -->
152
+ <script>
153
+ import { HtmlAtom } from '@svelte-atoms/core';
154
+ import { defineVariants } from '@svelte-atoms/core/utils';
155
+
156
+ // Extend preset variants with additional local variants
157
+ const localVariants = defineVariants({
158
+ variants: {
159
+ variant: {
160
+ // Add new variant not in preset
161
+ ghost: 'hover:bg-accent hover:text-accent-foreground'
162
+ },
163
+ // Add new variant key
164
+ loading: {
165
+ true: 'opacity-50 cursor-wait',
166
+ false: ''
167
+ }
168
+ }
169
+ });
170
+
171
+ let {
172
+ variant = 'primary',
173
+ size = 'md',
174
+ loading = false,
175
+ ...props
176
+ } = $props();
177
+ </script>
178
+
179
+ <HtmlAtom
180
+ preset="button" <!-- Uses global preset -->
181
+ variants={localVariants} <!-- Merges with preset -->
182
+ {variant}
183
+ {size}
184
+ {loading}
185
+ {...props}
186
+ >
187
+ {@render children?.()}
188
+ </HtmlAtom>
189
+ ```
190
+
191
+ **Result:** The button will have:
192
+
193
+ - Base classes from preset
194
+ - Preset variant classes (primary, sm/md/lg)
195
+ - Local variant classes (ghost, loading)
196
+ - Merged intelligently - local overrides preset
197
+
198
+ ## Basic Usage
199
+
200
+ **Key Pattern:** `defineVariants()` returns a **function** that you pass to `HtmlAtom`. The component calls this function internally with bond and props.
201
+
202
+ ```svelte
203
+ <script lang="ts">
204
+ import { HtmlAtom } from '@svelte-atoms/core';
205
+ import { defineVariants } from '@svelte-atoms/core/utils';
206
+
207
+ // defineVariants returns a FUNCTION
208
+ const variants = defineVariants({
209
+ class: 'rounded-md font-medium transition-colors',
210
+ variants: {
211
+ variant: {
212
+ primary: {
213
+ class: 'bg-blue-500 text-white hover:bg-blue-600'
214
+ },
215
+ secondary: {
216
+ class: 'bg-gray-500 text-white hover:bg-gray-600'
217
+ },
218
+ danger: {
219
+ class: 'bg-red-500 text-white hover:bg-red-600'
220
+ }
221
+ },
222
+ size: {
223
+ sm: 'px-2 py-1 text-sm',
224
+ md: 'px-4 py-2 text-base',
225
+ lg: 'px-6 py-3 text-lg'
226
+ }
227
+ },
228
+ compounds: [
229
+ {
230
+ variant: 'primary',
231
+ size: 'lg',
232
+ class: 'shadow-lg' // Applied when both conditions match
233
+ }
234
+ ],
235
+ defaults: {
236
+ variant: 'primary',
237
+ size: 'md'
238
+ }
239
+ });
240
+
241
+ let { variant, size, ...props } = $props();
242
+ </script>
243
+
244
+ <!-- Pass the variant FUNCTION to HtmlAtom -->
245
+ <!-- HtmlAtom will call it internally: variants(bond, { variant, size }) -->
246
+ <HtmlAtom {variants} {variant} {size} {...props}>
247
+ {@render children?.()}
248
+ </HtmlAtom>
249
+ ```
250
+
251
+ **How it works:**
252
+
253
+ 1. `defineVariants(config)` → returns a function
254
+ 2. You pass this function to `HtmlAtom` via `{variants}`
255
+ 3. `HtmlAtom` internally calls `variants(bond, restProps)`
256
+ 4. The function returns `{ class: [...], ...attributes }`
257
+ 5. `HtmlAtom` applies these to the rendered element
258
+
259
+ ## Key Features
260
+
261
+ ### 1. Access to Component State (Bond)
262
+
263
+ Variant values can be functions that receive the component's bond for reactive styling:
264
+
265
+ ```typescript
266
+ const accordionVariants = defineVariants({
267
+ class: 'border rounded-md transition-all',
268
+ variants: {
269
+ state: {
270
+ open: (bond) => ({
271
+ class: bond?.state?.isOpen ? 'bg-blue-50 border-blue-200' : 'bg-white',
272
+ 'aria-expanded': bond?.state?.isOpen,
273
+ 'data-state': bond?.state?.isOpen ? 'open' : 'closed'
274
+ }),
275
+ disabled: (bond) => ({
276
+ class: bond?.state?.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
277
+ 'aria-disabled': bond?.state?.disabled
278
+ })
279
+ }
280
+ }
281
+ });
282
+
283
+ // Usage in component:
284
+ let { state = 'open', ...props } = $props();
285
+
286
+ <HtmlAtom {variants} {state} {...props}>
287
+ {@render children?.()}
288
+ </HtmlAtom>
289
+ ```
290
+
291
+ ### 2. Return Both Classes and Attributes
292
+
293
+ Variants can return not just classes, but any HTML attributes:
294
+
295
+ ```typescript
296
+ const buttonVariants = defineVariants({
297
+ variants: {
298
+ variant: {
299
+ primary: {
300
+ class: 'bg-blue-500 text-white',
301
+ 'aria-label': 'Primary action',
302
+ 'data-variant': 'primary'
303
+ },
304
+ danger: (bond) => ({
305
+ class: bond?.state?.disabled ? 'bg-red-300' : 'bg-red-500',
306
+ 'aria-disabled': bond?.state?.disabled,
307
+ role: 'button'
308
+ })
309
+ }
310
+ }
311
+ });
312
+
313
+ // When called by HtmlAtom, returns: { class: [...], 'aria-label': '...', 'data-variant': '...', ... }
314
+ ```
315
+
316
+ ### 3. Compound Variants
317
+
318
+ Apply additional styling when multiple conditions match:
319
+
320
+ ```typescript
321
+ const alertVariants = defineVariants({
322
+ class: 'rounded-lg p-4 border',
323
+ variants: {
324
+ variant: {
325
+ error: 'bg-red-50 border-red-200 text-red-900'
326
+ },
327
+ size: {
328
+ lg: 'text-lg'
329
+ }
330
+ },
331
+ compounds: [
332
+ {
333
+ variant: 'error',
334
+ size: 'lg',
335
+ class: 'font-bold', // Only applied when both variant=error AND size=lg
336
+ role: 'alert'
337
+ }
338
+ ]
339
+ });
340
+ ```
341
+
342
+ ### 4. Type Safety
343
+
344
+ Full TypeScript support with automatic type inference:
345
+
346
+ ```typescript
347
+ type ButtonVariants = VariantPropsType<typeof buttonVariants>;
348
+ // Inferred type: { variant?: 'primary' | 'secondary' | 'danger'; size?: 'sm' | 'md' | 'lg' }
349
+ ```
350
+
351
+ ### 5. Default Variants
352
+
353
+ Specify default values that are used when no variant is provided:
354
+
355
+ ```typescript
356
+ const buttonVariants = defineVariants({
357
+ class: 'rounded-md',
358
+ variants: {
359
+ variant: {
360
+ primary: 'bg-blue-500',
361
+ secondary: 'bg-gray-500'
362
+ },
363
+ size: {
364
+ sm: 'text-sm',
365
+ md: 'text-base',
366
+ lg: 'text-lg'
367
+ }
368
+ },
369
+ defaults: {
370
+ variant: 'primary', // Used if variant prop is undefined
371
+ size: 'md' // Used if size prop is undefined
372
+ }
373
+ });
374
+
375
+ // Pass to HtmlAtom - uses defaults when props not provided
376
+ <HtmlAtom {variants} {...props}>
377
+ {@render children?.()}
378
+ </HtmlAtom>
379
+ ```
380
+
381
+ ## Complete Examples
382
+
383
+ ### Example 1: Preset-based Button (Recommended)
384
+
385
+ ```typescript
386
+ // Theme.svelte - Define global button variants
387
+ import { setPreset } from '@svelte-atoms/core/context';
388
+
389
+ setPreset({
390
+ button: () => ({
391
+ class:
392
+ 'inline-flex items-center justify-center font-medium rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2',
393
+ variants: {
394
+ variant: {
395
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
396
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
397
+ outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
398
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
399
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
400
+ link: 'text-primary underline-offset-4 hover:underline'
401
+ },
402
+ size: {
403
+ default: 'h-10 px-4 py-2',
404
+ sm: 'h-9 rounded-md px-3',
405
+ lg: 'h-11 rounded-md px-8',
406
+ icon: 'h-10 w-10'
407
+ }
408
+ },
409
+ compounds: [
410
+ {
411
+ variant: 'default',
412
+ size: 'lg',
413
+ class: 'text-base font-semibold'
414
+ }
415
+ ],
416
+ defaults: {
417
+ variant: 'default',
418
+ size: 'default'
419
+ }
420
+ })
421
+ });
422
+ ```
423
+
424
+ ```svelte
425
+ <!-- Button.svelte - Use preset variants -->
426
+ <script lang="ts">
427
+ import { HtmlAtom } from '@svelte-atoms/core';
428
+
429
+ type ButtonProps = {
430
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
431
+ size?: 'default' | 'sm' | 'lg' | 'icon';
432
+ disabled?: boolean;
433
+ };
434
+
435
+ let { variant, size, disabled = false, class: klass = '', ...props }: ButtonProps = $props();
436
+ </script>
437
+
438
+ <HtmlAtom preset="button" as="button" {variant} {size} {disabled} class={klass} {...props}>
439
+ {@render children?.()}
440
+ </HtmlAtom>
441
+ ```
442
+
443
+ ### Example 2: Local Variants with HtmlAtom
444
+
445
+ ```svelte
446
+ <script lang="ts">
447
+ import { HtmlAtom } from '@svelte-atoms/core';
448
+ import { defineVariants } from '@svelte-atoms/core/utils';
449
+
450
+ const buttonVariants = defineVariants({
451
+ class: 'inline-flex items-center justify-center rounded-md font-medium',
452
+ variants: {
453
+ variant: {
454
+ primary: 'bg-blue-500 text-white hover:bg-blue-600',
455
+ secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
456
+ ghost: 'hover:bg-gray-100'
457
+ },
458
+ size: {
459
+ sm: 'h-8 px-3 text-sm',
460
+ md: 'h-10 px-4',
461
+ lg: 'h-12 px-6 text-lg'
462
+ }
463
+ },
464
+ defaults: {
465
+ variant: 'primary',
466
+ size: 'md'
467
+ }
468
+ });
469
+
470
+ let { variant, size, ...props } = $props();
471
+ </script>
472
+
473
+ <HtmlAtom {variants} as="button" {variant} {size} {...props}>
474
+ {@render children?.()}
475
+ </HtmlAtom>
476
+ ```
477
+
478
+ ### Example 3: Extending Preset with Local Variants
479
+
480
+ Combine global preset variants with component-specific variants:
481
+
482
+ ```svelte
483
+ <!-- special-button.svelte -->
484
+ <script>
485
+ import { HtmlAtom } from '@svelte-atoms/core';
486
+ import { defineVariants } from '@svelte-atoms/core/utils';
487
+
488
+ // Local variants that extend/override preset
489
+ const localVariants = defineVariants({
490
+ variants: {
491
+ variant: {
492
+ // Add new variants not in preset
493
+ gradient: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white',
494
+ neon: 'bg-black text-neon-green border-2 border-neon-green'
495
+ },
496
+ // Add completely new variant key
497
+ animated: {
498
+ true: 'animate-pulse',
499
+ false: ''
500
+ }
501
+ }
502
+ });
503
+
504
+ let {
505
+ variant,
506
+ size,
507
+ animated = false,
508
+ ...props
509
+ } = $props();
510
+ </script>
511
+
512
+ <HtmlAtom
513
+ preset="button" <!-- Gets base variants -->
514
+ variants={localVariants} <!-- Merges/extends -->
515
+ {variant}
516
+ {size}
517
+ {animated}
518
+ {...props}
519
+ >
520
+ {@render children?.()}
521
+ </HtmlAtom>
522
+ ````
523
+
524
+ ### Example 4: Alert Component with Compound Variants
525
+
526
+ ```svelte
527
+ <script lang="ts">
528
+ import { HtmlAtom } from '@svelte-atoms/core';
529
+ import { defineVariants } from '@svelte-atoms/core/utils';
530
+
531
+ const alertVariants = defineVariants({
532
+ class: 'rounded-lg p-4 border',
533
+ variants: {
534
+ variant: {
535
+ info: 'bg-blue-50 border-blue-200 text-blue-900',
536
+ success: 'bg-green-50 border-green-200 text-green-900',
537
+ warning: 'bg-yellow-50 border-yellow-200 text-yellow-900',
538
+ error: 'bg-red-50 border-red-200 text-red-900'
539
+ },
540
+ size: {
541
+ sm: 'text-sm p-2',
542
+ md: 'text-base p-4',
543
+ lg: 'text-lg p-6'
544
+ }
545
+ },
546
+ compounds: [
547
+ {
548
+ variant: 'error',
549
+ size: 'lg',
550
+ class: 'font-bold',
551
+ role: 'alert',
552
+ 'aria-live': 'assertive'
553
+ }
554
+ ],
555
+ defaults: {
556
+ variant: 'info',
557
+ size: 'md'
558
+ }
559
+ });
560
+
561
+ let { variant, size, ...props } = $props();
562
+ </script>
563
+
564
+ <HtmlAtom {variants} {variant} {size} {...props}>
565
+ {@render children?.()}
566
+ </HtmlAtom>
567
+ ```
568
+
569
+ ````
570
+
571
+ ### Example 3: Accordion with Reactive Bond State
572
+
573
+ ```svelte
574
+ <!-- accordion-item.svelte -->
575
+ <script>
576
+ import { HtmlAtom } from '@svelte-atoms/core';
577
+ import { defineVariants } from '@svelte-atoms/core/utils';
578
+ import { AccordionBond } from './bond.svelte';
579
+
580
+ const accordionVariants = defineVariants({
581
+ class: 'border rounded-md transition-all duration-200',
582
+ variants: {
583
+ state: {
584
+ // Reactive: changes when bond.state.isOpen changes
585
+ open: (bond) => ({
586
+ class: bond?.state?.isOpen
587
+ ? 'bg-blue-50 border-blue-200'
588
+ : 'bg-white border-gray-200',
589
+ 'aria-expanded': bond?.state?.isOpen,
590
+ 'data-state': bond?.state?.isOpen ? 'open' : 'closed'
591
+ }),
592
+ disabled: (bond) => ({
593
+ class: bond?.state?.disabled
594
+ ? 'opacity-50 cursor-not-allowed'
595
+ : 'cursor-pointer',
596
+ 'aria-disabled': bond?.state?.disabled
597
+ })
598
+ }
599
+ }
600
+ });
601
+
602
+ let { state = 'open', ...props } = $props();
603
+ </script>
604
+
605
+ <HtmlAtom {variants} {state} {...props}>
606
+ {@render children?.()}
607
+ </HtmlAtom>
608
+ ````
609
+
610
+ ## Migration Guide
611
+
612
+ ### Before: Manual Variant Management
613
+
614
+ ```svelte
615
+ <script>
616
+ let { variant = 'primary', size = 'md' } = $props();
617
+
618
+ const variantClasses = {
619
+ primary: 'bg-blue-500 text-white hover:bg-blue-600',
620
+ secondary: 'bg-gray-500 text-white hover:bg-gray-600',
621
+ danger: 'bg-red-500 text-white hover:bg-red-600'
622
+ };
623
+
624
+ const sizeClasses = {
625
+ sm: 'px-2 py-1 text-sm',
626
+ md: 'px-4 py-2 text-base',
627
+ lg: 'px-6 py-3 text-lg'
628
+ };
629
+
630
+ const classes = `rounded-md ${variantClasses[variant]} ${sizeClasses[size]}`;
631
+ </script>
632
+
633
+ <button class={classes}> Click me </button>
634
+ ```
635
+
636
+ ### After: defineVariants
637
+
638
+ ```svelte
639
+ <script>
640
+ import { HtmlAtom } from '@svelte-atoms/core';
641
+ import { defineVariants } from '@svelte-atoms/core/utils';
642
+
643
+ const buttonVariants = defineVariants({
644
+ class: 'rounded-md',
645
+ variants: {
646
+ variant: {
647
+ primary: 'bg-blue-500 text-white hover:bg-blue-600',
648
+ secondary: 'bg-gray-500 text-white hover:bg-gray-600',
649
+ danger: 'bg-red-500 text-white hover:bg-red-600'
650
+ },
651
+ size: {
652
+ sm: 'px-2 py-1 text-sm',
653
+ md: 'px-4 py-2 text-base',
654
+ lg: 'px-6 py-3 text-lg'
655
+ }
656
+ },
657
+ defaults: {
658
+ variant: 'primary',
659
+ size: 'md'
660
+ }
661
+ });
662
+
663
+ let { variant, size, ...props } = $props();
664
+ </script>
665
+
666
+ <!-- Pass the variant function to HtmlAtom -->
667
+ <HtmlAtom {variants} as="button" {variant} {size} {...props}>
668
+ Click me
669
+ </HtmlAtom>
670
+ ```
671
+
672
+ ### Benefits After Migration
673
+
674
+ **Less boilerplate** - Define once, use everywhere
675
+ ✅ **Type safety** - Automatic type inference
676
+ **Compound variants** - Complex styling combinations
677
+ ✅ **Default values** - No need for fallback logic
678
+ ✅ **Bond integration** - Access component state for reactive variants
679
+ ✅ **Return attributes** - Not just classes, any HTML attributes
680
+
681
+ ## Best Practices
682
+
683
+ ### 1. Organize Variants in Separate Files
684
+
685
+ ```typescript
686
+ // variants.ts
687
+ import { defineVariants } from '@svelte-atoms/core/utils';
688
+
689
+ export const buttonVariants = defineVariants({
690
+ class: 'rounded-md font-medium transition-colors',
691
+ variants: {
692
+ /* ... */
693
+ },
694
+ defaults: {
695
+ /* ... */
696
+ }
697
+ });
698
+
699
+ export const cardVariants = defineVariants({
700
+ class: 'rounded-lg border',
701
+ variants: {
702
+ /* ... */
703
+ },
704
+ defaults: {
705
+ /* ... */
706
+ }
707
+ });
708
+ ```
709
+
710
+ ### 2. Pass Variants Function to Components
711
+
712
+ Always pass the variant function (not the result) to `HtmlAtom`:
713
+
714
+ ```svelte
715
+ <script>
716
+ const variants = defineVariants({ ... });
717
+
718
+ let { variant, size, ...props } = $props();
719
+ </script>
720
+
721
+ <!-- Correct: Pass the function -->
722
+ <HtmlAtom {variants} {variant} {size} {...props}>
723
+ {@render children?.()}
724
+ </HtmlAtom>
725
+ ```
726
+
727
+ ### 3. Extend with Additional Classes
728
+
729
+ Merge variant classes with custom classes:
730
+
731
+ ```svelte
732
+ <script>
733
+ let { variant, size, class: klass = '', ...props } = $props();
734
+ </script>
735
+
736
+ <HtmlAtom {variants} {variant} {size} class={['custom-class', klass]} {...props}>
737
+ {@render children?.()}
738
+ </HtmlAtom>
739
+ ```
740
+
741
+ ### 4. Type Props from Variants
742
+
743
+ Extract variant types for component props:
744
+
745
+ ```typescript
746
+ import { VariantPropsType } from '@svelte-atoms/core/utils';
747
+ import { buttonVariants } from './variants';
748
+
749
+ type ButtonProps = VariantPropsType<typeof buttonVariants> & {
750
+ disabled?: boolean;
751
+ // other props...
752
+ };
753
+ ```
754
+
755
+ ## Creating Variants: Local vs Global
756
+
757
+ ### Local Variants (Component-Specific)
758
+
759
+ Create variants directly in your component file when they're only used in one place:
760
+
761
+ ```svelte
762
+ <!-- my-button.svelte -->
763
+ <script lang="ts">
764
+ import { HtmlAtom } from '@svelte-atoms/core';
765
+ import { defineVariants, type VariantPropsType } from '@svelte-atoms/core/utils';
766
+
767
+ // Define variants locally
768
+ const buttonVariants = defineVariants({
769
+ class: 'inline-flex items-center justify-center rounded-md font-medium',
770
+ variants: {
771
+ variant: {
772
+ primary: 'bg-blue-500 text-white hover:bg-blue-600',
773
+ secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
774
+ ghost: 'hover:bg-gray-100'
775
+ },
776
+ size: {
777
+ sm: 'h-8 px-3 text-sm',
778
+ md: 'h-10 px-4',
779
+ lg: 'h-12 px-6 text-lg'
780
+ }
781
+ },
782
+ compounds: [
783
+ {
784
+ variant: 'primary',
785
+ size: 'lg',
786
+ class: 'shadow-md font-semibold'
787
+ },
788
+ {
789
+ variant: 'secondary',
790
+ size: 'sm',
791
+ class: 'text-xs'
792
+ }
793
+ ],
794
+ defaults: {
795
+ variant: 'primary',
796
+ size: 'md'
797
+ }
798
+ });
799
+
800
+ // Extract variant prop types
801
+ type ButtonVariantProps = VariantPropsType<typeof buttonVariants>;
802
+
803
+ // Define component props extending variant props
804
+ type ButtonProps = ButtonVariantProps & {
805
+ disabled?: boolean;
806
+ onclick?: (e: MouseEvent) => void;
807
+ class?: string;
808
+ };
809
+
810
+ let { variant, size, disabled = false, class: klass = '', ...props }: ButtonProps = $props();
811
+
812
+ const variants = buttonVariants; // The variant function
813
+ </script>
814
+
815
+ <HtmlAtom {variants} as="button" {variant} {size} {disabled} class={klass} {...props}>
816
+ {@render children?.()}
817
+ </HtmlAtom>
818
+ ```
819
+
820
+ **Use local variants when:**
821
+
822
+ - The component is unique and won't be reused elsewhere
823
+ - You need quick prototyping
824
+ - Variants are tightly coupled to the component logic
825
+
826
+ ### Global Variants (Preset-Based)
827
+
828
+ Define variants globally in your theme/preset configuration for reusable components:
829
+
830
+ ```typescript
831
+ // +layout.svelte or theme.svelte
832
+ import { setPreset } from '@svelte-atoms/core/context';
833
+
834
+ setPreset({
835
+ // Global button variants
836
+ button: () => ({
837
+ class:
838
+ 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2',
839
+ variants: {
840
+ variant: {
841
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
842
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
843
+ outline: 'border border-input bg-background hover:bg-accent',
844
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
845
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
846
+ link: 'text-primary underline-offset-4 hover:underline'
847
+ },
848
+ size: {
849
+ default: 'h-10 px-4 py-2',
850
+ sm: 'h-9 px-3',
851
+ lg: 'h-11 px-8',
852
+ icon: 'h-10 w-10'
853
+ }
854
+ },
855
+ compounds: [
856
+ {
857
+ variant: 'default',
858
+ size: 'lg',
859
+ class: 'text-base font-semibold'
860
+ }
861
+ ],
862
+ defaults: {
863
+ variant: 'default',
864
+ size: 'default'
865
+ }
866
+ }),
867
+
868
+ // Global card variants
869
+ card: () => ({
870
+ class: 'rounded-lg border bg-card text-card-foreground shadow-sm',
871
+ variants: {
872
+ variant: {
873
+ default: 'border-border',
874
+ elevated: 'shadow-lg',
875
+ outlined: 'border-2'
876
+ }
877
+ },
878
+ defaults: {
879
+ variant: 'default'
880
+ }
881
+ })
882
+ });
883
+ ```
884
+
885
+ ```svelte
886
+ <!-- button.svelte - Consume global variants -->
887
+ <script lang="ts">
888
+ import { HtmlAtom } from '@svelte-atoms/core';
889
+
890
+ // Type-safe props based on preset
891
+ type ButtonProps = {
892
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
893
+ size?: 'default' | 'sm' | 'lg' | 'icon';
894
+ disabled?: boolean;
895
+ class?: string;
896
+ };
897
+
898
+ let {
899
+ variant,
900
+ size,
901
+ disabled = false,
902
+ class: klass = '',
903
+ ...props
904
+ }: ButtonProps = $props();
905
+ </script>
906
+
907
+ <HtmlAtom
908
+ preset="button" <!-- Uses global preset variants -->
909
+ as="button"
910
+ {variant}
911
+ {size}
912
+ {disabled}
913
+ class={klass}
914
+ {...props}
915
+ >
916
+ {@render children?.()}
917
+ </HtmlAtom>
918
+ ```
919
+
920
+ **Use global variants when:**
921
+
922
+ - Building a design system with consistent styling
923
+ - Components are used across multiple pages/features
924
+ - You want centralized theme control
925
+ - You need to support theme switching
926
+
927
+ ### Extending Global Variants Locally
928
+
929
+ Combine the best of both worlds - use global presets as a base and extend with local variants:
930
+
931
+ ```svelte
932
+ <!-- special-button.svelte -->
933
+ <script lang="ts">
934
+ import { HtmlAtom } from '@svelte-atoms/core';
935
+ import { defineVariants, type VariantPropsType } from '@svelte-atoms/core/utils';
936
+
937
+ // Local variants that extend the global preset
938
+ const extendedVariants = defineVariants({
939
+ variants: {
940
+ variant: {
941
+ // Add new variants not in preset
942
+ gradient: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:from-purple-600 hover:to-pink-600',
943
+ neon: 'bg-black text-green-400 border-2 border-green-400 hover:bg-green-400 hover:text-black',
944
+ glass: 'bg-white/10 backdrop-blur-lg border border-white/20 hover:bg-white/20'
945
+ },
946
+ // Add new variant dimension
947
+ animated: {
948
+ true: 'animate-pulse',
949
+ false: ''
950
+ },
951
+ shadow: {
952
+ none: '',
953
+ sm: 'shadow-sm',
954
+ md: 'shadow-md',
955
+ lg: 'shadow-lg'
956
+ }
957
+ },
958
+ defaults: {
959
+ animated: false,
960
+ shadow: 'none'
961
+ }
962
+ });
963
+
964
+ // Extract extended variant types
965
+ type ExtendedVariantProps = VariantPropsType<typeof extendedVariants>;
966
+
967
+ // Combine preset variants with extended variants
968
+ type SpecialButtonProps = {
969
+ // Preset variant types
970
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'gradient' | 'neon' | 'glass';
971
+ size?: 'default' | 'sm' | 'lg' | 'icon';
972
+ // Extended variant types
973
+ animated?: boolean;
974
+ shadow?: 'none' | 'sm' | 'md' | 'lg';
975
+ disabled?: boolean;
976
+ class?: string;
977
+ };
978
+
979
+ let {
980
+ variant,
981
+ size,
982
+ animated,
983
+ shadow,
984
+ disabled = false,
985
+ class: klass = '',
986
+ ...props
987
+ }: SpecialButtonProps = $props();
988
+ </script>
989
+
990
+ <HtmlAtom
991
+ preset="button" <!-- Gets base global variants -->
992
+ variants={extendedVariants} <!-- Extends with local variants -->
993
+ as="button"
994
+ {variant}
995
+ {size}
996
+ {animated}
997
+ {shadow}
998
+ {disabled}
999
+ class={klass}
1000
+ {...props}
1001
+ >
1002
+ {@render children?.()}
1003
+ </HtmlAtom>
1004
+ ```
1005
+
1006
+ ## Type-Safe Component Props with Variants
1007
+
1008
+ ### Method 1: Manual Type Definition (Preset-based)
1009
+
1010
+ When using presets, manually define the variant types based on your preset configuration:
1011
+
1012
+ ```svelte
1013
+ <script lang="ts">
1014
+ import { HtmlAtom } from '@svelte-atoms/core';
1015
+
1016
+ // Manually typed based on preset definition
1017
+ type ButtonProps = {
1018
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
1019
+ size?: 'default' | 'sm' | 'lg' | 'icon';
1020
+ disabled?: boolean;
1021
+ class?: string;
1022
+ onclick?: (e: MouseEvent) => void;
1023
+ };
1024
+
1025
+ let { variant, size, disabled = false, class: klass = '', ...props }: ButtonProps = $props();
1026
+ </script>
1027
+
1028
+ <HtmlAtom preset="button" as="button" {variant} {size} {disabled} class={klass} {...props}>
1029
+ {@render children?.()}
1030
+ </HtmlAtom>
1031
+ ```
1032
+
1033
+ ### Method 2: Extract Types from defineVariants
1034
+
1035
+ For local variants, use `VariantPropsType` to automatically extract types:
1036
+
1037
+ ```svelte
1038
+ <script lang="ts">
1039
+ import { HtmlAtom } from '@svelte-atoms/core';
1040
+ import { defineVariants, type VariantPropsType } from '@svelte-atoms/core/utils';
1041
+
1042
+ const buttonVariants = defineVariants({
1043
+ class: 'rounded-md',
1044
+ variants: {
1045
+ variant: {
1046
+ primary: 'bg-blue-500',
1047
+ secondary: 'bg-gray-500',
1048
+ danger: 'bg-red-500'
1049
+ },
1050
+ size: {
1051
+ sm: 'text-sm',
1052
+ md: 'text-base',
1053
+ lg: 'text-lg'
1054
+ },
1055
+ fullWidth: {
1056
+ true: 'w-full',
1057
+ false: 'w-auto'
1058
+ }
1059
+ },
1060
+ defaults: {
1061
+ variant: 'primary',
1062
+ size: 'md',
1063
+ fullWidth: false
1064
+ }
1065
+ });
1066
+
1067
+ // Automatically extract variant prop types
1068
+ type ButtonVariantProps = VariantPropsType<typeof buttonVariants>;
1069
+ // Result: { variant?: 'primary' | 'secondary' | 'danger'; size?: 'sm' | 'md' | 'lg'; fullWidth?: boolean }
1070
+
1071
+ // Extend with additional props
1072
+ type ButtonProps = ButtonVariantProps & {
1073
+ disabled?: boolean;
1074
+ onclick?: (e: MouseEvent) => void;
1075
+ class?: string;
1076
+ };
1077
+
1078
+ let {
1079
+ variant,
1080
+ size,
1081
+ fullWidth,
1082
+ disabled = false,
1083
+ class: klass = '',
1084
+ ...props
1085
+ }: ButtonProps = $props();
1086
+ </script>
1087
+
1088
+ <HtmlAtom {variants} as="button" {variant} {size} {fullWidth} {disabled} class={klass} {...props}>
1089
+ {@render children?.()}
1090
+ </HtmlAtom>
1091
+ ```
1092
+
1093
+ ### Method 3: Shared Variant Types
1094
+
1095
+ Create reusable variant type definitions:
1096
+
1097
+ ```typescript
1098
+ // types/button.ts
1099
+ import { defineVariants, type VariantPropsType } from '@svelte-atoms/core/utils';
1100
+
1101
+ export const buttonVariants = defineVariants({
1102
+ class: 'inline-flex items-center justify-center rounded-md font-medium',
1103
+ variants: {
1104
+ variant: {
1105
+ primary: 'bg-blue-500 text-white',
1106
+ secondary: 'bg-gray-200 text-gray-900',
1107
+ ghost: 'hover:bg-gray-100'
1108
+ },
1109
+ size: {
1110
+ sm: 'h-8 px-3 text-sm',
1111
+ md: 'h-10 px-4',
1112
+ lg: 'h-12 px-6 text-lg'
1113
+ }
1114
+ },
1115
+ defaults: {
1116
+ variant: 'primary',
1117
+ size: 'md'
1118
+ }
1119
+ });
1120
+
1121
+ // Export the variant types
1122
+ export type ButtonVariantProps = VariantPropsType<typeof buttonVariants>;
1123
+
1124
+ // Export full component props
1125
+ export type ButtonProps = ButtonVariantProps & {
1126
+ disabled?: boolean;
1127
+ onclick?: (e: MouseEvent) => void;
1128
+ class?: string;
1129
+ };
1130
+ ```
1131
+
1132
+ ```svelte
1133
+ <!-- button.svelte -->
1134
+ <script lang="ts">
1135
+ import { HtmlAtom } from '@svelte-atoms/core';
1136
+ import { buttonVariants, type ButtonProps } from './types/button';
1137
+
1138
+ const variants = buttonVariants; // The function from the shared file
1139
+
1140
+ let { variant, size, disabled = false, class: klass = '', ...props }: ButtonProps = $props();
1141
+ </script>
1142
+
1143
+ <HtmlAtom {variants} as="button" {variant} {size} {disabled} class={klass} {...props}>
1144
+ {@render children?.()}
1145
+ </HtmlAtom>
1146
+ ```
1147
+
1148
+ ### Method 4: Component with Bond State Types
1149
+
1150
+ For components using the Bond pattern:
1151
+
1152
+ ```typescript
1153
+ // accordion-item.svelte
1154
+ <script lang="ts">
1155
+ import { HtmlAtom } from '@svelte-atoms/core';
1156
+ import { defineVariants, type VariantPropsType } from '@svelte-atoms/core/utils';
1157
+ import { AccordionBond } from './bond.svelte';
1158
+
1159
+ const accordionVariants = defineVariants({
1160
+ class: 'border rounded-md',
1161
+ variants: {
1162
+ state: {
1163
+ open: (bond) => ({
1164
+ class: bond?.state?.isOpen ? 'bg-blue-50' : 'bg-white',
1165
+ 'aria-expanded': bond?.state?.isOpen
1166
+ })
1167
+ },
1168
+ bordered: {
1169
+ true: 'border-2',
1170
+ false: 'border'
1171
+ }
1172
+ },
1173
+ defaults: {
1174
+ bordered: false
1175
+ }
1176
+ });
1177
+
1178
+ // Extract variant types
1179
+ type AccordionVariantProps = VariantPropsType<typeof accordionVariants>;
1180
+
1181
+ // Extend with component-specific props
1182
+ type AccordionItemProps = Omit<AccordionVariantProps, 'state'> & {
1183
+ value: string;
1184
+ disabled?: boolean;
1185
+ class?: string;
1186
+ };
1187
+
1188
+ let {
1189
+ value,
1190
+ bordered,
1191
+ disabled = false,
1192
+ class: klass = '',
1193
+ ...props
1194
+ }: AccordionItemProps = $props();
1195
+
1196
+ const variants = accordionVariants; // The variant function
1197
+ </script>
1198
+
1199
+ <HtmlAtom {variants} {bordered} class={klass} {...props}>
1200
+ {@render children?.()}
1201
+ </HtmlAtom>
1202
+ ```
1203
+
1204
+ ### Best Practices for Typed Variants
1205
+
1206
+ 1. **Always extract types from defineVariants**
1207
+
1208
+ ```typescript
1209
+ const variants = defineVariants({...});
1210
+ type VariantProps = VariantPropsType<typeof variants>;
1211
+ ```
1212
+
1213
+ 2. **Extend variant types with component props**
1214
+
1215
+ ```typescript
1216
+ type ComponentProps = VariantProps & {
1217
+ disabled?: boolean;
1218
+ onclick?: () => void;
1219
+ };
1220
+ ```
1221
+
1222
+ 3. **Use Omit for bond-driven variants**
1223
+
1224
+ ```typescript
1225
+ // Remove 'state' from props since it's driven by bond
1226
+ type Props = Omit<VariantProps, 'state'> & { ... };
1227
+ ```
1228
+
1229
+ 4. **Share types across related components**
1230
+
1231
+ ```typescript
1232
+ // types/card.ts
1233
+ export type CardVariantProps = VariantPropsType<typeof cardVariants>;
1234
+ export type CardHeaderProps = { class?: string };
1235
+ export type CardBodyProps = { class?: string };
1236
+ ```
1237
+
1238
+ 5. **Document variant options in JSDoc**
1239
+ ```typescript
1240
+ /**
1241
+ * Button component with multiple variants
1242
+ * @param variant - Visual style: 'primary' | 'secondary' | 'ghost'
1243
+ * @param size - Size variant: 'sm' | 'md' | 'lg'
1244
+ */
1245
+ type ButtonProps = VariantPropsType<typeof buttonVariants> & {...};
1246
+ ```
1247
+
1248
+ ## Summary
1249
+
1250
+ `defineVariants()` provides:
1251
+
1252
+ ✅ **Single function** - One API for all variant needs
1253
+ ✅ **Type-safe** - Automatic TypeScript inference
1254
+ ✅ **Reactive** - Access bond state for dynamic styling
1255
+ ✅ **Powerful** - Base classes, compound variants, defaults
1256
+ ✅ **Flexible** - Return both classes and attributes
1257
+ ✅ **Clean** - No manual object merging or conditionals
1258
+ ✅ **Extensible** - Combine global presets with local variants
1259
+ ✅ **Type extraction** - Use `VariantPropsType` for automatic type inference
1260
+
1261
+ Inspired by Class Variance Authority but integrated with @svelte-atoms/core's bond system.