@once-ui-system/core 2.0.0-alpha.0 → 2.0.0-alpha.2

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 (162) hide show
  1. package/AGENTS.md +1 -1
  2. package/LICENSE.md +21 -0
  3. package/ai/catalog.json +11 -4
  4. package/ai/components/Effect.json +1 -1
  5. package/ai/components/Feedback.json +1 -0
  6. package/ai/components/Form.json +15 -0
  7. package/ai/components/Input.json +3 -2
  8. package/ai/components/{Particle.json → ParticleFx.json} +1 -1
  9. package/ai/components/ScrollContainer.json +9 -1
  10. package/ai/components/Textarea.json +3 -2
  11. package/ai/components/ThemeSwitcher.json +2 -1
  12. package/ai/eval-tasks.md +2 -1
  13. package/ai/examples/auth.tsx +1 -2
  14. package/ai/examples/blocks/Dashboard1.tsx +8 -8
  15. package/ai/examples/blocks/Feed3.tsx +4 -4
  16. package/ai/examples/blocks/Footer4.tsx +2 -2
  17. package/ai/examples/blocks/Header2.tsx +1 -1
  18. package/ai/examples/blocks/Header4.tsx +1 -2
  19. package/ai/examples/blocks/Productivity1.tsx +1 -1
  20. package/ai/examples/blocks/Roadmap1.tsx +3 -3
  21. package/ai/examples/blocks/Sidebar1.tsx +1 -1
  22. package/ai/examples/blocks/Sidebar2.tsx +1 -1
  23. package/ai/examples/blocks/Table1.tsx +3 -3
  24. package/ai/examples/blocks/manifest.json +2 -2
  25. package/ai/examples/dashboard.tsx +5 -5
  26. package/ai/examples/marketing-hero.tsx +2 -2
  27. package/ai/examples/pricing.tsx +3 -3
  28. package/ai/examples/settings.tsx +4 -4
  29. package/ai/gotchas.json +26 -2
  30. package/ai/manifest.json +2 -2
  31. package/ai/recipes.md +1 -1
  32. package/ai/rules.compact.md +8 -2
  33. package/ai/rules.md +18 -6
  34. package/ai/spec.json +35 -10
  35. package/ai/tasks/settings.json +1 -1
  36. package/dist/AGENTS.md +1 -1
  37. package/dist/ai/catalog.json +11 -4
  38. package/dist/ai/components/Effect.json +1 -1
  39. package/dist/ai/components/Feedback.json +1 -0
  40. package/dist/ai/components/Form.json +15 -0
  41. package/dist/ai/components/Input.json +3 -2
  42. package/dist/ai/components/{Particle.json → ParticleFx.json} +1 -1
  43. package/dist/ai/components/ScrollContainer.json +9 -1
  44. package/dist/ai/components/Textarea.json +3 -2
  45. package/dist/ai/components/ThemeSwitcher.json +2 -1
  46. package/dist/ai/eval-tasks.md +2 -1
  47. package/dist/ai/examples/blocks/manifest.json +2 -2
  48. package/dist/ai/gotchas.json +26 -2
  49. package/dist/ai/manifest.json +2 -2
  50. package/dist/ai/recipes.md +1 -1
  51. package/dist/ai/rules.compact.md +8 -2
  52. package/dist/ai/rules.md +18 -6
  53. package/dist/ai/spec.json +35 -10
  54. package/dist/ai/tasks/settings.json +1 -1
  55. package/dist/components/Animation.d.ts +1 -2
  56. package/dist/components/Animation.d.ts.map +1 -1
  57. package/dist/components/Animation.js +1 -1
  58. package/dist/components/Animation.js.map +1 -1
  59. package/dist/components/ArrowNavigationContext.d.ts.map +1 -1
  60. package/dist/components/ArrowNavigationContext.js +12 -3
  61. package/dist/components/ArrowNavigationContext.js.map +1 -1
  62. package/dist/components/Avatar.d.ts.map +1 -1
  63. package/dist/components/Avatar.js +1 -1
  64. package/dist/components/Avatar.js.map +1 -1
  65. package/dist/components/Carousel.js +1 -1
  66. package/dist/components/Carousel.js.map +1 -1
  67. package/dist/components/ContextMenu.d.ts +1 -1
  68. package/dist/components/ContextMenu.d.ts.map +1 -1
  69. package/dist/components/CountFx.d.ts.map +1 -1
  70. package/dist/components/CountFx.js +19 -7
  71. package/dist/components/CountFx.js.map +1 -1
  72. package/dist/components/CursorCard.d.ts +1 -1
  73. package/dist/components/CursorCard.d.ts.map +1 -1
  74. package/dist/components/DatePicker.d.ts.map +1 -1
  75. package/dist/components/DatePicker.js +9 -2
  76. package/dist/components/DatePicker.js.map +1 -1
  77. package/dist/components/DropdownWrapper.d.ts +1 -1
  78. package/dist/components/DropdownWrapper.d.ts.map +1 -1
  79. package/dist/components/DropdownWrapper.js +178 -35
  80. package/dist/components/DropdownWrapper.js.map +1 -1
  81. package/dist/components/DropdownWrapper.module.scss +3 -1
  82. package/dist/components/Effect.d.ts +2 -2
  83. package/dist/components/Effect.d.ts.map +1 -1
  84. package/dist/components/Effect.js +2 -2
  85. package/dist/components/Effect.js.map +1 -1
  86. package/dist/components/EmojiPicker.js +1 -1
  87. package/dist/components/EmojiPicker.js.map +1 -1
  88. package/dist/components/Feedback.d.ts +4 -0
  89. package/dist/components/Feedback.d.ts.map +1 -1
  90. package/dist/components/Feedback.js +2 -2
  91. package/dist/components/Feedback.js.map +1 -1
  92. package/dist/components/FocusTrap.d.ts +3 -3
  93. package/dist/components/FocusTrap.d.ts.map +1 -1
  94. package/dist/components/FocusTrap.js +10 -0
  95. package/dist/components/FocusTrap.js.map +1 -1
  96. package/dist/components/Form.d.ts +48 -0
  97. package/dist/components/Form.d.ts.map +1 -0
  98. package/dist/components/Form.js +191 -0
  99. package/dist/components/Form.js.map +1 -0
  100. package/dist/components/Form.module.scss +160 -0
  101. package/dist/components/FormContext.d.ts +20 -0
  102. package/dist/components/FormContext.d.ts.map +1 -0
  103. package/dist/components/FormContext.js +12 -0
  104. package/dist/components/FormContext.js.map +1 -0
  105. package/dist/components/Input.d.ts +3 -0
  106. package/dist/components/Input.d.ts.map +1 -1
  107. package/dist/components/Input.js +13 -2
  108. package/dist/components/Input.js.map +1 -1
  109. package/dist/components/Input.module.scss +27 -6
  110. package/dist/components/OgCard.d.ts.map +1 -1
  111. package/dist/components/OgCard.js +7 -4
  112. package/dist/components/OgCard.js.map +1 -1
  113. package/dist/components/{Particle.d.ts → ParticleFx.d.ts} +9 -4
  114. package/dist/components/ParticleFx.d.ts.map +1 -0
  115. package/dist/components/{Particle.js → ParticleFx.js} +9 -4
  116. package/dist/components/ParticleFx.js.map +1 -0
  117. package/dist/components/ScrollContainer.d.ts +69 -2
  118. package/dist/components/ScrollContainer.d.ts.map +1 -1
  119. package/dist/components/ScrollContainer.js +377 -46
  120. package/dist/components/ScrollContainer.js.map +1 -1
  121. package/dist/components/ScrollContainer.module.scss +131 -0
  122. package/dist/components/Select.d.ts +1 -1
  123. package/dist/components/Select.d.ts.map +1 -1
  124. package/dist/components/Select.js +1 -1
  125. package/dist/components/Select.js.map +1 -1
  126. package/dist/components/Setting.d.ts.map +1 -1
  127. package/dist/components/Setting.js +6 -1
  128. package/dist/components/Setting.js.map +1 -1
  129. package/dist/components/Table.js +2 -2
  130. package/dist/components/Table.js.map +1 -1
  131. package/dist/components/Textarea.d.ts +3 -0
  132. package/dist/components/Textarea.d.ts.map +1 -1
  133. package/dist/components/Textarea.js +22 -6
  134. package/dist/components/Textarea.js.map +1 -1
  135. package/dist/components/ThemeSwitcher.d.ts +16 -0
  136. package/dist/components/ThemeSwitcher.d.ts.map +1 -1
  137. package/dist/components/ThemeSwitcher.js +48 -9
  138. package/dist/components/ThemeSwitcher.js.map +1 -1
  139. package/dist/components/ThemeSwitcher.module.scss +112 -0
  140. package/dist/components/User.js +1 -1
  141. package/dist/components/User.js.map +1 -1
  142. package/dist/components/UserMenu.d.ts +1 -1
  143. package/dist/components/UserMenu.d.ts.map +1 -1
  144. package/dist/components/index.d.ts +2 -1
  145. package/dist/components/index.d.ts.map +1 -1
  146. package/dist/components/index.js +2 -1
  147. package/dist/components/index.js.map +1 -1
  148. package/dist/data/emoji-data.json +10 -0
  149. package/dist/hooks/useArrowNavigation.d.ts.map +1 -1
  150. package/dist/hooks/useArrowNavigation.js +52 -7
  151. package/dist/hooks/useArrowNavigation.js.map +1 -1
  152. package/dist/modules/navigation/MegaMenu.d.ts.map +1 -1
  153. package/dist/modules/navigation/MegaMenu.js +76 -10
  154. package/dist/modules/navigation/MegaMenu.js.map +1 -1
  155. package/dist/package.json +1 -1
  156. package/dist/scripts/validate-ai-code.js +239 -21
  157. package/dist/types.d.ts +14 -0
  158. package/dist/types.d.ts.map +1 -1
  159. package/package.json +1 -1
  160. package/scripts/validate-ai-code.js +239 -21
  161. package/dist/components/Particle.d.ts.map +0 -1
  162. package/dist/components/Particle.js.map +0 -1
package/ai/recipes.md CHANGED
@@ -87,7 +87,7 @@ For the emphasized item in a set (e.g. "most popular" pricing tier). Border + to
87
87
  Self-contained drifting brand/accent blobs (already blurred and pointer-transparent). Use behind a final CTA or feature visual — counts as continuous motion:
88
88
 
89
89
  ```tsx
90
- <Card fillWidth radius="l" border="neutral-alpha-weak" overflow="hidden" padding="48">
90
+ <Card fillWidth radius="l" border overflow="hidden" padding="48">
91
91
  <BlobFx position="absolute" top="0" left="0" fill opacity={60} seed={2} />
92
92
  <Column zIndex={1} center gap="24">
93
93
  {/* CTA content */}
@@ -11,6 +11,9 @@ Load with task bundle from `@once-ui-system/core/ai/tasks/{intent}.json`. For an
11
11
  | `fill` | `fillWidth fillHeight` |
12
12
  | `fit` | `fitWidth fitHeight` |
13
13
  | `center` | `horizontal="center" vertical="center"` |
14
+ | `maxWidth="l"` | `fillWidth maxWidth="l"` |
15
+ | `fillWidth` | `fillWidth minWidth={0}` |
16
+ | `border` | `border="neutral-alpha-weak"` |
14
17
 
15
18
  - `Flex` only when direction changes at breakpoints: `<Row s={{ direction: "column" }}>`
16
19
  - Page skeleton: `Column as="main" fillWidth horizontal="center"` → content `Column maxWidth="l"` → sections
@@ -22,9 +25,10 @@ Load with task bundle from `@once-ui-system/core/ai/tasks/{intent}.json`. For an
22
25
  Static panel recipe (pricing tiers, stats, settings sections):
23
26
 
24
27
  ```tsx
25
- <Column background="surface" border="neutral-alpha-weak" radius="l" padding="24" gap="16">
28
+ <Column background="surface" border radius="l" padding="24" gap="16">
26
29
  ```
27
30
 
31
+ - Bare `border` (and `borderTop`, `borderBottom`, …) = the default border. Name a colour only to deviate: `border="brand-alpha-medium"`.
28
32
  - `Card` = interactive only (`href` or `onClick`). Static panels = Column + surface recipe.
29
33
  - Colors: `{scheme}-{weight}` or `{scheme}-alpha-{weight}`. Never hex/rgb.
30
34
  - `background` also: `surface | overlay | page | transparent`
@@ -40,6 +44,8 @@ Static panel recipe (pricing tiers, stats, settings sections):
40
44
 
41
45
  Absolute layers always: `position="absolute" top="0" left="0" fill pointerEvents="none"`, content `zIndex={1}`.
42
46
 
47
+ - `zIndex` only inside that layer group (or on a sticky header over its pane). Never on a lone element, never a page-wide ladder.
48
+
43
49
  - Gradient `width`: quarter-percent (400 = 100%). Glows: 100–200.
44
50
  - Glow colors: `brand-alpha-medium`, not `brand-background-weak`
45
51
  - Ambient layers on full-bleed section wrapper, outside `maxWidth` column
@@ -81,7 +87,7 @@ from `spec.json`.
81
87
 
82
88
  ## Defaults — omit these
83
89
 
84
- `position="relative"`, `direction` on Row/Column, `variant="primary"` on Button, `size="m"` when default.
90
+ `position="relative"`, `direction` on Row/Column, `variant="primary"` on Button, `size="m"` when default, `fillWidth` beside `maxWidth`, `minWidth={0}` beside `fillWidth`, a border colour that restates the default.
85
91
 
86
92
  ## Before building
87
93
 
package/ai/rules.md CHANGED
@@ -40,10 +40,22 @@ If you can't ask (or the user says "you decide"), default to: restrained, center
40
40
  <Row fillWidth fillHeight horizontal="center" vertical="center">...</Row>
41
41
  ```
42
42
 
43
- 3. Never write a prop that equals its default. The spec marks defaults with `= value`. Common offenders:
43
+ 3. Never write a prop that equals its default, or one that another prop on the same element already implies. The spec marks defaults with `= value`. Common offenders:
44
44
  - `position="relative"` (default on all layout components)
45
45
  - `direction="row"` on `Row`, `direction="column"` on `Column`
46
46
  - `variant="primary"`, `size="m"` on `Button`
47
+ - `fillWidth` beside `maxWidth` — `maxWidth` already fills the width up to the cap
48
+ - `minWidth={0}` beside `fillWidth` — `fillWidth` already sets `min-width: 0`
49
+ - a colour on `border` (or `borderTop`, `borderBottom`, `borderX`, …) that only restates the default — bare `border` draws the default border; name a colour only to deviate from it
50
+
51
+ ```tsx
52
+ // Good
53
+ <Column maxWidth="l" border radius="l">
54
+ // Bad
55
+ <Column fillWidth maxWidth="l" minWidth={0} position="relative" border="neutral-alpha-weak" radius="l">
56
+ ```
57
+
58
+ 3b. `zIndex` belongs to a stacking group, not to a page. Use it only inside an isolated group that needs an order — a decorative absolute layer and the content sibling above it (rule 21), a sticky header over the pane it covers — and keep the values small within that group. A `zIndex` on a lone element, or a page-wide ladder of `zIndex={3}`, `{5}`, `{9}`, means the hierarchy is being managed by hand; restructure the tree instead.
47
59
 
48
60
  ## Spacing and sizing
49
61
 
@@ -66,10 +78,10 @@ If you can't ask (or the user says "you decide"), default to: restrained, center
66
78
  - `background` also accepts `surface | overlay | page | transparent`
67
79
  - `onBackground` colors text/icons on a background
68
80
 
69
- 8. Standard surface recipe: `background="surface"` (or `"overlay"` over imagery) + `border="neutral-alpha-weak"` (or `-medium`) + `radius="l"`.
81
+ 8. Standard surface recipe: `background="surface"` (or `"overlay"` over imagery) + `border` + `radius="l"`. Bare `border` is the default border; name a colour (`border="neutral-alpha-medium"`, `border="brand-alpha-medium"`) only when the surface should differ from it.
70
82
 
71
83
  ```tsx
72
- <Column background="surface" border="neutral-alpha-weak" radius="l" padding="24" gap="16">
84
+ <Column background="surface" border radius="l" padding="24" gap="16">
73
85
  ```
74
86
 
75
87
  ## Text
@@ -105,7 +117,7 @@ If you can't ask (or the user says "you decide"), default to: restrained, center
105
117
 
106
118
  13. Use the distinctive features when the design calls for them — they exist and are cheap:
107
119
  - `Dialog` supports `stack` (stacked dialogs) and `base` props
108
- - Effects: `RevealFx`, `LetterFx`, `TypeFx`, `CountFx`, `GlitchFx`, `HoloFx`, `TiltFx`, `FlipFx`, `MatrixFx`, `BlobFx`, `Particle`, `ShineFx`, `CelebrationFx`
120
+ - Effects: `RevealFx`, `LetterFx`, `TypeFx`, `CountFx`, `GlitchFx`, `HoloFx`, `TiltFx`, `FlipFx`, `MatrixFx`, `BlobFx`, `ParticleFx`, `ShineFx`, `CelebrationFx`
109
121
  - `CursorCard`, `HoverCard`, `ContextMenu`, `EmojiPicker`, `CompareImage`, `OgCard`, `Timeline`, `MasonryGrid`, `InfiniteScroll`, `SegmentedControl`, `Kbar` (command palette)
110
122
  - Forms: `Input`, `PasswordInput`, `OTPInput`, `NumberInput`, `TagInput`, `Select`, `DatePicker`, `DateRangePicker`, `ColorInput`, `Slider`, `Switch`, `Checkbox`, `Chip`
111
123
 
@@ -183,7 +195,7 @@ the card it was meant to support.
183
195
  <Grid columns={5} s={{ columns: 3 }} gap="8" fillWidth>
184
196
  {items.map(i => (
185
197
  <Column key={i} aspectRatio="1 / 1" padding="12" radius="l"
186
- background="page" border="neutral-alpha-weak" vertical="between">
198
+ background="page" border vertical="between">
187
199
  {/* a preview of the thing, not just its name */}
188
200
  </Column>
189
201
  ))}
@@ -204,7 +216,7 @@ in that order.
204
216
  <Background position="absolute" top="0" left="0" fill pointerEvents="none" ... />
205
217
  ```
206
218
 
207
- Siblings holding content get `zIndex={1}`. `overflow="hidden"` goes on the parent **only when it has a radius** (cards, panels) — clipping a full-bleed section turns the glow into a hard-edged band.
219
+ Siblings holding content get `zIndex={1}`. That layer and its content sibling are the whole stacking group — nothing else in the section needs a `zIndex` (rule 3b). `overflow="hidden"` goes on the parent **only when it has a radius** (cards, panels) — clipping a full-bleed section turns the glow into a hard-edged band.
208
220
 
209
221
  21b. Ambient section layers (hero glows) live on the **full-width section wrapper**, outside the `maxWidth` content column, with generous room (`paddingY="80"`+). Inside a narrow container the gradient gets clipped at the container edge and reads as a misplaced card:
210
222
 
package/ai/spec.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@once-ui-system/core",
3
- "version": "2.0.0-alpha.0",
4
- "generated": "2026-09-16",
3
+ "version": "2.0.0-alpha.2",
4
+ "generated": "2026-09-23",
5
5
  "format": "props are 'type', 'type = default', or '!type' (required). Components with 'mixins' also accept all props of those mixins. 'extends' means all props of that component are accepted. In 'types', an array lists a union's allowed values, an object gives a shape's fields in the same notation as props, and a string is the type as written — usually pointing at a component whose props are already listed.",
6
6
  "tokens": {
7
7
  "StaticSpacingToken": "\"0\" | \"1\" | \"2\" | \"4\" | \"8\" | \"12\" | \"16\" | \"20\" | \"24\" | \"32\" | \"40\" | \"48\" | \"56\" | \"64\" | \"72\" | \"80\" | \"104\" | \"128\" | \"160\"",
@@ -1180,6 +1180,7 @@
1180
1180
  "props": {
1181
1181
  "variant": "\"info\" | \"danger\" | \"warning\" | \"success\" = info",
1182
1182
  "showIcon": "boolean = true",
1183
+ "icon": "IconName",
1183
1184
  "title": "string",
1184
1185
  "description": "string",
1185
1186
  "showCloseButton": "boolean = false",
@@ -1233,6 +1234,19 @@
1233
1234
  "style": "React.CSSProperties"
1234
1235
  }
1235
1236
  },
1237
+ "Form": {
1238
+ "group": "components",
1239
+ "props": {
1240
+ "density": "\"stacked\" | \"tight\" | \"spacious\" = tight",
1241
+ "columns": "FormColumns",
1242
+ "size": "TShirtSizes",
1243
+ "as": "\"div\" | \"form\" = div",
1244
+ "onSubmit": "React.FormEventHandler<HTMLFormElement>",
1245
+ "className": "string",
1246
+ "style": "React.CSSProperties",
1247
+ "children": "React.ReactNode"
1248
+ }
1249
+ },
1236
1250
  "GlitchFx": {
1237
1251
  "group": "components",
1238
1252
  "extends": [
@@ -1436,7 +1450,7 @@
1436
1450
  "id": "!string",
1437
1451
  "label": "string",
1438
1452
  "placeholder": "string",
1439
- "size": "TShirtSizes = m",
1453
+ "size": "TShirtSizes",
1440
1454
  "error": "boolean = false",
1441
1455
  "errorMessage": "ReactNode",
1442
1456
  "description": "ReactNode",
@@ -1450,7 +1464,8 @@
1450
1464
  "characterCount": "boolean",
1451
1465
  "cursor": "undefined | \"interactive\"",
1452
1466
  "validate": "(value: ReactNode) => ReactNode | null",
1453
- "loading": "boolean = false"
1467
+ "loading": "boolean = false",
1468
+ "span": "number"
1454
1469
  }
1455
1470
  },
1456
1471
  "InteractiveDetails": {
@@ -1782,7 +1797,7 @@
1782
1797
  "autoFocus": "boolean = false"
1783
1798
  }
1784
1799
  },
1785
- "Particle": {
1800
+ "ParticleFx": {
1786
1801
  "group": "components",
1787
1802
  "extends": [
1788
1803
  "Flex"
@@ -1960,7 +1975,15 @@
1960
1975
  ],
1961
1976
  "props": {
1962
1977
  "items": "!React.ReactNode[]",
1963
- "controlPlacement": "\"top-start\" | \"top-center\" | \"top-end\" | \"top-between\" | \"bottom-start\" | \"bottom-center\" | \"bottom-end\" | \"bottom-between\" = top-start"
1978
+ "controlPlacement": "ScrollContainerControlPlacement = top-start",
1979
+ "infinite": "boolean = false",
1980
+ "markers": "boolean | \"top\" | \"bottom\" = false",
1981
+ "draggable": "boolean = true",
1982
+ "step": "number = 1",
1983
+ "clip": "boolean = false",
1984
+ "proximity": "boolean | number = false",
1985
+ "active": "number",
1986
+ "onActiveChange": "(index: number) => void"
1964
1987
  }
1965
1988
  },
1966
1989
  "ScrollToTop": {
@@ -2295,7 +2318,7 @@
2295
2318
  "label": "string",
2296
2319
  "placeholder": "string",
2297
2320
  "lines": "number | \"auto\" = auto",
2298
- "size": "TShirtSizes = m",
2321
+ "size": "TShirtSizes",
2299
2322
  "error": "boolean = false",
2300
2323
  "errorMessage": "ReactNode",
2301
2324
  "description": "ReactNode",
@@ -2308,7 +2331,8 @@
2308
2331
  "characterCount": "boolean",
2309
2332
  "resize": "\"horizontal\" | \"vertical\" | \"both\" | \"none\" = vertical",
2310
2333
  "validate": "(value: ReactNode) => ReactNode | null",
2311
- "disabled": "boolean = false"
2334
+ "disabled": "boolean = false",
2335
+ "span": "number"
2312
2336
  }
2313
2337
  },
2314
2338
  "TiltFx": {
@@ -2445,7 +2469,8 @@
2445
2469
  "StyleProps"
2446
2470
  ],
2447
2471
  "props": {
2448
- "collapsed": "boolean"
2472
+ "collapsed": "boolean",
2473
+ "direction": "\"row\" | \"column\" = row"
2449
2474
  }
2450
2475
  },
2451
2476
  "User": {
@@ -2521,7 +2546,7 @@
2521
2546
  "blob": "Omit<React.ComponentProps<typeof BlobFx>, \"children\">",
2522
2547
  "matrix": "Omit<React.ComponentProps<typeof MatrixFx>, \"children\">",
2523
2548
  "weather": "Omit<React.ComponentProps<typeof WeatherFx>, \"children\">",
2524
- "particle": "Omit<React.ComponentProps<typeof Particle>, \"children\">",
2549
+ "particle": "Omit<React.ComponentProps<typeof ParticleFx>, \"children\">",
2525
2550
  "celebration": "Omit<React.ComponentProps<typeof CelebrationFx>, \"children\">",
2526
2551
  "children": "React.ReactNode"
2527
2552
  }
@@ -28,7 +28,7 @@
28
28
  "layout.defaults"
29
29
  ],
30
30
  "quality": {
31
- "surfaceRecipe": "background=\"surface\" border=\"neutral-alpha-weak\" radius=\"l\" padding=\"24\"",
31
+ "surfaceRecipe": "background=\"surface\" border radius=\"l\" padding=\"24\"",
32
32
  "dangerZone": "border=\"danger-alpha-medium\" on danger actions only"
33
33
  }
34
34
  }
package/dist/AGENTS.md CHANGED
@@ -47,7 +47,7 @@ If you cannot read `node_modules`, use the docs host (may not match your install
47
47
 
48
48
  - Use `Column` / `Row`, not `Flex` unless direction changes at breakpoints
49
49
  - Use `fill`, not `fillWidth fillHeight`; use `center`, not `horizontal="center" vertical="center"`
50
- - Static panels: `Column background="surface" border="neutral-alpha-weak" radius="l" padding="24"`
50
+ - Static panels: `Column background="surface" border radius="l" padding="24"`
51
51
  - `Card` is interactive only (`href` or `onClick`)
52
52
 
53
53
  Full rules: `@once-ui-system/core/ai/rules.compact.md`
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "2.0.0-alpha.0",
3
- "generated": "2026-09-16",
2
+ "version": "2.0.0-alpha.2",
3
+ "generated": "2026-09-23",
4
4
  "components": {
5
5
  "Accordion": {
6
6
  "group": "components",
@@ -430,6 +430,13 @@
430
430
  "components"
431
431
  ]
432
432
  },
433
+ "Form": {
434
+ "group": "components",
435
+ "purpose": "Form component",
436
+ "tags": [
437
+ "components"
438
+ ]
439
+ },
433
440
  "GlitchFx": {
434
441
  "group": "components",
435
442
  "extends": [
@@ -683,12 +690,12 @@
683
690
  "components"
684
691
  ]
685
692
  },
686
- "Particle": {
693
+ "ParticleFx": {
687
694
  "group": "components",
688
695
  "extends": [
689
696
  "Flex"
690
697
  ],
691
- "purpose": "Particle component",
698
+ "purpose": "ParticleFx component",
692
699
  "tags": [
693
700
  "components"
694
701
  ]
@@ -20,7 +20,7 @@
20
20
  "blob": "Omit<React.ComponentProps<typeof BlobFx>, \"children\">",
21
21
  "matrix": "Omit<React.ComponentProps<typeof MatrixFx>, \"children\">",
22
22
  "weather": "Omit<React.ComponentProps<typeof WeatherFx>, \"children\">",
23
- "particle": "Omit<React.ComponentProps<typeof Particle>, \"children\">",
23
+ "particle": "Omit<React.ComponentProps<typeof ParticleFx>, \"children\">",
24
24
  "celebration": "Omit<React.ComponentProps<typeof CelebrationFx>, \"children\">",
25
25
  "children": "React.ReactNode"
26
26
  },
@@ -15,6 +15,7 @@
15
15
  "props": {
16
16
  "variant": "\"info\" | \"danger\" | \"warning\" | \"success\" = info",
17
17
  "showIcon": "boolean = true",
18
+ "icon": "IconName",
18
19
  "title": "string",
19
20
  "description": "string",
20
21
  "showCloseButton": "boolean = false",
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "Form",
3
+ "group": "components",
4
+ "props": {
5
+ "density": "\"stacked\" | \"tight\" | \"spacious\" = tight",
6
+ "columns": "FormColumns",
7
+ "size": "TShirtSizes",
8
+ "as": "\"div\" | \"form\" = div",
9
+ "onSubmit": "React.FormEventHandler<HTMLFormElement>",
10
+ "className": "string",
11
+ "style": "React.CSSProperties",
12
+ "children": "React.ReactNode"
13
+ },
14
+ "mixinsRef": []
15
+ }
@@ -5,7 +5,7 @@
5
5
  "id": "!string",
6
6
  "label": "string",
7
7
  "placeholder": "string",
8
- "size": "TShirtSizes = m",
8
+ "size": "TShirtSizes",
9
9
  "error": "boolean = false",
10
10
  "errorMessage": "ReactNode",
11
11
  "description": "ReactNode",
@@ -19,7 +19,8 @@
19
19
  "characterCount": "boolean",
20
20
  "cursor": "undefined | \"interactive\"",
21
21
  "validate": "(value: ReactNode) => ReactNode | null",
22
- "loading": "boolean = false"
22
+ "loading": "boolean = false",
23
+ "span": "number"
23
24
  },
24
25
  "mixinsRef": []
25
26
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "Particle",
2
+ "name": "ParticleFx",
3
3
  "group": "components",
4
4
  "extends": [
5
5
  "Flex"
@@ -15,7 +15,15 @@
15
15
  ],
16
16
  "props": {
17
17
  "items": "!React.ReactNode[]",
18
- "controlPlacement": "\"top-start\" | \"top-center\" | \"top-end\" | \"top-between\" | \"bottom-start\" | \"bottom-center\" | \"bottom-end\" | \"bottom-between\" = top-start"
18
+ "controlPlacement": "ScrollContainerControlPlacement = top-start",
19
+ "infinite": "boolean = false",
20
+ "markers": "boolean | \"top\" | \"bottom\" = false",
21
+ "draggable": "boolean = true",
22
+ "step": "number = 1",
23
+ "clip": "boolean = false",
24
+ "proximity": "boolean | number = false",
25
+ "active": "number",
26
+ "onActiveChange": "(index: number) => void"
19
27
  },
20
28
  "mixinsRef": [
21
29
  "CommonProps",
@@ -6,7 +6,7 @@
6
6
  "label": "string",
7
7
  "placeholder": "string",
8
8
  "lines": "number | \"auto\" = auto",
9
- "size": "TShirtSizes = m",
9
+ "size": "TShirtSizes",
10
10
  "error": "boolean = false",
11
11
  "errorMessage": "ReactNode",
12
12
  "description": "ReactNode",
@@ -19,7 +19,8 @@
19
19
  "characterCount": "boolean",
20
20
  "resize": "\"horizontal\" | \"vertical\" | \"both\" | \"none\" = vertical",
21
21
  "validate": "(value: ReactNode) => ReactNode | null",
22
- "disabled": "boolean = false"
22
+ "disabled": "boolean = false",
23
+ "span": "number"
23
24
  },
24
25
  "mixinsRef": []
25
26
  }
@@ -14,7 +14,8 @@
14
14
  "StyleProps"
15
15
  ],
16
16
  "props": {
17
- "collapsed": "boolean"
17
+ "collapsed": "boolean",
18
+ "direction": "\"row\" | \"column\" = row"
18
19
  },
19
20
  "mixinsRef": [
20
21
  "CommonProps",
@@ -25,7 +25,8 @@ is found in the wild.
25
25
  Mechanical (count occurrences):
26
26
 
27
27
  - [ ] Compiles / no invented props or components
28
- - [ ] Redundant-default props (`position="relative"`, `variant="primary"` on Button, ...) — target: 0
28
+ - [ ] Redundant-default props (`position="relative"`, `variant="primary"` on Button, `fillWidth` beside `maxWidth`, `minWidth={0}` beside `fillWidth`, `border="neutral-alpha-weak"` for the default border, ...) — target: 0
29
+ - [ ] `zIndex` outside an isolated stacking group (nothing absolute/fixed/sticky nearby) — target: 0
29
30
  - [ ] Shorthand violations (`fillWidth fillHeight` instead of `fill`, `horizontal+vertical center` instead of `center`, `Flex direction=` instead of `Row`/`Column`) — target: 0
30
31
  - [ ] `style={{}}` uses where a token prop exists — target: 0
31
32
  - [ ] Raw HTML elements where a component exists (`img`, `a`, `button`) — target: 0
@@ -69,9 +69,9 @@
69
69
  {
70
70
  "id": "Footer4",
71
71
  "file": "Footer4.tsx",
72
- "title": "Premium footer with TypeFx + Particle",
72
+ "title": "Premium footer with TypeFx + ParticleFx",
73
73
  "tags": ["footer", "marketing", "effect"],
74
- "components": ["Background", "Particle", "TypeFx", "Mask", "Avatar", "Tag"],
74
+ "components": ["Background", "ParticleFx", "TypeFx", "Mask", "Avatar", "Tag"],
75
75
  "patterns": ["animated wordmark", "particle decoration", "masked gradient"],
76
76
  "tasks": ["marketing-hero"],
77
77
  "standalone": true
@@ -20,7 +20,7 @@
20
20
  ]
21
21
  },
22
22
  "Card.interactive": {
23
- "rule": "Card is for interactive surfaces only (href or onClick). Static panels use Column + background=\"surface\" border=\"neutral-alpha-weak\" radius=\"l\".",
23
+ "rule": "Card is for interactive surfaces only (href or onClick). Static panels use Column + background=\"surface\" border radius=\"l\".",
24
24
  "components": [
25
25
  "Card"
26
26
  ]
@@ -76,6 +76,13 @@
76
76
  "Tag"
77
77
  ]
78
78
  },
79
+ "Timeline.notScrubber": {
80
+ "rule": "Timeline lays a sequence of steps down the page. For a video or audio editor's timeline — a playhead over stacked tracks of movable, trimmable blocks — use Scrubber. Do not build one out of Flex and absolute positioning.",
81
+ "components": [
82
+ "Timeline",
83
+ "Scrubber"
84
+ ]
85
+ },
79
86
  "color.tokens": {
80
87
  "rule": "Colors are semantic tokens ({scheme}-{weight}), never hex/rgb. background also accepts surface | overlay | page | transparent.",
81
88
  "components": [
@@ -101,13 +108,30 @@
101
108
  ]
102
109
  },
103
110
  "layout.defaults": {
104
- "rule": "Never write props equal to defaults: position=\"relative\", direction on Row/Column, variant=\"primary\" on Button.",
111
+ "rule": "Never write props equal to defaults or implied by another prop on the element: position=\"relative\", direction on Row/Column, variant=\"primary\" on Button, fillWidth beside maxWidth (maxWidth already fills the width), minWidth={0} beside fillWidth (fillWidth already sets min-width: 0).",
105
112
  "components": [
106
113
  "Row",
107
114
  "Column",
115
+ "Grid",
108
116
  "Button"
109
117
  ]
110
118
  },
119
+ "border.default": {
120
+ "rule": "Bare border (and borderTop, borderBottom, borderX, …) draws the default border. Do not write border=\"neutral-alpha-weak\" to get it; name a colour only to deviate, e.g. border=\"brand-alpha-medium\".",
121
+ "components": [
122
+ "Row",
123
+ "Column",
124
+ "Card"
125
+ ]
126
+ },
127
+ "zIndex.scope": {
128
+ "rule": "zIndex is for an isolated stacking group: an absolute decorative layer and its content sibling (zIndex={1}), or a sticky header over its pane. Never on a lone element, never a page-wide ladder of 3 / 5 / 9 — restructure the tree instead.",
129
+ "components": [
130
+ "Row",
131
+ "Column",
132
+ "Background"
133
+ ]
134
+ },
111
135
  "section.gaps": {
112
136
  "rule": "Gap between page sections: 80–160 (typical 104). Inside sections: 24–40.",
113
137
  "components": [
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "2.0.0-alpha.0",
3
- "generated": "2026-09-16",
2
+ "version": "2.0.0-alpha.2",
3
+ "generated": "2026-09-23",
4
4
  "files": {
5
5
  "spec": "spec.json",
6
6
  "catalog": "catalog.json",
@@ -87,7 +87,7 @@ For the emphasized item in a set (e.g. "most popular" pricing tier). Border + to
87
87
  Self-contained drifting brand/accent blobs (already blurred and pointer-transparent). Use behind a final CTA or feature visual — counts as continuous motion:
88
88
 
89
89
  ```tsx
90
- <Card fillWidth radius="l" border="neutral-alpha-weak" overflow="hidden" padding="48">
90
+ <Card fillWidth radius="l" border overflow="hidden" padding="48">
91
91
  <BlobFx position="absolute" top="0" left="0" fill opacity={60} seed={2} />
92
92
  <Column zIndex={1} center gap="24">
93
93
  {/* CTA content */}
@@ -11,6 +11,9 @@ Load with task bundle from `@once-ui-system/core/ai/tasks/{intent}.json`. For an
11
11
  | `fill` | `fillWidth fillHeight` |
12
12
  | `fit` | `fitWidth fitHeight` |
13
13
  | `center` | `horizontal="center" vertical="center"` |
14
+ | `maxWidth="l"` | `fillWidth maxWidth="l"` |
15
+ | `fillWidth` | `fillWidth minWidth={0}` |
16
+ | `border` | `border="neutral-alpha-weak"` |
14
17
 
15
18
  - `Flex` only when direction changes at breakpoints: `<Row s={{ direction: "column" }}>`
16
19
  - Page skeleton: `Column as="main" fillWidth horizontal="center"` → content `Column maxWidth="l"` → sections
@@ -22,9 +25,10 @@ Load with task bundle from `@once-ui-system/core/ai/tasks/{intent}.json`. For an
22
25
  Static panel recipe (pricing tiers, stats, settings sections):
23
26
 
24
27
  ```tsx
25
- <Column background="surface" border="neutral-alpha-weak" radius="l" padding="24" gap="16">
28
+ <Column background="surface" border radius="l" padding="24" gap="16">
26
29
  ```
27
30
 
31
+ - Bare `border` (and `borderTop`, `borderBottom`, …) = the default border. Name a colour only to deviate: `border="brand-alpha-medium"`.
28
32
  - `Card` = interactive only (`href` or `onClick`). Static panels = Column + surface recipe.
29
33
  - Colors: `{scheme}-{weight}` or `{scheme}-alpha-{weight}`. Never hex/rgb.
30
34
  - `background` also: `surface | overlay | page | transparent`
@@ -40,6 +44,8 @@ Static panel recipe (pricing tiers, stats, settings sections):
40
44
 
41
45
  Absolute layers always: `position="absolute" top="0" left="0" fill pointerEvents="none"`, content `zIndex={1}`.
42
46
 
47
+ - `zIndex` only inside that layer group (or on a sticky header over its pane). Never on a lone element, never a page-wide ladder.
48
+
43
49
  - Gradient `width`: quarter-percent (400 = 100%). Glows: 100–200.
44
50
  - Glow colors: `brand-alpha-medium`, not `brand-background-weak`
45
51
  - Ambient layers on full-bleed section wrapper, outside `maxWidth` column
@@ -81,7 +87,7 @@ from `spec.json`.
81
87
 
82
88
  ## Defaults — omit these
83
89
 
84
- `position="relative"`, `direction` on Row/Column, `variant="primary"` on Button, `size="m"` when default.
90
+ `position="relative"`, `direction` on Row/Column, `variant="primary"` on Button, `size="m"` when default, `fillWidth` beside `maxWidth`, `minWidth={0}` beside `fillWidth`, a border colour that restates the default.
85
91
 
86
92
  ## Before building
87
93
 
package/dist/ai/rules.md CHANGED
@@ -40,10 +40,22 @@ If you can't ask (or the user says "you decide"), default to: restrained, center
40
40
  <Row fillWidth fillHeight horizontal="center" vertical="center">...</Row>
41
41
  ```
42
42
 
43
- 3. Never write a prop that equals its default. The spec marks defaults with `= value`. Common offenders:
43
+ 3. Never write a prop that equals its default, or one that another prop on the same element already implies. The spec marks defaults with `= value`. Common offenders:
44
44
  - `position="relative"` (default on all layout components)
45
45
  - `direction="row"` on `Row`, `direction="column"` on `Column`
46
46
  - `variant="primary"`, `size="m"` on `Button`
47
+ - `fillWidth` beside `maxWidth` — `maxWidth` already fills the width up to the cap
48
+ - `minWidth={0}` beside `fillWidth` — `fillWidth` already sets `min-width: 0`
49
+ - a colour on `border` (or `borderTop`, `borderBottom`, `borderX`, …) that only restates the default — bare `border` draws the default border; name a colour only to deviate from it
50
+
51
+ ```tsx
52
+ // Good
53
+ <Column maxWidth="l" border radius="l">
54
+ // Bad
55
+ <Column fillWidth maxWidth="l" minWidth={0} position="relative" border="neutral-alpha-weak" radius="l">
56
+ ```
57
+
58
+ 3b. `zIndex` belongs to a stacking group, not to a page. Use it only inside an isolated group that needs an order — a decorative absolute layer and the content sibling above it (rule 21), a sticky header over the pane it covers — and keep the values small within that group. A `zIndex` on a lone element, or a page-wide ladder of `zIndex={3}`, `{5}`, `{9}`, means the hierarchy is being managed by hand; restructure the tree instead.
47
59
 
48
60
  ## Spacing and sizing
49
61
 
@@ -66,10 +78,10 @@ If you can't ask (or the user says "you decide"), default to: restrained, center
66
78
  - `background` also accepts `surface | overlay | page | transparent`
67
79
  - `onBackground` colors text/icons on a background
68
80
 
69
- 8. Standard surface recipe: `background="surface"` (or `"overlay"` over imagery) + `border="neutral-alpha-weak"` (or `-medium`) + `radius="l"`.
81
+ 8. Standard surface recipe: `background="surface"` (or `"overlay"` over imagery) + `border` + `radius="l"`. Bare `border` is the default border; name a colour (`border="neutral-alpha-medium"`, `border="brand-alpha-medium"`) only when the surface should differ from it.
70
82
 
71
83
  ```tsx
72
- <Column background="surface" border="neutral-alpha-weak" radius="l" padding="24" gap="16">
84
+ <Column background="surface" border radius="l" padding="24" gap="16">
73
85
  ```
74
86
 
75
87
  ## Text
@@ -105,7 +117,7 @@ If you can't ask (or the user says "you decide"), default to: restrained, center
105
117
 
106
118
  13. Use the distinctive features when the design calls for them — they exist and are cheap:
107
119
  - `Dialog` supports `stack` (stacked dialogs) and `base` props
108
- - Effects: `RevealFx`, `LetterFx`, `TypeFx`, `CountFx`, `GlitchFx`, `HoloFx`, `TiltFx`, `FlipFx`, `MatrixFx`, `BlobFx`, `Particle`, `ShineFx`, `CelebrationFx`
120
+ - Effects: `RevealFx`, `LetterFx`, `TypeFx`, `CountFx`, `GlitchFx`, `HoloFx`, `TiltFx`, `FlipFx`, `MatrixFx`, `BlobFx`, `ParticleFx`, `ShineFx`, `CelebrationFx`
109
121
  - `CursorCard`, `HoverCard`, `ContextMenu`, `EmojiPicker`, `CompareImage`, `OgCard`, `Timeline`, `MasonryGrid`, `InfiniteScroll`, `SegmentedControl`, `Kbar` (command palette)
110
122
  - Forms: `Input`, `PasswordInput`, `OTPInput`, `NumberInput`, `TagInput`, `Select`, `DatePicker`, `DateRangePicker`, `ColorInput`, `Slider`, `Switch`, `Checkbox`, `Chip`
111
123
 
@@ -183,7 +195,7 @@ the card it was meant to support.
183
195
  <Grid columns={5} s={{ columns: 3 }} gap="8" fillWidth>
184
196
  {items.map(i => (
185
197
  <Column key={i} aspectRatio="1 / 1" padding="12" radius="l"
186
- background="page" border="neutral-alpha-weak" vertical="between">
198
+ background="page" border vertical="between">
187
199
  {/* a preview of the thing, not just its name */}
188
200
  </Column>
189
201
  ))}
@@ -204,7 +216,7 @@ in that order.
204
216
  <Background position="absolute" top="0" left="0" fill pointerEvents="none" ... />
205
217
  ```
206
218
 
207
- Siblings holding content get `zIndex={1}`. `overflow="hidden"` goes on the parent **only when it has a radius** (cards, panels) — clipping a full-bleed section turns the glow into a hard-edged band.
219
+ Siblings holding content get `zIndex={1}`. That layer and its content sibling are the whole stacking group — nothing else in the section needs a `zIndex` (rule 3b). `overflow="hidden"` goes on the parent **only when it has a radius** (cards, panels) — clipping a full-bleed section turns the glow into a hard-edged band.
208
220
 
209
221
  21b. Ambient section layers (hero glows) live on the **full-width section wrapper**, outside the `maxWidth` content column, with generous room (`paddingY="80"`+). Inside a narrow container the gradient gets clipped at the container edge and reads as a misplaced card:
210
222