@k8ordo/ui 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/README.md +197 -187
  2. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  3. package/dist/components/_internal/focus-ring.css +37 -0
  4. package/dist/components/_internal/focus-ring.module.mjs +9 -0
  5. package/dist/components/_internal/form-value.d.mts +33 -0
  6. package/dist/components/_internal/form-value.mjs +91 -0
  7. package/dist/components/_internal/high-contrast.css +18 -0
  8. package/dist/components/_internal/high-contrast.module.mjs +5 -0
  9. package/dist/components/_internal/ignore-changes.d.mts +20 -0
  10. package/dist/components/_internal/ignore-changes.mjs +37 -0
  11. package/dist/components/_internal/nav-link.css +78 -0
  12. package/dist/components/_internal/nav-link.d.mts +5 -0
  13. package/dist/components/_internal/nav-link.mjs +8 -0
  14. package/dist/components/_internal/nav-link.module.mjs +14 -0
  15. package/dist/components/_internal/sr-only.css +17 -0
  16. package/dist/components/_internal/sr-only.module.mjs +5 -0
  17. package/dist/components/_internal/status-tone.css +37 -0
  18. package/dist/components/_internal/status-tone.d.mts +20 -0
  19. package/dist/components/_internal/status-tone.mjs +22 -0
  20. package/dist/components/_internal/status-tone.module.mjs +14 -0
  21. package/dist/components/ai/_internal/attachment-preview.css +75 -0
  22. package/dist/components/ai/_internal/attachment-preview.d.mts +14 -0
  23. package/dist/components/ai/_internal/attachment-preview.mjs +50 -0
  24. package/dist/components/ai/_internal/attachment-preview.module.mjs +14 -0
  25. package/dist/components/ai/_internal/collapsible.css +62 -0
  26. package/dist/components/ai/_internal/collapsible.d.mts +4 -3
  27. package/dist/components/ai/_internal/collapsible.mjs +42 -33
  28. package/dist/components/ai/_internal/collapsible.module.mjs +12 -0
  29. package/dist/components/ai/_internal/icons.d.mts +7 -0
  30. package/dist/components/ai/_internal/icons.mjs +14 -0
  31. package/dist/components/ai/_internal/streaming-cursor.css +28 -0
  32. package/dist/components/ai/_internal/streaming-cursor.d.mts +2 -3
  33. package/dist/components/ai/_internal/streaming-cursor.mjs +2 -1
  34. package/dist/components/ai/_internal/streaming-cursor.module.mjs +8 -0
  35. package/dist/components/ai/attachment/attachment.css +17 -0
  36. package/dist/components/ai/attachment/attachment.d.mts +14 -0
  37. package/dist/components/ai/attachment/attachment.mjs +25 -0
  38. package/dist/components/ai/attachment/attachment.module.mjs +8 -0
  39. package/dist/components/ai/attachment/index.d.mts +13 -0
  40. package/dist/components/ai/attachment/index.mjs +8 -0
  41. package/dist/components/ai/conversation/conversation.css +71 -0
  42. package/dist/components/ai/conversation/conversation.d.mts +4 -5
  43. package/dist/components/ai/conversation/conversation.mjs +38 -34
  44. package/dist/components/ai/conversation/conversation.module.mjs +11 -0
  45. package/dist/components/ai/conversation/index.d.mts +2 -3
  46. package/dist/components/ai/index.d.mts +4 -2
  47. package/dist/components/ai/index.mjs +3 -1
  48. package/dist/components/ai/message/index.d.mts +29 -3
  49. package/dist/components/ai/message/index.mjs +7 -2
  50. package/dist/components/ai/message/message.css +71 -0
  51. package/dist/components/ai/message/message.d.mts +34 -4
  52. package/dist/components/ai/message/message.mjs +99 -9
  53. package/dist/components/ai/message/message.module.mjs +18 -0
  54. package/dist/components/ai/prompt-input/index.d.mts +11 -4
  55. package/dist/components/ai/prompt-input/index.mjs +3 -1
  56. package/dist/components/ai/prompt-input/prompt-input.css +101 -0
  57. package/dist/components/ai/prompt-input/prompt-input.d.mts +15 -6
  58. package/dist/components/ai/prompt-input/prompt-input.mjs +152 -37
  59. package/dist/components/ai/prompt-input/prompt-input.module.mjs +15 -0
  60. package/dist/components/ai/reasoning/reasoning.css +16 -0
  61. package/dist/components/ai/reasoning/reasoning.d.mts +2 -3
  62. package/dist/components/ai/reasoning/reasoning.mjs +5 -5
  63. package/dist/components/ai/reasoning/reasoning.module.mjs +8 -0
  64. package/dist/components/ai/response/response.css +453 -0
  65. package/dist/components/ai/response/response.d.mts +4 -6
  66. package/dist/components/ai/response/response.mjs +20 -15
  67. package/dist/components/ai/response/response.module.mjs +8 -0
  68. package/dist/components/ai/source/index.d.mts +12 -0
  69. package/dist/components/ai/source/index.mjs +8 -0
  70. package/dist/components/ai/source/source.css +54 -0
  71. package/dist/components/ai/source/source.d.mts +13 -0
  72. package/dist/components/ai/source/source.mjs +51 -0
  73. package/dist/components/ai/source/source.module.mjs +12 -0
  74. package/dist/components/ai/suggestion/index.d.mts +2 -3
  75. package/dist/components/ai/suggestion/suggestion.css +32 -0
  76. package/dist/components/ai/suggestion/suggestion.d.mts +3 -4
  77. package/dist/components/ai/suggestion/suggestion.mjs +7 -6
  78. package/dist/components/ai/suggestion/suggestion.module.mjs +8 -0
  79. package/dist/components/ai/tool-invocation/approval-bar.css +26 -0
  80. package/dist/components/ai/tool-invocation/approval-bar.d.mts +11 -0
  81. package/dist/components/ai/tool-invocation/approval-bar.mjs +59 -0
  82. package/dist/components/ai/tool-invocation/approval-bar.module.mjs +9 -0
  83. package/dist/components/ai/tool-invocation/tool-invocation.css +55 -0
  84. package/dist/components/ai/tool-invocation/tool-invocation.d.mts +5 -5
  85. package/dist/components/ai/tool-invocation/tool-invocation.mjs +39 -18
  86. package/dist/components/ai/tool-invocation/tool-invocation.module.mjs +15 -0
  87. package/dist/components/ai/types.d.mts +21 -4
  88. package/dist/components/buttons/button/button.css +158 -0
  89. package/dist/components/buttons/button/button.d.mts +32 -9
  90. package/dist/components/buttons/button/button.mjs +46 -36
  91. package/dist/components/buttons/button/button.module.mjs +23 -0
  92. package/dist/components/buttons/button/index.d.mts +2 -2
  93. package/dist/components/buttons/copy-button/copy-button.css +15 -0
  94. package/dist/components/buttons/copy-button/copy-button.d.mts +10 -0
  95. package/dist/components/buttons/copy-button/copy-button.mjs +70 -0
  96. package/dist/components/buttons/copy-button/copy-button.module.mjs +8 -0
  97. package/dist/components/buttons/copy-button/index.d.mts +2 -0
  98. package/dist/components/buttons/copy-button/index.mjs +2 -0
  99. package/dist/components/buttons/icon-button/icon-button.css +81 -0
  100. package/dist/components/buttons/icon-button/icon-button.d.mts +39 -12
  101. package/dist/components/buttons/icon-button/icon-button.mjs +70 -57
  102. package/dist/components/buttons/icon-button/icon-button.module.mjs +15 -0
  103. package/dist/components/buttons/icon-button/index.d.mts +2 -2
  104. package/dist/components/buttons/toolbar/index.d.mts +14 -0
  105. package/dist/components/buttons/toolbar/index.mjs +9 -0
  106. package/dist/components/buttons/toolbar/toolbar.css +30 -0
  107. package/dist/components/buttons/toolbar/toolbar.d.mts +18 -0
  108. package/dist/components/buttons/toolbar/toolbar.mjs +111 -0
  109. package/dist/components/buttons/toolbar/toolbar.module.mjs +9 -0
  110. package/dist/components/data-display/accordion/accordion-button.css +48 -0
  111. package/dist/components/data-display/accordion/accordion-button.d.mts +2 -3
  112. package/dist/components/data-display/accordion/accordion-button.mjs +5 -4
  113. package/dist/components/data-display/accordion/accordion-button.module.mjs +9 -0
  114. package/dist/components/data-display/accordion/accordion-item.css +11 -0
  115. package/dist/components/data-display/accordion/accordion-item.d.mts +2 -3
  116. package/dist/components/data-display/accordion/accordion-item.mjs +2 -1
  117. package/dist/components/data-display/accordion/accordion-item.module.mjs +5 -0
  118. package/dist/components/data-display/accordion/accordion-panel.css +9 -0
  119. package/dist/components/data-display/accordion/accordion-panel.d.mts +2 -3
  120. package/dist/components/data-display/accordion/accordion-panel.mjs +2 -2
  121. package/dist/components/data-display/accordion/accordion-panel.module.mjs +5 -0
  122. package/dist/components/data-display/accordion/accordion.css +19 -0
  123. package/dist/components/data-display/accordion/accordion.d.mts +2 -3
  124. package/dist/components/data-display/accordion/accordion.mjs +2 -1
  125. package/dist/components/data-display/accordion/accordion.module.mjs +5 -0
  126. package/dist/components/data-display/accordion/context.d.mts +3 -3
  127. package/dist/components/data-display/accordion/index.d.mts +2 -3
  128. package/dist/components/data-display/avatar/avatar.css +95 -0
  129. package/dist/components/data-display/avatar/avatar.d.mts +2 -3
  130. package/dist/components/data-display/avatar/avatar.mjs +22 -6
  131. package/dist/components/data-display/avatar/avatar.module.mjs +18 -0
  132. package/dist/components/data-display/badge/badge.css +160 -0
  133. package/dist/components/data-display/badge/badge.d.mts +2 -3
  134. package/dist/components/data-display/badge/badge.mjs +30 -5
  135. package/dist/components/data-display/badge/badge.module.mjs +21 -0
  136. package/dist/components/data-display/card/card.css +44 -0
  137. package/dist/components/data-display/card/card.d.mts +2 -3
  138. package/dist/components/data-display/card/card.mjs +12 -2
  139. package/dist/components/data-display/card/card.module.mjs +12 -0
  140. package/dist/components/data-display/carousel/carousel.css +83 -0
  141. package/dist/components/data-display/carousel/carousel.d.mts +11 -0
  142. package/dist/components/data-display/carousel/carousel.mjs +186 -0
  143. package/dist/components/data-display/carousel/carousel.module.mjs +18 -0
  144. package/dist/components/data-display/carousel/index.d.mts +11 -0
  145. package/dist/components/data-display/carousel/index.mjs +8 -0
  146. package/dist/components/data-display/code/code.css +37 -0
  147. package/dist/components/data-display/code/code.d.mts +2 -3
  148. package/dist/components/data-display/code/code.mjs +7 -6
  149. package/dist/components/data-display/code/code.module.mjs +9 -0
  150. package/dist/components/data-display/code/find-all-colors.d.mts +2 -3
  151. package/dist/components/data-display/code/find-all-colors.mjs +0 -161
  152. package/dist/components/data-display/code-block/code-block.css +161 -0
  153. package/dist/components/data-display/code-block/code-block.d.mts +12 -0
  154. package/dist/components/data-display/code-block/code-block.mjs +44 -0
  155. package/dist/components/data-display/code-block/code-block.module.mjs +11 -0
  156. package/dist/components/data-display/code-block/highlight.d.mts +9 -0
  157. package/dist/components/data-display/code-block/highlight.mjs +54 -0
  158. package/dist/components/data-display/code-block/index.d.mts +2 -0
  159. package/dist/components/data-display/code-block/index.mjs +2 -0
  160. package/dist/components/data-display/data-table/data-table.css +74 -0
  161. package/dist/components/data-display/data-table/data-table.d.mts +31 -0
  162. package/dist/components/data-display/data-table/data-table.mjs +155 -0
  163. package/dist/components/data-display/data-table/data-table.module.mjs +14 -0
  164. package/dist/components/data-display/data-table/index.d.mts +2 -0
  165. package/dist/components/data-display/data-table/index.mjs +2 -0
  166. package/dist/components/data-display/heading/heading.css +99 -0
  167. package/dist/components/data-display/heading/heading.d.mts +2 -3
  168. package/dist/components/data-display/heading/heading.mjs +15 -14
  169. package/dist/components/data-display/heading/heading.module.mjs +20 -0
  170. package/dist/components/data-display/kbd/index.d.mts +2 -0
  171. package/dist/components/data-display/kbd/index.mjs +2 -0
  172. package/dist/components/data-display/kbd/kbd.css +21 -0
  173. package/dist/components/data-display/kbd/kbd.d.mts +8 -0
  174. package/dist/components/data-display/kbd/kbd.mjs +17 -0
  175. package/dist/components/data-display/kbd/kbd.module.mjs +5 -0
  176. package/dist/components/data-display/prose/index.d.mts +2 -0
  177. package/dist/components/data-display/prose/index.mjs +2 -0
  178. package/dist/components/data-display/prose/prose.css +253 -0
  179. package/dist/components/data-display/prose/prose.d.mts +5 -0
  180. package/dist/components/data-display/prose/prose.mjs +10 -0
  181. package/dist/components/data-display/prose/prose.module.mjs +5 -0
  182. package/dist/components/data-display/table/table.css +107 -0
  183. package/dist/components/data-display/table/table.d.mts +10 -9
  184. package/dist/components/data-display/table/table.mjs +26 -15
  185. package/dist/components/data-display/table/table.module.mjs +22 -0
  186. package/dist/components/data-display/tree/index.d.mts +2 -0
  187. package/dist/components/data-display/tree/index.mjs +2 -0
  188. package/dist/components/data-display/tree/tree.css +84 -0
  189. package/dist/components/data-display/tree/tree.d.mts +20 -0
  190. package/dist/components/data-display/tree/tree.mjs +151 -0
  191. package/dist/components/data-display/tree/tree.module.mjs +15 -0
  192. package/dist/components/feedback/alert/alert.css +42 -0
  193. package/dist/components/feedback/alert/alert.d.mts +3 -4
  194. package/dist/components/feedback/alert/alert.mjs +20 -23
  195. package/dist/components/feedback/alert/alert.module.mjs +14 -0
  196. package/dist/components/feedback/callout/callout.css +38 -0
  197. package/dist/components/feedback/callout/callout.d.mts +10 -0
  198. package/dist/components/feedback/callout/callout.mjs +38 -0
  199. package/dist/components/feedback/callout/callout.module.mjs +11 -0
  200. package/dist/components/feedback/callout/index.d.mts +2 -0
  201. package/dist/components/feedback/callout/index.mjs +2 -0
  202. package/dist/components/feedback/empty-state/empty-state.css +36 -0
  203. package/dist/components/feedback/empty-state/empty-state.d.mts +10 -0
  204. package/dist/components/feedback/empty-state/empty-state.mjs +27 -0
  205. package/dist/components/feedback/empty-state/empty-state.module.mjs +11 -0
  206. package/dist/components/feedback/empty-state/index.d.mts +2 -0
  207. package/dist/components/feedback/empty-state/index.mjs +2 -0
  208. package/dist/components/feedback/progress/progress.css +78 -0
  209. package/dist/components/feedback/progress/progress.d.mts +5 -5
  210. package/dist/components/feedback/progress/progress.mjs +17 -3
  211. package/dist/components/feedback/progress/progress.module.mjs +12 -0
  212. package/dist/components/feedback/skeleton/skeleton.css +64 -0
  213. package/dist/components/feedback/skeleton/skeleton.d.mts +2 -3
  214. package/dist/components/feedback/skeleton/skeleton.mjs +19 -2
  215. package/dist/components/feedback/skeleton/skeleton.module.mjs +17 -0
  216. package/dist/components/feedback/spinner/spinner.css +40 -0
  217. package/dist/components/feedback/spinner/spinner.d.mts +2 -3
  218. package/dist/components/feedback/spinner/spinner.mjs +13 -7
  219. package/dist/components/feedback/spinner/spinner.module.mjs +12 -0
  220. package/dist/components/feedback/toast/context.d.mts +8 -9
  221. package/dist/components/feedback/toast/provider.css +69 -0
  222. package/dist/components/feedback/toast/provider.d.mts +2 -3
  223. package/dist/components/feedback/toast/provider.mjs +17 -13
  224. package/dist/components/feedback/toast/provider.module.mjs +12 -0
  225. package/dist/components/feedback/toast/toast.d.mts +2 -3
  226. package/dist/components/form/autocomplete/autocomplete.css +154 -0
  227. package/dist/components/form/autocomplete/autocomplete.d.mts +2 -3
  228. package/dist/components/form/autocomplete/autocomplete.mjs +70 -46
  229. package/dist/components/form/autocomplete/autocomplete.module.mjs +21 -0
  230. package/dist/components/form/calendar/calendar.css +98 -0
  231. package/dist/components/form/calendar/calendar.d.mts +14 -0
  232. package/dist/components/form/calendar/calendar.mjs +182 -0
  233. package/dist/components/form/calendar/calendar.module.mjs +18 -0
  234. package/dist/components/form/calendar/index.d.mts +2 -0
  235. package/dist/components/form/calendar/index.mjs +2 -0
  236. package/dist/components/form/checkbox/checkbox.css +90 -0
  237. package/dist/components/form/checkbox/checkbox.d.mts +4 -3
  238. package/dist/components/form/checkbox/checkbox.mjs +29 -27
  239. package/dist/components/form/checkbox/checkbox.module.mjs +13 -0
  240. package/dist/components/form/checkbox-card/checkbox-card.css +138 -0
  241. package/dist/components/form/checkbox-card/checkbox-card.d.mts +3 -4
  242. package/dist/components/form/checkbox-card/checkbox-card.mjs +17 -23
  243. package/dist/components/form/checkbox-card/checkbox-card.module.mjs +17 -0
  244. package/dist/components/form/checkbox-group/checkbox-group.css +15 -0
  245. package/dist/components/form/checkbox-group/checkbox-group.d.mts +7 -5
  246. package/dist/components/form/checkbox-group/checkbox-group.mjs +12 -16
  247. package/dist/components/form/checkbox-group/checkbox-group.module.mjs +8 -0
  248. package/dist/components/form/checkbox-group/index.d.mts +4 -3
  249. package/dist/components/form/color-picker/color-picker.css +172 -0
  250. package/dist/components/form/color-picker/color-picker.d.mts +28 -0
  251. package/dist/components/form/color-picker/color-picker.mjs +239 -0
  252. package/dist/components/form/color-picker/color-picker.module.mjs +20 -0
  253. package/dist/components/form/color-picker/index.d.mts +2 -0
  254. package/dist/components/form/color-picker/index.mjs +2 -0
  255. package/dist/components/form/combobox/combobox.css +143 -0
  256. package/dist/components/form/combobox/combobox.d.mts +32 -0
  257. package/dist/components/form/combobox/combobox.mjs +278 -0
  258. package/dist/components/form/combobox/combobox.module.mjs +19 -0
  259. package/dist/components/form/combobox/index.d.mts +2 -0
  260. package/dist/components/form/combobox/index.mjs +2 -0
  261. package/dist/components/form/date-field/date-field.css +31 -0
  262. package/dist/components/form/date-field/date-field.d.mts +8 -0
  263. package/dist/components/form/date-field/date-field.mjs +20 -0
  264. package/dist/components/form/date-field/date-field.module.mjs +5 -0
  265. package/dist/components/form/date-field/index.d.mts +2 -0
  266. package/dist/components/form/date-field/index.mjs +2 -0
  267. package/dist/components/form/date-picker/date-picker.css +90 -0
  268. package/dist/components/form/date-picker/date-picker.d.mts +12 -0
  269. package/dist/components/form/date-picker/date-picker.mjs +77 -0
  270. package/dist/components/form/date-picker/date-picker.module.mjs +14 -0
  271. package/dist/components/form/date-picker/index.d.mts +2 -0
  272. package/dist/components/form/date-picker/index.mjs +2 -0
  273. package/dist/components/form/file-field/file-field.css +88 -0
  274. package/dist/components/form/file-field/file-field.d.mts +9 -7
  275. package/dist/components/form/file-field/file-field.mjs +130 -37
  276. package/dist/components/form/file-field/file-field.module.mjs +17 -0
  277. package/dist/components/form/file-field/index.d.mts +5 -3
  278. package/dist/components/form/file-field/index.mjs +2 -1
  279. package/dist/components/form/form/form.css +11 -0
  280. package/dist/components/form/form/form.d.mts +4 -4
  281. package/dist/components/form/form/form.mjs +2 -1
  282. package/dist/components/form/form/form.module.mjs +5 -0
  283. package/dist/components/form/form-control/form-control.css +43 -0
  284. package/dist/components/form/form-control/form-control.d.mts +2 -3
  285. package/dist/components/form/form-control/form-control.mjs +11 -10
  286. package/dist/components/form/form-control/form-control.module.mjs +12 -0
  287. package/dist/components/form/number-field/cast.d.mts +2 -3
  288. package/dist/components/form/number-field/cast.mjs +3 -22
  289. package/dist/components/form/number-field/number-field.css +122 -0
  290. package/dist/components/form/number-field/number-field.d.mts +10 -10
  291. package/dist/components/form/number-field/number-field.mjs +126 -54
  292. package/dist/components/form/number-field/number-field.module.mjs +10 -0
  293. package/dist/components/form/password-input/password-input.css +88 -0
  294. package/dist/components/form/password-input/password-input.d.mts +2 -3
  295. package/dist/components/form/password-input/password-input.mjs +15 -12
  296. package/dist/components/form/password-input/password-input.module.mjs +12 -0
  297. package/dist/components/form/radio/radio.css +89 -0
  298. package/dist/components/form/radio/radio.d.mts +3 -3
  299. package/dist/components/form/radio/radio.mjs +15 -19
  300. package/dist/components/form/radio/radio.module.mjs +16 -0
  301. package/dist/components/form/radio-card/radio-card.css +147 -0
  302. package/dist/components/form/radio-card/radio-card.d.mts +4 -4
  303. package/dist/components/form/radio-card/radio-card.mjs +17 -23
  304. package/dist/components/form/radio-card/radio-card.module.mjs +19 -0
  305. package/dist/components/form/range-slider/index.d.mts +2 -0
  306. package/dist/components/form/range-slider/index.mjs +2 -0
  307. package/dist/components/form/range-slider/range-slider.css +67 -0
  308. package/dist/components/form/range-slider/range-slider.d.mts +30 -0
  309. package/dist/components/form/range-slider/range-slider.mjs +135 -0
  310. package/dist/components/form/range-slider/range-slider.module.mjs +14 -0
  311. package/dist/components/form/select/select.css +74 -0
  312. package/dist/components/form/select/select.d.mts +2 -3
  313. package/dist/components/form/select/select.mjs +25 -8
  314. package/dist/components/form/select/select.module.mjs +12 -0
  315. package/dist/components/form/slider/range-input-class.css +97 -0
  316. package/dist/components/form/slider/range-input-class.d.mts +7 -0
  317. package/dist/components/form/slider/range-input-class.mjs +10 -0
  318. package/dist/components/form/slider/range-input-class.module.mjs +8 -0
  319. package/dist/components/form/slider/slider.css +55 -0
  320. package/dist/components/form/slider/slider.d.mts +6 -7
  321. package/dist/components/form/slider/slider.mjs +44 -11
  322. package/dist/components/form/slider/slider.module.mjs +12 -0
  323. package/dist/components/form/switch/switch.css +103 -0
  324. package/dist/components/form/switch/switch.d.mts +2 -3
  325. package/dist/components/form/switch/switch.mjs +14 -17
  326. package/dist/components/form/switch/switch.module.mjs +16 -0
  327. package/dist/components/form/text-field/text-field.css +41 -0
  328. package/dist/components/form/text-field/text-field.d.mts +4 -5
  329. package/dist/components/form/text-field/text-field.mjs +2 -3
  330. package/dist/components/form/text-field/text-field.module.mjs +5 -0
  331. package/dist/components/form/textarea/textarea.css +50 -0
  332. package/dist/components/form/textarea/textarea.d.mts +3 -3
  333. package/dist/components/form/textarea/textarea.mjs +6 -20
  334. package/dist/components/form/textarea/textarea.module.mjs +9 -0
  335. package/dist/components/icons/base.css +40 -0
  336. package/dist/components/icons/base.d.mts +4 -5
  337. package/dist/components/icons/base.mjs +10 -10
  338. package/dist/components/icons/base.module.mjs +13 -0
  339. package/dist/components/icons/browsers.d.mts +5 -6
  340. package/dist/components/icons/color-scale.d.mts +2 -3
  341. package/dist/components/icons/github-mark.d.mts +2 -3
  342. package/dist/components/icons/logo.css +17 -0
  343. package/dist/components/icons/logo.d.mts +3 -4
  344. package/dist/components/icons/logo.mjs +4 -3
  345. package/dist/components/icons/logo.module.mjs +9 -0
  346. package/dist/components/icons/lucide-deep.d.mts +3 -1
  347. package/dist/components/icons/lucide-imports.d.mts +3 -1
  348. package/dist/components/icons/lucide-imports.mjs +3 -1
  349. package/dist/components/icons/lucide.d.mts +60 -61
  350. package/dist/components/icons/qiita.d.mts +2 -3
  351. package/dist/components/icons/twitter.css +9 -0
  352. package/dist/components/icons/twitter.d.mts +2 -3
  353. package/dist/components/icons/twitter.mjs +2 -1
  354. package/dist/components/icons/twitter.module.mjs +5 -0
  355. package/dist/components/icons/vertical-writing.d.mts +2 -3
  356. package/dist/components/index.d.mts +48 -9
  357. package/dist/components/index.mjs +32 -10
  358. package/dist/components/layout/_shared/gap.css +25 -0
  359. package/dist/components/layout/_shared/gap.d.mts +8 -9
  360. package/dist/components/layout/_shared/gap.mjs +6 -5
  361. package/dist/components/layout/_shared/gap.module.mjs +11 -0
  362. package/dist/components/layout/_shared/padding.css +25 -0
  363. package/dist/components/layout/_shared/padding.d.mts +8 -9
  364. package/dist/components/layout/_shared/padding.mjs +6 -5
  365. package/dist/components/layout/_shared/padding.module.mjs +11 -0
  366. package/dist/components/layout/grid/grid.css +93 -0
  367. package/dist/components/layout/grid/grid.d.mts +5 -6
  368. package/dist/components/layout/grid/grid.mjs +21 -20
  369. package/dist/components/layout/grid/grid.module.mjs +25 -0
  370. package/dist/components/layout/resizable-panels/index.d.mts +18 -0
  371. package/dist/components/layout/resizable-panels/index.mjs +9 -0
  372. package/dist/components/layout/resizable-panels/resizable-panels.css +77 -0
  373. package/dist/components/layout/resizable-panels/resizable-panels.d.mts +21 -0
  374. package/dist/components/layout/resizable-panels/resizable-panels.mjs +164 -0
  375. package/dist/components/layout/resizable-panels/resizable-panels.module.mjs +16 -0
  376. package/dist/components/layout/separator/separator.css +37 -0
  377. package/dist/components/layout/separator/separator.d.mts +2 -3
  378. package/dist/components/layout/separator/separator.mjs +12 -9
  379. package/dist/components/layout/separator/separator.module.mjs +12 -0
  380. package/dist/components/layout/stack/stack.css +49 -0
  381. package/dist/components/layout/stack/stack.d.mts +3 -4
  382. package/dist/components/layout/stack/stack.mjs +11 -10
  383. package/dist/components/layout/stack/stack.module.mjs +17 -0
  384. package/dist/components/navigation/anchor/anchor.css +29 -0
  385. package/dist/components/navigation/anchor/anchor.d.mts +2 -3
  386. package/dist/components/navigation/anchor/anchor.mjs +5 -2
  387. package/dist/components/navigation/anchor/anchor.module.mjs +8 -0
  388. package/dist/components/navigation/breadcrumb/breadcrumb.css +72 -0
  389. package/dist/components/navigation/breadcrumb/breadcrumb.d.mts +5 -6
  390. package/dist/components/navigation/breadcrumb/breadcrumb.mjs +15 -9
  391. package/dist/components/navigation/breadcrumb/breadcrumb.module.mjs +14 -0
  392. package/dist/components/navigation/breadcrumb/index.d.mts +2 -3
  393. package/dist/components/navigation/pagination/pagination.css +36 -0
  394. package/dist/components/navigation/pagination/pagination.d.mts +2 -3
  395. package/dist/components/navigation/pagination/pagination.mjs +9 -8
  396. package/dist/components/navigation/pagination/pagination.module.mjs +11 -0
  397. package/dist/components/navigation/side-nav/index.d.mts +16 -0
  398. package/dist/components/navigation/side-nav/index.mjs +10 -0
  399. package/dist/components/navigation/side-nav/side-nav.css +73 -0
  400. package/dist/components/navigation/side-nav/side-nav.d.mts +31 -0
  401. package/dist/components/navigation/side-nav/side-nav.mjs +62 -0
  402. package/dist/components/navigation/side-nav/side-nav.module.mjs +11 -0
  403. package/dist/components/navigation/stepper/index.d.mts +2 -0
  404. package/dist/components/navigation/stepper/index.mjs +2 -0
  405. package/dist/components/navigation/stepper/stepper.css +151 -0
  406. package/dist/components/navigation/stepper/stepper.d.mts +19 -0
  407. package/dist/components/navigation/stepper/stepper.mjs +87 -0
  408. package/dist/components/navigation/stepper/stepper.module.mjs +29 -0
  409. package/dist/components/navigation/table-of-contents/index.d.mts +2 -0
  410. package/dist/components/navigation/table-of-contents/index.mjs +2 -0
  411. package/dist/components/navigation/table-of-contents/table-of-contents.css +17 -0
  412. package/dist/components/navigation/table-of-contents/table-of-contents.d.mts +12 -0
  413. package/dist/components/navigation/table-of-contents/table-of-contents.mjs +94 -0
  414. package/dist/components/navigation/table-of-contents/table-of-contents.module.mjs +8 -0
  415. package/dist/components/navigation/tabs/index.d.mts +2 -3
  416. package/dist/components/navigation/tabs/tabs.css +70 -0
  417. package/dist/components/navigation/tabs/tabs.d.mts +5 -6
  418. package/dist/components/navigation/tabs/tabs.mjs +51 -42
  419. package/dist/components/navigation/tabs/tabs.module.mjs +12 -0
  420. package/dist/components/observers/fragment-instance.d.mts +1 -0
  421. package/dist/components/observers/in-view/in-view.d.mts +11 -0
  422. package/dist/components/observers/in-view/in-view.mjs +60 -0
  423. package/dist/components/observers/in-view/index.d.mts +2 -0
  424. package/dist/components/observers/in-view/index.mjs +2 -0
  425. package/dist/components/observers/resize/index.d.mts +2 -0
  426. package/dist/components/observers/resize/index.mjs +2 -0
  427. package/dist/components/observers/resize/resize.d.mts +7 -0
  428. package/dist/components/observers/resize/resize.mjs +25 -0
  429. package/dist/components/overlays/_internal/modal-dialog-context.d.mts +4 -5
  430. package/dist/components/overlays/_internal/use-can-hover.d.mts +2 -3
  431. package/dist/components/overlays/_internal/use-hover-intent.d.mts +3 -4
  432. package/dist/components/overlays/_internal/use-list-navigation.d.mts +17 -11
  433. package/dist/components/overlays/_internal/use-list-navigation.mjs +42 -26
  434. package/dist/components/overlays/command-palette/command-palette.css +91 -0
  435. package/dist/components/overlays/command-palette/command-palette.d.mts +24 -0
  436. package/dist/components/overlays/command-palette/command-palette.mjs +139 -0
  437. package/dist/components/overlays/command-palette/command-palette.module.mjs +14 -0
  438. package/dist/components/overlays/command-palette/index.d.mts +2 -0
  439. package/dist/components/overlays/command-palette/index.mjs +2 -0
  440. package/dist/components/overlays/context-menu/context-menu.d.mts +13 -0
  441. package/dist/components/overlays/context-menu/context-menu.mjs +79 -0
  442. package/dist/components/overlays/context-menu/index.d.mts +21 -0
  443. package/dist/components/overlays/context-menu/index.mjs +13 -0
  444. package/dist/components/overlays/dialog/dialog.css +31 -0
  445. package/dist/components/overlays/dialog/dialog.d.mts +4 -5
  446. package/dist/components/overlays/dialog/dialog.mjs +10 -7
  447. package/dist/components/overlays/dialog/dialog.module.mjs +10 -0
  448. package/dist/components/overlays/dialog/index.d.mts +2 -3
  449. package/dist/components/overlays/drawer/drawer.css +48 -0
  450. package/dist/components/overlays/drawer/drawer.d.mts +2 -3
  451. package/dist/components/overlays/drawer/drawer.mjs +11 -10
  452. package/dist/components/overlays/drawer/drawer.module.mjs +13 -0
  453. package/dist/components/overlays/dropdown-menu/dropdown-menu.d.mts +7 -10
  454. package/dist/components/overlays/dropdown-menu/dropdown-menu.mjs +10 -9
  455. package/dist/components/overlays/dropdown-menu/hooks.d.mts +7 -7
  456. package/dist/components/overlays/dropdown-menu/hooks.mjs +3 -3
  457. package/dist/components/overlays/dropdown-menu/index.d.mts +8 -10
  458. package/dist/components/overlays/dropdown-menu/shared.css +51 -0
  459. package/dist/components/overlays/dropdown-menu/shared.d.mts +3 -6
  460. package/dist/components/overlays/dropdown-menu/shared.mjs +4 -6
  461. package/dist/components/overlays/dropdown-menu/shared.module.mjs +8 -0
  462. package/dist/components/overlays/dropdown-menu/sub-menu.css +28 -0
  463. package/dist/components/overlays/dropdown-menu/sub-menu.d.mts +3 -6
  464. package/dist/components/overlays/dropdown-menu/sub-menu.mjs +15 -15
  465. package/dist/components/overlays/dropdown-menu/sub-menu.module.mjs +9 -0
  466. package/dist/components/overlays/dropdown-menu/use-safe-triangle.d.mts +2 -3
  467. package/dist/components/overlays/list-box/hooks.d.mts +6 -5
  468. package/dist/components/overlays/list-box/hooks.mjs +5 -4
  469. package/dist/components/overlays/list-box/index.d.mts +2 -3
  470. package/dist/components/overlays/list-box/list-box.css +74 -0
  471. package/dist/components/overlays/list-box/list-box.d.mts +5 -6
  472. package/dist/components/overlays/list-box/list-box.mjs +15 -13
  473. package/dist/components/overlays/list-box/list-box.module.mjs +10 -0
  474. package/dist/components/overlays/modal/modal.css +119 -0
  475. package/dist/components/overlays/modal/modal.d.mts +2 -3
  476. package/dist/components/overlays/modal/modal.mjs +14 -11
  477. package/dist/components/overlays/modal/modal.module.mjs +11 -0
  478. package/dist/components/overlays/popover/anchor-positioning.d.mts +4 -5
  479. package/dist/components/overlays/popover/escape-stack.d.mts +2 -3
  480. package/dist/components/overlays/popover/hooks.d.mts +7 -8
  481. package/dist/components/overlays/popover/index.d.mts +2 -2
  482. package/dist/components/overlays/popover/popover.css +51 -0
  483. package/dist/components/overlays/popover/popover.d.mts +4 -4
  484. package/dist/components/overlays/popover/popover.mjs +11 -5
  485. package/dist/components/overlays/popover/popover.module.mjs +11 -0
  486. package/dist/components/overlays/tooltip/bridge.css +68 -0
  487. package/dist/components/overlays/tooltip/bridge.d.mts +2 -3
  488. package/dist/components/overlays/tooltip/bridge.mjs +7 -21
  489. package/dist/components/overlays/tooltip/bridge.module.mjs +11 -0
  490. package/dist/components/overlays/tooltip/index.d.mts +2 -2
  491. package/dist/components/overlays/tooltip/tooltip.css +15 -0
  492. package/dist/components/overlays/tooltip/tooltip.d.mts +5 -6
  493. package/dist/components/overlays/tooltip/tooltip.mjs +3 -2
  494. package/dist/components/overlays/tooltip/tooltip.module.mjs +5 -0
  495. package/dist/components/providers/index.d.mts +1 -2
  496. package/dist/components/providers/portal-root.d.mts +9 -7
  497. package/dist/components/providers/portal-root.mjs +7 -7
  498. package/dist/components/providers/ui-provider.d.mts +2 -6
  499. package/dist/components/providers/ui-provider.mjs +1 -5
  500. package/dist/helpers/chain.d.mts +2 -3
  501. package/dist/helpers/chain.mjs +0 -23
  502. package/dist/helpers/create-safe-context.d.mts +2 -3
  503. package/dist/helpers/create-safe-context.mjs +0 -17
  504. package/dist/helpers/index.d.mts +1 -2
  505. package/dist/helpers/index.mjs +2 -3
  506. package/dist/helpers/merge-props.d.mts +2 -3
  507. package/dist/helpers/merge-props.mjs +2 -37
  508. package/dist/helpers/merge-refs.d.mts +2 -3
  509. package/dist/helpers/merge-refs.mjs +0 -32
  510. package/dist/hooks/click-away/index.d.mts +2 -3
  511. package/dist/hooks/controllable-state/index.d.mts +2 -3
  512. package/dist/hooks/controllable-state/index.mjs +9 -11
  513. package/dist/hooks/index.d.mts +2 -20
  514. package/dist/hooks/index.mjs +2 -19
  515. package/dist/hooks/writing-mode/index.d.mts +15 -5
  516. package/dist/hooks/writing-mode/index.mjs +21 -14
  517. package/dist/i18n/current.d.mts +23 -0
  518. package/dist/i18n/current.mjs +43 -0
  519. package/dist/i18n/en.d.mts +2 -3
  520. package/dist/i18n/en.mjs +46 -0
  521. package/dist/i18n/index.d.mts +3 -2
  522. package/dist/i18n/index.mjs +4 -3
  523. package/dist/i18n/ja.d.mts +2 -3
  524. package/dist/i18n/ja.mjs +46 -0
  525. package/dist/i18n/messages.d.mts +81 -3
  526. package/dist/i18n/usage.d.mts +8 -0
  527. package/dist/i18n/usage.mjs +131 -0
  528. package/dist/index.d.mts +29 -34
  529. package/dist/index.mjs +32 -35
  530. package/dist/integrations/_shared/openui-defs.d.mts +3 -4
  531. package/dist/integrations/_shared/openui-defs.mjs +123 -54
  532. package/dist/integrations/_shared/renderers.css +50 -0
  533. package/dist/integrations/_shared/renderers.d.mts +71 -52
  534. package/dist/integrations/_shared/renderers.mjs +288 -30
  535. package/dist/integrations/_shared/renderers.module.mjs +14 -0
  536. package/dist/integrations/_shared/schemas.d.mts +387 -103
  537. package/dist/integrations/_shared/schemas.mjs +165 -27
  538. package/dist/integrations/ai-sdk/index.d.mts +2 -2
  539. package/dist/integrations/ai-sdk/map-parts.d.mts +25 -7
  540. package/dist/integrations/ai-sdk/map-parts.mjs +28 -3
  541. package/dist/integrations/json-render/catalog.d.mts +336 -18
  542. package/dist/integrations/json-render/catalog.mjs +120 -57
  543. package/dist/integrations/json-render/registry.d.mts +3 -4
  544. package/dist/integrations/json-render/registry.mjs +38 -4
  545. package/dist/integrations/openui/form-views.d.mts +23 -18
  546. package/dist/integrations/openui/form-views.mjs +27 -3
  547. package/dist/integrations/openui/library.d.mts +59 -7
  548. package/dist/integrations/openui/library.mjs +18 -3
  549. package/dist/integrations/openui/prompt.d.mts +2 -3
  550. package/dist/internal/accepts-file.d.mts +8 -0
  551. package/dist/internal/accepts-file.mjs +19 -0
  552. package/dist/internal/carries-files.d.mts +3 -0
  553. package/dist/internal/carries-files.mjs +4 -0
  554. package/dist/internal/clamp.d.mts +2 -3
  555. package/dist/internal/clamp.mjs +0 -11
  556. package/dist/internal/commit-input-value.d.mts +10 -0
  557. package/dist/internal/commit-input-value.mjs +15 -0
  558. package/dist/internal/dropped-files.d.mts +3 -0
  559. package/dist/internal/dropped-files.mjs +8 -0
  560. package/dist/internal/focus-trap.d.mts +5 -6
  561. package/dist/internal/focus-trap.mjs +4 -4
  562. package/dist/internal/hex-color.d.mts +22 -0
  563. package/dist/internal/hex-color.mjs +53 -0
  564. package/dist/internal/iso-date.d.mts +39 -0
  565. package/dist/internal/iso-date.mjs +98 -0
  566. package/dist/internal/to-precision.d.mts +2 -3
  567. package/dist/internal/to-precision.mjs +0 -8
  568. package/dist/styles/index.css +513 -2526
  569. package/dist/styles/tailwind.css +217 -17
  570. package/dist/styles/tokens.d.mts +1 -2
  571. package/dist/styles/tokens.mjs +1 -2
  572. package/dist/types/variables.d.mts +7 -9
  573. package/docs/GUIDE.md +239 -72
  574. package/docs/llms.txt +11 -7
  575. package/docs/props.generated.json +2156 -309
  576. package/docs/references/ai-chat.md +345 -62
  577. package/docs/references/color.md +222 -78
  578. package/docs/references/components.md +1664 -242
  579. package/docs/references/generative-ui.md +125 -17
  580. package/docs/references/interaction-design.md +65 -35
  581. package/docs/references/spatial-design.md +83 -63
  582. package/docs/references/types.md +101 -0
  583. package/docs/references/typography.md +159 -51
  584. package/package.json +64 -62
  585. package/dist/components/_internal/focus-ring.d.mts +0 -8
  586. package/dist/components/_internal/focus-ring.mjs +0 -8
  587. package/dist/components/layout/scroll-linked/index.d.mts +0 -2
  588. package/dist/components/layout/scroll-linked/index.mjs +0 -2
  589. package/dist/components/layout/scroll-linked/scroll-linked.d.mts +0 -14
  590. package/dist/components/layout/scroll-linked/scroll-linked.mjs +0 -59
  591. package/dist/helpers/cn.d.mts +0 -5
  592. package/dist/helpers/cn.mjs +0 -27
  593. package/dist/hooks/breakpoint/index.d.mts +0 -5
  594. package/dist/hooks/breakpoint/index.mjs +0 -24
  595. package/dist/hooks/client/index.d.mts +0 -4
  596. package/dist/hooks/client/index.mjs +0 -6
  597. package/dist/hooks/clipboard/index.d.mts +0 -7
  598. package/dist/hooks/clipboard/index.mjs +0 -15
  599. package/dist/hooks/debounced-transition/index.d.mts +0 -5
  600. package/dist/hooks/debounced-transition/index.mjs +0 -49
  601. package/dist/hooks/deferred-debounce/index.d.mts +0 -4
  602. package/dist/hooks/deferred-debounce/index.mjs +0 -9
  603. package/dist/hooks/disclosure/index.d.mts +0 -10
  604. package/dist/hooks/disclosure/index.mjs +0 -20
  605. package/dist/hooks/hover/index.d.mts +0 -12
  606. package/dist/hooks/hover/index.mjs +0 -19
  607. package/dist/hooks/intersection-observer/index.d.mts +0 -3
  608. package/dist/hooks/intersection-observer/index.mjs +0 -3
  609. package/dist/hooks/intersection-observer/use-in-view.d.mts +0 -11
  610. package/dist/hooks/intersection-observer/use-in-view.mjs +0 -18
  611. package/dist/hooks/intersection-observer/use-intersection-observer.d.mts +0 -11
  612. package/dist/hooks/intersection-observer/use-intersection-observer.mjs +0 -33
  613. package/dist/hooks/interval/index.d.mts +0 -4
  614. package/dist/hooks/interval/index.mjs +0 -19
  615. package/dist/hooks/resize/index.d.mts +0 -8
  616. package/dist/hooks/resize/index.mjs +0 -24
  617. package/dist/hooks/scroll-direction/index.d.mts +0 -13
  618. package/dist/hooks/scroll-direction/index.mjs +0 -67
  619. package/dist/hooks/scroll-lock/index.d.mts +0 -9
  620. package/dist/hooks/scroll-lock/index.mjs +0 -59
  621. package/dist/hooks/step/index.d.mts +0 -15
  622. package/dist/hooks/step/index.mjs +0 -39
  623. package/dist/hooks/timeout/index.d.mts +0 -4
  624. package/dist/hooks/timeout/index.mjs +0 -19
  625. package/dist/hooks/window-resize/index.d.mts +0 -11
  626. package/dist/hooks/window-resize/index.mjs +0 -21
  627. package/dist/hooks/window-size/index.d.mts +0 -8
  628. package/dist/hooks/window-size/index.mjs +0 -31
  629. package/dist/i18n/context.d.mts +0 -9
  630. package/dist/i18n/context.mjs +0 -19
  631. package/dist/styles/base.css +0 -315
  632. package/dist/styles/tokens.css +0 -380
  633. package/dist/styles/tokens.generated.d.mts +0 -840
  634. package/dist/styles/tokens.generated.mjs +0 -813
  635. package/dist/styles/utilities.css +0 -62
  636. package/docs/references/helpers.md +0 -223
  637. package/docs/references/hooks.md +0 -301
@@ -3,15 +3,15 @@
3
3
  ## Importing
4
4
 
5
5
  ```tsx
6
- // The stylesheet (required). In a Tailwind CSS 4 project,
7
- // import '@k8ordo/ui/tailwind.css' instead
6
+ // The stylesheet (required). In a Tailwind CSS 4 project, a CSS file
7
+ // imports 'tailwindcss' and then '@k8ordo/ui/tailwind.css' instead
8
8
  import '@k8ordo/ui/styles.css';
9
-
10
9
  // The provider (once, at the app root)
11
10
  import { UIProvider } from '@k8ordo/ui';
12
-
13
11
  // Components (all from the root entry)
14
12
  import { Button, Card, TextField } from '@k8ordo/ui';
13
+ // Except CodeBlock, which highlights on the server and has its own entry
14
+ import { CodeBlock } from '@k8ordo/ui/code-block';
15
15
  ```
16
16
 
17
17
  Every component can be rendered from a Server Component, compound ones
@@ -19,7 +19,59 @@ included: `Dialog.Root`, `Tabs.Root`, `Popover.Trigger`, and the rest are
19
19
  composed outside the client modules, so the parts resolve on the server. Only
20
20
  the interactive parts carry `'use client'`.
21
21
 
22
- ## Buttons and links
22
+ ## Render props
23
+
24
+ Two different contracts are spelled `render*`, and mixing them up is the usual
25
+ source of surprise.
26
+
27
+ **Replacing the element** — `renderItem` on `Button` and `IconButton`,
28
+ `renderAnchor` on `Anchor`, `Breadcrumb.Link`, and `SideNav.Link`. The component computes
29
+ everything and hands back the exact props it would have put on its own element,
30
+ so substituting an `<a>`, a framework `<Link>`, or your own button loses
31
+ nothing. For `Button` and `IconButton` that is the resolved `className`, the
32
+ composed `children` (icons and the pending spinner included), the click handler,
33
+ the disabled and pending state, the `ref`, and every `aria-*` / `data-*` /
34
+ native attribute the caller passed in. Spread the bag onto whatever you render.
35
+
36
+ - Handlers and the `ref` are typed for `HTMLElement` rather than for the
37
+ element the component would have rendered, so the bag spreads onto any tag.
38
+ - `disabled` and `type` are the only members that exist on `<button>` alone.
39
+ Destructure them away when you render something else — `aria-disabled` in the
40
+ same bag carries the state, and the supplied `onClick` already does nothing
41
+ and calls `preventDefault()` while disabled, so a disabled link will not
42
+ navigate. The examples below name them `_disabled` / `_type` so the discarded
43
+ bindings pass a `no-unused-vars` rule.
44
+ - `disabled` is only the `disabled` you passed. While an action is pending (see
45
+ [Button](#button)), the bag carries `aria-disabled` and `aria-busy` but
46
+ `disabled` stays `false`, so the pressed element keeps focus; the supplied
47
+ `onClick` is what ignores presses meanwhile.
48
+ - Spreading onto a real `<button>` is exact, but write `type` on the element
49
+ anyway: the `button-has-type` lint rule cannot see a `type` that arrives
50
+ through a spread.
51
+
52
+ The link components own less, so their bags are smaller:
53
+
54
+ - `Anchor` hands back `href`, `className`, `children` (with the new-tab icon on
55
+ an external link), `target` / `rel` on an external link, and every other
56
+ anchor attribute the caller passed, plus `kind` (`'internal'` |
57
+ `'external'`). `kind` is not an attribute: destructure it away before
58
+ spreading, or use it to choose between a framework `<Link>` and a plain `<a>`.
59
+ There is no `ref` and no disabled state.
60
+ - `Breadcrumb.Link` hands back `href`, `className`, and `children` only — it
61
+ takes no other attributes. A `current` link renders
62
+ `<span aria-current="page">` and does not call `renderAnchor`.
63
+ - `SideNav.Link` hands back `href`, `className`, `children`, `aria-current`
64
+ (`'page'` on the current link), and every other anchor attribute the caller
65
+ passed. A current link is still a link.
66
+
67
+ **Filling a slot** — `renderInput` on `FormControl`, `renderItem` on
68
+ `Popover.Trigger`, `Tooltip.Trigger`, `FileField.Trigger`, and `Alert`'s
69
+ `action`. Here the component owns no element of its own. It computes wiring —
70
+ ids, ARIA relationships, open/close handlers — and you supply the element that
71
+ wiring belongs to. Those bags carry only the wiring, and they are required
72
+ rather than optional.
73
+
74
+ ## Buttons
23
75
 
24
76
  ### Button
25
77
 
@@ -48,20 +100,34 @@ Props:
48
100
  - `fullWidth`: `boolean` (default: `false`)
49
101
  - `isActive`: `boolean` (default: `false`)
50
102
  - `onAction`: `() => void | Promise<void>`
51
- - `renderItem`: `(props: { className: string; children: ReactNode }) => ReactNode`
103
+ - `renderItem`: `(props: ButtonRenderItemProps) => ReactNode`
52
104
  - `size`: `'sm'` | `'md'` | `'lg'` (default: `'md'`)
53
105
  - `startIcon`: `ReactNode`
54
106
  - `type`: `'button'` | `'submit'` (default: `'button'`)
55
107
  - `variant`: `'solid'` | `'outline'` | `'skeleton'` (default: `'solid'`)
56
-
57
- Use the `renderItem` prop to render it as a link. The same applies to things like Next.js's `<Link>`.
108
+ - Other props are forwarded to `ComponentPropsWithRef<'button'>`, except `className` / `style`.
109
+
110
+ `onAction` runs inside a transition. While the promise it returns is pending —
111
+ and, with `type="submit"`, while the form's action is pending — the button shows
112
+ a spinner, carries `aria-busy` and `aria-disabled`, and ignores presses,
113
+ including the implicit submission of pressing Enter in a field. It does not
114
+ become natively `disabled`: Chromium moves focus to `body` when the focused
115
+ element is disabled, so a keyboard user who pressed the button would lose their
116
+ place. The `disabled` you pass is native, and takes the button out of the tab
117
+ order.
118
+
119
+ `renderItem` replaces the `<button>`; see [Render props](#render-props) for the
120
+ contract. It receives `className`, the composed `children`, `ref`, `type`,
121
+ `disabled`, `aria-disabled`, `aria-busy`, `onClick`, and every other attribute
122
+ passed to `Button`. Use it to render a link — the same shape works for things
123
+ like Next.js's `<Link>`.
58
124
 
59
125
  ```tsx
60
126
  <Button
61
127
  color="base"
62
128
  variant="outline"
63
- renderItem={({ className, children }) => (
64
- <a className={className} href="/page">
129
+ renderItem={({ children, disabled: _disabled, type: _type, ...props }) => (
130
+ <a href="/page" {...props}>
65
131
  {children}
66
132
  </a>
67
133
  )}
@@ -88,29 +154,34 @@ Props:
88
154
  - `children`: `ReactNode`
89
155
  - `color`: `'transparent'` | `'base'` | `'primary'` | `'secondary'` (default: `'transparent'`)
90
156
  - `onAction`: `() => void | Promise<void>`
91
- - `renderItem`: `(props: { className: string; children: ReactNode; 'aria-label': string; triggerProps: IconButtonTriggerProps; }) => ReactNode`
157
+ - `renderItem`: `(props: IconButtonRenderItemProps) => ReactNode`
92
158
  - `size`: `'sm'` | `'md'` | `'lg'` (default: `'md'`)
93
159
  - `tooltipDisabled`: `boolean` (default: `false`)
94
160
  - `tooltipPlacement`: `Placement` (default: `'top'`)
161
+ - Other props are forwarded to `ComponentPropsWithRef<'button'>`, except `type` / `className` / `style`.
162
+
163
+ `onAction` keeps the button busy the way `Button`'s does — `aria-busy`,
164
+ `aria-disabled`, presses ignored, focus kept — without the spinner.
95
165
 
96
- Use the `renderItem` prop to render it as a link. Spreading `triggerProps` onto the `<a>` shows `label` as a tooltip on hover and focus.
166
+ `renderItem` replaces the `<button>` under the same contract as `Button`'s,
167
+ with one addition: the tooltip wiring is kept in a nested `triggerProps` so it
168
+ stays free of any element type. Spread it too, and `label` shows as a tooltip on
169
+ hover and focus. `triggerProps` also carries the merged `ref` and any
170
+ `onMouseEnter` / `onMouseLeave` / `onFocus` / `onBlur` the caller passed, so the
171
+ flat part of the bag holds neither.
97
172
 
98
173
  ```tsx
99
174
  <IconButton
100
175
  color="base"
101
176
  label="Mail"
102
177
  renderItem={({
103
- className,
104
178
  children,
105
- 'aria-label': ariaLabel,
179
+ disabled: _disabled,
106
180
  triggerProps,
181
+ type: _type,
182
+ ...props
107
183
  }) => (
108
- <a
109
- aria-label={ariaLabel}
110
- className={className}
111
- href="/contact"
112
- {...triggerProps}
113
- >
184
+ <a href="/contact" {...props} {...triggerProps}>
114
185
  {children}
115
186
  </a>
116
187
  )}
@@ -119,60 +190,97 @@ Use the `renderItem` prop to render it as a link. Spreading `triggerProps` onto
119
190
  </IconButton>
120
191
  ```
121
192
 
122
- ### Anchor
193
+ ### CopyButton
123
194
 
124
- A text link. External links automatically get a new-tab icon.
195
+ A button that copies text to the clipboard and shows that it did: its icon
196
+ turns into a check for two seconds (an error icon if the write fails), and a
197
+ live region beside it announces `copied` / `copyFailed` from the dictionary. The
198
+ button's name stays the same throughout. By default it is an outline `Button`
199
+ with its `label` as text; `iconOnly` makes it a transparent `IconButton` whose
200
+ `label` is the tooltip.
125
201
 
126
202
  ```tsx
127
- import { Anchor } from '@k8ordo/ui';
203
+ import { CopyButton } from '@k8ordo/ui';
128
204
 
129
- <Anchor href="https://example.com">External link</Anchor>
130
- <Anchor href="/about">Internal link</Anchor>
131
- <Anchor href="/docs" openInNewTab>Open in a new tab</Anchor>
205
+ <CopyButton value={css} label="Copy CSS" size="sm" />;
206
+
207
+ // Icon only, e.g. in the header of a code block
208
+ <CopyButton value={code} label="Copy code" iconOnly size="sm" />;
209
+
210
+ // Build the text when the button is pressed: read the DOM, or fetch it
211
+ <CopyButton
212
+ label="Copy as Markdown"
213
+ value={async () => (await fetch(`/blog/${slug}.md`)).text()}
214
+ />;
132
215
  ```
133
216
 
217
+ `value` may be a function, and it may return a `Promise`. It is called on the
218
+ click, and the promise goes to the clipboard as it is (a `ClipboardItem`), so the
219
+ write starts inside the click even when the text arrives later. Safari refuses
220
+ a write that begins after an `await`.
221
+
134
222
  Props:
135
223
 
136
- - `children`: `ReactNode` (required)
137
- - `href`: `T` (required)
138
- - `openInNewTab`: `boolean` (default: `false`)
139
- - `renderAnchor`: `(props: RenderAnchorProps<T>) => ReactNode` (default: `defaultRenderAnchor`)
224
+ - `value`: `string` | `(() => string | Promise<string>)` (required)
225
+ - `disabled`: `boolean` (default: `false`)
226
+ - `iconOnly`: `boolean` (default: `false`)
227
+ - `label`: `string`
228
+ - `size`: `'sm'` | `'md'` | `'lg'` (default: `'md'`)
140
229
 
141
- ## Layout and navigation
230
+ ### Toolbar
142
231
 
143
- ### Accordion
232
+ A group of controls that takes one Tab stop (the WAI-ARIA toolbar). Arrow keys
233
+ move between the items — left/right when `orientation` is `horizontal`, up/down
234
+ when `vertical`, and in vertical writing mode the other pair, since the row
235
+ itself runs vertically — `Home` / `End` jump to the ends, and disabled items are
236
+ skipped. Tab leaves the toolbar, and coming back lands on the item last focused.
144
237
 
145
- A collapsible section, as a compound component.
238
+ Each item is a slot: `Toolbar.Item`'s `renderItem` receives `ref`, `tabIndex`,
239
+ and `onFocus` (`ToolbarItemProps`) to spread onto a `Button`, an `IconButton`,
240
+ or any other button. A toggle item sets `aria-pressed`; the toolbar shades a
241
+ pressed item. The root needs a name (`aria-label` or `aria-labelledby`).
242
+ Items are meant to be buttons: a control that uses the arrow keys itself (a text
243
+ field, a slider) would fight the toolbar for them.
146
244
 
147
245
  ```tsx
148
- import { Accordion } from '@k8ordo/ui';
149
-
150
- <Accordion.Root>
151
- <Accordion.Item>
152
- <Accordion.Button>Section 1</Accordion.Button>
153
- <Accordion.Panel>Content</Accordion.Panel>
154
- </Accordion.Item>
155
- </Accordion.Root>;
246
+ import { IconButton, Toolbar } from '@k8ordo/ui';
247
+
248
+ <Toolbar.Root aria-label="Formatting">
249
+ <Toolbar.Item
250
+ renderItem={(props) => (
251
+ <IconButton {...props} label="Copy">
252
+ <CopyIcon />
253
+ </IconButton>
254
+ )}
255
+ />
256
+ <Toolbar.Separator />
257
+ <Toolbar.Item
258
+ renderItem={(props) => (
259
+ <IconButton
260
+ {...props}
261
+ aria-pressed={isList}
262
+ label="Bulleted list"
263
+ onClick={toggleList}
264
+ >
265
+ <ListIcon />
266
+ </IconButton>
267
+ )}
268
+ />
269
+ </Toolbar.Root>;
156
270
  ```
157
271
 
158
- Props (Accordion.Button):
159
-
160
- - `children`: `ReactNode`
161
-
162
- Props (Accordion.Item):
272
+ Props (Root):
163
273
 
164
274
  - `children`: `ReactNode`
165
- - `defaultOpen`: `boolean` (default: `false`)
166
- - `isOpen`: `boolean`
167
- - `onChange`: `(isOpen: boolean) => void`
168
-
169
- Props (Accordion.Panel):
275
+ - `orientation`: `'horizontal'` | `'vertical'` (default: `'horizontal'`)
276
+ - `ref`: `Ref<HTMLDivElement>`
277
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style` / `role` / `aria-orientation`.
170
278
 
171
- - `children`: `ReactNode`
279
+ Props (Toolbar.Item):
172
280
 
173
- Props (Accordion.Root):
281
+ - `renderItem`: `(props: ToolbarItemProps) => ReactElement` (required)
174
282
 
175
- - `children`: `ReactNode`
283
+ ## Navigation
176
284
 
177
285
  ### Breadcrumb
178
286
 
@@ -210,6 +318,9 @@ Props (Breadcrumb.Link):
210
318
  - `current`: `boolean` (default: `false`)
211
319
  - `renderAnchor`: `(props: RenderBreadcrumbAnchorProps<T>) => ReactNode` (default: `defaultRenderBreadcrumbAnchor`)
212
320
 
321
+ `Breadcrumb.Link`'s `renderAnchor` receives `href`, `className`, and `children`;
322
+ see [Render props](#render-props).
323
+
213
324
  Props (Breadcrumb.Item):
214
325
 
215
326
  - `children`: `ReactNode`
@@ -234,164 +345,250 @@ Props:
234
345
  - `nextLabel`: `string`
235
346
  - `prevLabel`: `string`
236
347
  - `ref`: `Ref<HTMLElement>`
348
+ - Other props are forwarded to `HTMLAttributes<HTMLElement>`, except `className` / `style` / `children`.
237
349
 
238
- ### Tabs
350
+ ### Stepper
239
351
 
240
- Tab switching, as a compound component.
352
+ The steps of a process in order, as an `<ol>`: done steps show a check and are
353
+ read with the built-in `stepperComplete` wording, and the current step carries
354
+ `aria-current="step"`. `value` is the index of the current step (0-based);
355
+ `steps.length` marks every step done. With `interactive`, done steps become
356
+ buttons that go back to them (`onChange` receives the index); steps ahead are
357
+ never buttons. Name the list with `aria-label`.
241
358
 
242
- ```tsx
243
- import { Tabs } from '@k8ordo/ui';
359
+ `orientation` is logical: `horizontal` lays the steps along the line and
360
+ `vertical` stacks them, so inside `writing-v` they run top to bottom and right
361
+ to left respectively, with the connecting lines drawn to match and the step
362
+ numbers kept upright. In `horizontal`, every step takes an equal share of the
363
+ width: the first row holds only the step markers and the lines between them,
364
+ and each step's label and description sit under its marker, wrapping within
365
+ that share. The lines keep their length however long the labels are, and a
366
+ narrow container wraps the text rather than squeezing the lines.
244
367
 
245
- <Tabs.Root ids={['tab1', 'tab2']}>
246
- <Tabs.List label="Tabs">
247
- <Tabs.Tab id="tab1">Tab 1</Tabs.Tab>
248
- <Tabs.Tab id="tab2">Tab 2</Tabs.Tab>
249
- </Tabs.List>
250
- <Tabs.Panel id="tab1">Panel 1</Tabs.Panel>
251
- <Tabs.Panel id="tab2">Panel 2</Tabs.Panel>
252
- </Tabs.Root>;
253
- ```
368
+ In a multi-step form (see `@k8ordo/form`'s guide), keep every step mounted and
369
+ drive `value` from the step you show.
254
370
 
255
- Props (Tabs.Root):
371
+ ```tsx
372
+ import { Stepper } from '@k8ordo/ui';
373
+
374
+ <Stepper
375
+ aria-label="Sign-up"
376
+ interactive
377
+ onChange={setStep}
378
+ steps={[
379
+ { label: 'Plan', description: 'Pick what fits' },
380
+ { label: 'Payment' },
381
+ { label: 'Review' },
382
+ ]}
383
+ value={step}
384
+ />;
385
+ ```
256
386
 
257
- - `ids`: `[string, ...string[]]` (required)
258
- - `children`: `ReactNode`
259
- - `defaultSelectedId`: `string` | `null` (default: `null`)
260
- - `onChange`: `(id: string) => void`
261
- - `selectedId`: `string`
387
+ Props:
262
388
 
263
- Props (Tabs.List):
389
+ - `steps`: `readonly StepperStep[]` (required)
390
+ - `defaultValue`: `number`
391
+ - `interactive`: `boolean` (default: `false`)
392
+ - `onChange`: `(value: number) => void`
393
+ - `orientation`: `'horizontal'` | `'vertical'` (default: `'horizontal'`)
394
+ - `ref`: `Ref<HTMLOListElement>`
395
+ - `value`: `number`
396
+ - Other props are forwarded to `OlHTMLAttributes<HTMLOListElement>`, except `className` / `style` / `children`.
264
397
 
265
- - `label`: `string` (required)
266
- - `children`: `ReactNode`
398
+ ### SideNav
267
399
 
268
- Props (Tabs.Panel):
400
+ Side navigation: groups of links, each under a small title, with the page being
401
+ shown marked by a bar (`aria-current="page"`). The library has no router, so
402
+ you say which link is current, and swap the `<a>` for your router's link with
403
+ `renderAnchor` — it replaces the element, and its bag holds `href`,
404
+ `className`, `children`, `aria-current`, and every other anchor attribute you
405
+ passed (an `onClick` that closes a drawer, say). Every part renders from a
406
+ Server Component.
269
407
 
270
- - `id`: `string` (required)
271
- - `children`: `ReactNode`
408
+ ```tsx
409
+ import { SideNav } from '@k8ordo/ui';
410
+
411
+ <SideNav.Root label="Components">
412
+ <SideNav.Group title="Buttons">
413
+ <SideNav.Link current={pathname === '/button'} href="/button">
414
+ Button
415
+ </SideNav.Link>
416
+ <SideNav.Link
417
+ href="/icon-button"
418
+ renderAnchor={({ children, ...props }) => (
419
+ <Link {...props}>{children}</Link>
420
+ )}
421
+ >
422
+ IconButton
423
+ </SideNav.Link>
424
+ </SideNav.Group>
425
+ </SideNav.Root>;
426
+ ```
272
427
 
273
- Props (Tabs.Tab):
428
+ Props (SideNav.Root):
274
429
 
275
- - `id`: `string` (required)
430
+ - `label`: `string` (required)
276
431
  - `children`: `ReactNode`
432
+ - Other props are forwarded to `HTMLAttributes<HTMLElement>`, except `className` / `style` / `aria-label`.
277
433
 
278
- ### Card
279
-
280
- A card that groups content.
434
+ A group can hold a level that opens and closes, `SideNav.Sub`, to make the
435
+ navigation a tree. It is a `<details>`, so it opens and closes without
436
+ JavaScript; open the one holding the current page with `defaultOpen`.
281
437
 
282
438
  ```tsx
283
- import { Card } from '@k8ordo/ui';
439
+ <SideNav.Group title="Components">
440
+ <SideNav.Link href="/components">Overview</SideNav.Link>
441
+ <SideNav.Sub defaultOpen title="Buttons">
442
+ <SideNav.Link current href="/button">
443
+ Button
444
+ </SideNav.Link>
445
+ </SideNav.Sub>
446
+ <SideNav.Sub title="Navigation">
447
+ <SideNav.Link href="/tabs">Tabs</SideNav.Link>
448
+ </SideNav.Sub>
449
+ </SideNav.Group>
450
+ ```
284
451
 
285
- // Static card
286
- <Card width="full" variant="shadow">
287
- <div className="p-6">Content</div>
288
- </Card>
452
+ Props (SideNav.Group):
289
453
 
290
- // Clickable card (hover:scale-[1.02], active:scale-[0.98])
291
- <Card variant="outline" interactive>
292
- <div className="p-6">Content</div>
293
- </Card>
294
- ```
454
+ - `title`: `string` (required)
455
+ - `children`: `ReactNode`
295
456
 
296
- Props:
457
+ Props (SideNav.Sub):
297
458
 
459
+ - `title`: `string` (required)
298
460
  - `children`: `ReactNode`
299
- - `interactive`: `boolean` (default: `false`)
300
- - `variant`: `'shadow'` | `'outline'` (default: `'shadow'`)
301
- - `width`: `'full'` | `'fit'` (default: `'full'`)
461
+ - `defaultOpen`: `boolean` (default: `false`)
302
462
 
303
- ### Separator
463
+ Props (SideNav.Link):
304
464
 
305
- A dividing rule.
465
+ - `children`: `ReactNode` (required)
466
+ - `href`: `T` (required)
467
+ - `current`: `boolean` (default: `false`)
468
+ - `renderAnchor`: `(props: RenderSideNavAnchorProps<T>) => ReactNode` (default: `defaultRenderAnchor`)
469
+ - Other props are forwarded to `AnchorHTMLAttributes<HTMLAnchorElement>`, except `className` / `style` / `aria-current`.
470
+
471
+ ### TableOfContents
472
+
473
+ The contents of the page, with the heading being read marked
474
+ (`aria-current="location"`). Pass the headings as a tree of `{ id, label,
475
+ children? }`; each item links to `#id`.
306
476
 
307
477
  ```tsx
308
- import { Separator } from '@k8ordo/ui';
478
+ import { TableOfContents } from '@k8ordo/ui';
309
479
 
310
- <Separator />
311
- <Separator color="mute" />
312
- <Separator color="subtle" />
313
- <Separator orientation="vertical" />
480
+ <TableOfContents
481
+ items={[
482
+ { id: 'install', label: 'Install' },
483
+ {
484
+ id: 'usage',
485
+ label: 'Usage',
486
+ children: [{ id: 'usage-basic', label: 'Basics' }],
487
+ },
488
+ ]}
489
+ />;
314
490
  ```
315
491
 
492
+ - The heading being read is the last one whose start has passed the heading's
493
+ own `scroll-margin-block-start`: set the margin your sticky header needs on
494
+ the headings, and a heading reached from the contents becomes the current
495
+ one. At the end of the document, the last heading is current even if its
496
+ section is too short to reach that line.
497
+ - It follows the document's scroll: the headings are expected to scroll with
498
+ the page, and `items` to be in document order. It measures on scroll, on
499
+ resize, and when the page's size changes. In a vertical document it reads
500
+ from right to left (`vertical-rl`) or left to right (`vertical-lr`).
501
+ - `label` replaces the title (`tableOfContents` in the message dictionary),
502
+ which also names the `nav`.
503
+
316
504
  Props:
317
505
 
318
- - `color`: `'base'` | `'mute'` | `'subtle'` (default: `'base'`)
319
- - `orientation`: `'horizontal'` | `'vertical'` (default: `'horizontal'`)
506
+ - `items`: `readonly TableOfContentsItem[]` (required)
507
+ - `label`: `string`
508
+ - Other props are forwarded to `HTMLAttributes<HTMLElement>`, except `className` / `style` / `children` / `aria-labelledby`.
320
509
 
321
- ### ScrollLinked
510
+ ### Tabs
322
511
 
323
- Shows scroll progress as a progress bar.
512
+ Tab switching, as a compound component.
324
513
 
325
514
  ```tsx
326
- import { ScrollLinked } from '@k8ordo/ui';
515
+ import { Tabs } from '@k8ordo/ui';
327
516
 
328
- <ScrollLinked />
329
- <ScrollLinked container={containerRef} />
517
+ <Tabs.Root ids={['tab1', 'tab2']}>
518
+ <Tabs.List label="Tabs">
519
+ <Tabs.Tab id="tab1">Tab 1</Tabs.Tab>
520
+ <Tabs.Tab id="tab2">Tab 2</Tabs.Tab>
521
+ </Tabs.List>
522
+ <Tabs.Panel id="tab1">Panel 1</Tabs.Panel>
523
+ <Tabs.Panel id="tab2">Panel 2</Tabs.Panel>
524
+ </Tabs.Root>;
330
525
  ```
331
526
 
332
- Props:
527
+ Selecting a tab is a transition: the panels cross-fade through React's
528
+ `<ViewTransition>` (off under `prefers-reduced-motion`), and a panel that
529
+ suspends keeps the current one on screen until it is ready.
333
530
 
334
- - `container`: `RefObject<HTMLElement | null>`
531
+ The keys follow the WAI-ARIA tabs pattern, and focusing a tab selects it.
532
+ `ArrowLeft` / `ArrowRight` move to the previous / next tab, wrapping at the
533
+ ends, and swap under `dir="rtl"`; in vertical writing mode the list is
534
+ vertical and `ArrowUp` / `ArrowDown` do the same. `Home` / `End` go to the
535
+ first / last tab.
335
536
 
336
- ### Stack
537
+ Props (Tabs.Root):
337
538
 
338
- Lays children out along one axis. Pick `gap` from the spacing tokens.
539
+ - `ids`: `[string, ...string[]]` (required)
540
+ - `children`: `ReactNode`
541
+ - `defaultSelectedId`: `string` | `null` (default: `null`)
542
+ - `onChange`: `(id: string) => void`
543
+ - `selectedId`: `string`
339
544
 
340
- ```tsx
341
- import { Stack } from '@k8ordo/ui';
545
+ Props (Tabs.List):
342
546
 
343
- <Stack gap="lg">
344
- <Card>1</Card>
345
- <Card>2</Card>
346
- </Stack>
547
+ - `label`: `string` (required)
548
+ - `children`: `ReactNode`
347
549
 
348
- <Stack direction="row" justify="between" align="center">
349
- <Heading level="h2">Title</Heading>
350
- <Button>Action</Button>
351
- </Stack>
352
- ```
550
+ Props (Tabs.Panel):
353
551
 
354
- Props:
552
+ - `id`: `string` (required)
553
+ - `children`: `ReactNode`
355
554
 
356
- - `align`: `'start'` | `'center'` | `'end'` | `'stretch'`
555
+ Props (Tabs.Tab):
556
+
557
+ - `id`: `string` (required)
357
558
  - `children`: `ReactNode`
358
- - `direction`: `'row'` | `'column'` (default: `'column'`)
359
- - `gap`: `GapSize` (default: `'md'`)
360
- - `justify`: `'start'` | `'center'` | `'end'` | `'between'`
361
- - `padding`: `PaddingSize`
362
559
 
363
- ### Grid
560
+ ### Anchor
364
561
 
365
- A grid layout. Passing `'auto-fill'` or `'auto-fit'` to `cols` wraps with `minItemSize` as the lower bound.
562
+ A text link. External links automatically get a new-tab icon.
366
563
 
367
564
  ```tsx
368
- import { Grid } from '@k8ordo/ui';
369
-
370
- <Grid cols={3} gap="md">
371
- <Card>1</Card>
372
- <Card>2</Card>
373
- <Card>3</Card>
374
- </Grid>
565
+ import { Anchor } from '@k8ordo/ui';
375
566
 
376
- <Grid cols="auto-fill" minItemSize={64}>
377
- {items.map((item) => (
378
- <Card key={item.id}>{item.name}</Card>
379
- ))}
380
- </Grid>
567
+ <Anchor href="https://example.com">External link</Anchor>
568
+ <Anchor href="/about">Internal link</Anchor>
569
+ <Anchor href="/docs" openInNewTab>Open in a new tab</Anchor>
381
570
  ```
382
571
 
383
572
  Props:
384
573
 
385
- - `children`: `ReactNode`
386
- - `cols`: `1` | `2` | `3` | `4` | `5` | `6` | `'auto-fill'` | `'auto-fit'` (default: `'auto-fill'`)
387
- - `gap`: `GapSize` (default: `'md'`)
388
- - `minItemSize`: `24` | `32` | `40` | `48` | `64` | `80` (default: `48`)
574
+ - `children`: `ReactNode` (required)
575
+ - `href`: `T` (required)
576
+ - `openInNewTab`: `boolean` (default: `false`)
577
+ - `renderAnchor`: `(props: RenderAnchorProps<T>) => ReactNode` (default: `defaultRenderAnchor`)
578
+ - Other props are forwarded to `AnchorHTMLAttributes<HTMLAnchorElement>`, except `target` / `rel` / `className` / `style`.
579
+
580
+ `renderAnchor` replaces the `<a>`; its bag is described under
581
+ [Render props](#render-props).
389
582
 
390
583
  ## Forms
391
584
 
392
- Form components are used together with `FormControl`'s `renderInput` pattern. Every form component supports both controlled and uncontrolled use.
585
+ Form components are used together with `FormControl`'s `renderInput` pattern. Every form component except `FileField` supports both controlled and uncontrolled use; `FileField.Root` takes only `defaultValue` and keeps the selected files itself.
586
+
587
+ `ref` reaches the real element (`input` / `textarea` / `select` / `fieldset`). `FileField` uses a ref internally but composes it with yours, so the `ref` you pass still reaches the element. `Radio` (a group that renders several inputs) puts its `ref` on the radiogroup `<div>`, and `FormControl` (a wrapper) on its wrapper element — a `<div>`, or a `<fieldset>` with `labelAs="legend"`.
393
588
 
394
- `ref` reaches the real element (`input` / `textarea` / `select` / `fieldset`). `Textarea` and `FileField` use a ref internally but compose it with yours, so `react-hook-form`'s `register()` and the like can be passed straight through. `Radio` (a group that renders several inputs) and `FormControl` (a wrapper) do not take a `ref`.
589
+ An uncontrolled field keeps its value in the DOM, so a form reset — `form.reset()`, a reset button, or React resetting the form after an action — puts it back to its `defaultValue`, and a `defaultValue` that changes after mount (the values a failed submission echoes back) is where the next reset goes. A value a component changes in code (a stepper, a chosen option, a removed file) is announced with an `input` event, so a form library listening on the `<form>` hears it like typing.
590
+
591
+ Every field takes the attributes `@k8ordo/form`'s `formFields` derives as they are — spread `field.input` after `FormControl`'s props. Its guide's "Working with @k8ordo/ui" section has one example per component.
395
592
 
396
593
  ### Form
397
594
 
@@ -414,6 +611,8 @@ Props:
414
611
 
415
612
  - `children`: `ReactNode` (required)
416
613
  - `action`: `((formData: FormData) => void | Promise<void>)` | `string`
614
+ - `ref`: `Ref<HTMLFormElement>`
615
+ - Other props are forwarded to `FormHTMLAttributes<HTMLFormElement>`, except `className` / `style`.
417
616
 
418
617
  ### FormControl
419
618
 
@@ -444,6 +643,7 @@ Props:
444
643
  - `labelAs`: `'label'` | `'legend'` (default: `'label'`)
445
644
  - `ref`: `Ref<HTMLElement>`
446
645
  - `required`: `boolean` (default: `false`)
646
+ - Other props are forwarded to `HTMLAttributes<HTMLElement>`, except `className` / `style` / `children`.
447
647
 
448
648
  `renderInput` receives `{ id, 'aria-describedby', 'aria-labelledby', disabled, invalid, required }`.
449
649
 
@@ -464,14 +664,21 @@ import { TextField } from '@k8ordo/ui';
464
664
 
465
665
  // type can be passed too (default: "text")
466
666
  <TextField id="tel" type="tel" inputMode="numeric" />
667
+
668
+ // date and time inputs render as the browser's own pickers
669
+ <TextField id="birthday" type="date" />
467
670
  ```
468
671
 
672
+ `type` also accepts any string, so the `type` `@k8ordo/form` derives
673
+ (`email`, `url`, `date`, `time`, `datetime-local`, …) spreads as it is.
674
+
469
675
  Props:
470
676
 
471
677
  - `children`: `ReactNode`
472
678
  - `invalid`: `boolean` (default: `false`)
473
679
  - `ref`: `Ref<HTMLInputElement>`
474
- - `type`: `TextInputType` (default: `'text'`)
680
+ - `type`: `'date'` | `'datetime-local'` | `'email'` | `'month'` | `'search'` | `'tel'` | `'text'` | `'time'` | `'url'` | `'week'` | `(string & Record<never, never>)` (default: `'text'`)
681
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `className` / `style`.
475
682
 
476
683
  ### Textarea
477
684
 
@@ -495,35 +702,178 @@ Props:
495
702
  - `fullHeight`: `boolean` (default: `false`)
496
703
  - `invalid`: `boolean` (default: `false`)
497
704
  - `ref`: `Ref<HTMLTextAreaElement>`
705
+ - `type`: `string`
706
+ - Other props are forwarded to `TextareaHTMLAttributes<HTMLTextAreaElement>`, except `className` / `style`.
707
+
708
+ `type` is accepted and dropped: a `<textarea>` has none, and the attributes
709
+ `@k8ordo/form` derives for a string carry `type="text"`. A `pattern` is not
710
+ checked by the browser on a `<textarea>` either.
498
711
 
499
712
  ### NumberField
500
713
 
501
714
  ```tsx
502
715
  import { NumberField } from '@k8ordo/ui';
503
716
 
717
+ // Uncontrolled: starts empty unless defaultValue is given
718
+ <NumberField
719
+ id="quantity"
720
+ name="quantity"
721
+ min={0}
722
+ max={100}
723
+ invalid={false}
724
+ disabled={false}
725
+ required={false}
726
+ />;
727
+
728
+ // Controlled: null is an empty field
729
+ const [value, setValue] = useState<number | null>(null);
504
730
  <NumberField
505
731
  id="quantity"
506
732
  min={0}
507
733
  max={100}
508
734
  value={value}
509
- onChange={onChange}
735
+ onChange={setValue}
510
736
  invalid={false}
511
737
  disabled={false}
512
738
  required={false}
513
739
  />;
514
740
  ```
515
741
 
742
+ An empty field is `null`, never `0`. While empty the input submits `''` and
743
+ has no `aria-valuenow`, and leaving it keeps it empty; clearing the field
744
+ reports `null` to `onChange`. The first ArrowUp / ArrowDown or stepper press on
745
+ an empty field fills in `0`, or the nearer of `min` / `max` when `0` is out of
746
+ range. `required` reaches the input itself, so an empty required field fails
747
+ native validation.
748
+
749
+ The input is `type="text"` (so it can format to `precision` and step with the
750
+ arrow keys), which the browser does not range-check — so the field does it: a
751
+ value below `min` or above `max` is reported with `setCustomValidity`, using
752
+ the `numberFieldRangeUnderflow` / `numberFieldRangeOverflow` wording. Leaving the
753
+ field clamps the value into range. A message set by someone else on the same
754
+ input — a form library's cross-field rule — is left alone.
755
+
756
+ `min`, `max`, and an uncontrolled `defaultValue` also take strings, and `step`
757
+ takes `'any'`, which is what `@k8ordo/form` derives. `precision` defaults to the
758
+ number of decimals in `step`; with `step="any"` the value is not rounded.
759
+
760
+ A value the field writes itself — an arrow key, a stepper press, formatting on
761
+ blur — is announced with one `input` event, and `onChange` is still called once.
762
+
763
+ A form reset — `form.reset()`, a reset button, or React resetting the form after
764
+ an action — puts an uncontrolled field back to `defaultValue` (or to empty) and
765
+ reports that value to `onChange`. A controlled field keeps its `value`; reset
766
+ your own state from the form's `onReset`.
767
+
516
768
  Props:
517
769
 
518
- - `defaultValue`: `never`
770
+ - `defaultValue`: `number` | `string`
519
771
  - `invalid`: `boolean` (default: `false`)
520
- - `max`: `number` (default: `9_007_199_254_740_991`)
521
- - `min`: `number` (default: `-9_007_199_254_740_991`)
522
- - `onChange`: `(value: number) => void`
523
- - `precision`: `number` (default: `0`)
772
+ - `max`: `number` | `string`
773
+ - `min`: `number` | `string`
774
+ - `onChange`: `(value: number | null) => void`
775
+ - `precision`: `number`
524
776
  - `ref`: `Ref<HTMLInputElement>`
525
- - `step`: `number` (default: `1`)
526
- - `value`: `number`
777
+ - `step`: `number` | `'any'` (default: `1`)
778
+ - `value`: `number` | `null`
779
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `type` / `role` / `className` / `style` / `children`.
780
+
781
+ ### DateField
782
+
783
+ A native `<input type="date">` styled like `TextField`. The value is a
784
+ `YYYY-MM-DD` string (`''` when empty), and the browser checks `min` / `max` /
785
+ `required` itself. `onChange` is the native event, as with `TextField`.
786
+
787
+ It accepts what `@k8ordo/form` derives from `z.iso.date()` as is: spread the
788
+ field's `input` and nothing needs to be taken out. `type` is always `date`, so a
789
+ `type` in the spread does not replace it.
790
+
791
+ ```tsx
792
+ import { DateField, FormControl } from '@k8ordo/ui';
793
+
794
+ const eventDate = form.field('eventDate'); // z.iso.date()
795
+
796
+ <FormControl
797
+ errorText={eventDate.error}
798
+ invalid={eventDate.invalid}
799
+ label="Date"
800
+ required={eventDate.required}
801
+ renderInput={(props) => <DateField {...props} {...eventDate.input} />}
802
+ />;
803
+ ```
804
+
805
+ Props:
806
+
807
+ - `invalid`: `boolean` (default: `false`)
808
+ - `ref`: `Ref<HTMLInputElement>`
809
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `className` / `style` / `type` / `children`.
810
+
811
+ ### DatePicker
812
+
813
+ `DateField` with a button that opens `Calendar` in a popover. The field is the
814
+ same native `<input type="date">`, so typing a date, `name`, `required`, and
815
+ `min` / `max` all work as they do on `DateField`, and spreading a derived
816
+ `@k8ordo/form` field works the same way. `onChange` takes the value
817
+ (`YYYY-MM-DD`, `''` when cleared), not the event, because a date picked from the
818
+ calendar has no input event of its own.
819
+
820
+ Picking a date writes it into the input and dispatches an `input` event, so a
821
+ form sees it exactly as if it had been typed (dirty state, rules, and clearing
822
+ an error). The popover closes and focus returns to the calendar button.
823
+
824
+ Firefox draws its own calendar button inside every `<input type="date">` and
825
+ offers no way to hide it, so there the field shows two calendar buttons: the
826
+ browser's and this component's. Chromium and Safari show only this one.
827
+
828
+ ```tsx
829
+ import { DatePicker } from '@k8ordo/ui';
830
+
831
+ <DatePicker
832
+ aria-label="Check-in"
833
+ min="2026-01-01"
834
+ name="checkIn"
835
+ onChange={setCheckIn}
836
+ value={checkIn}
837
+ />;
838
+ ```
839
+
840
+ Props:
841
+
842
+ - `defaultValue`: `string`
843
+ - `invalid`: `boolean` (default: `false`)
844
+ - `onChange`: `(value: string) => void`
845
+ - `ref`: `Ref<HTMLInputElement>`
846
+ - `value`: `string`
847
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `className` / `style` / `type` / `children`.
848
+
849
+ ### Calendar
850
+
851
+ A month grid for picking one day (the WAI-ARIA date picker grid). The value is
852
+ a `YYYY-MM-DD` string. Arrow keys move by day and week, `Home` / `End` to the
853
+ ends of the week, `PageUp` / `PageDown` by month (with `Shift`, by year), and
854
+ `Enter` / `Space` select. Days outside `min` / `max` stay focusable but cannot
855
+ be selected.
856
+
857
+ Month and weekday names, and the first day of the week, follow the same locale
858
+ as the built-in wording (i18n, below). Today is marked with
859
+ `aria-current="date"` in the visitor's time zone, which only the browser knows,
860
+ so the calendar renders in the browser alone: the server writes an empty box of
861
+ the same size. It submits nothing; inside a form, use `DatePicker` or
862
+ `DateField`.
863
+
864
+ ```tsx
865
+ import { Calendar } from '@k8ordo/ui';
866
+
867
+ <Calendar defaultValue="2026-09-25" max="2026-12-31" onChange={setDay} />;
868
+ ```
869
+
870
+ Props:
871
+
872
+ - `defaultValue`: `string`
873
+ - `max`: `string`
874
+ - `min`: `string`
875
+ - `onChange`: `(value: string) => void`
876
+ - `value`: `string` | `null`
527
877
 
528
878
  ### PasswordInput
529
879
 
@@ -551,6 +901,10 @@ Props:
551
901
  - `invalid`: `boolean` (default: `false`)
552
902
  - `ref`: `Ref<HTMLInputElement>`
553
903
  - `showLabel`: `string`
904
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `type` / `className` / `style`.
905
+
906
+ A spread `type="password"` (what `@k8ordo/form` derives for a password) does not
907
+ override the show/hide toggle.
554
908
 
555
909
  ### Select
556
910
 
@@ -577,10 +931,29 @@ Props:
577
931
  - `children`: `ReactNode`
578
932
  - `invalid`: `boolean` (default: `false`)
579
933
  - `ref`: `Ref<HTMLSelectElement>`
934
+ - Other props are forwarded to `SelectHTMLAttributes<HTMLSelectElement>`, except `className` / `style`.
935
+
936
+ React applies a `<select>`'s `defaultValue` only when it mounts; `Select` also
937
+ applies a later one, so the next reset — React's after a form action included —
938
+ goes back to it. With `required`, put a placeholder option whose `value` is
939
+ `''` first; leaving it selected fails validation.
580
940
 
581
941
  ### Autocomplete
582
942
 
583
- A multi-select autocomplete. `value` and `onChange` are `string[]`.
943
+ A multi-select autocomplete over a fixed list of options, shown as removable
944
+ tags. `value` and `onChange` are `string[]`. To pick one option, or to search a
945
+ list that lives on a server, use `Combobox`.
946
+
947
+ Typing filters `options` by label. `ArrowDown` / `ArrowUp` open the list and
948
+ move through it, `Enter` adds or removes the active option, `Backspace` in an
949
+ empty field removes the last tag, and `Escape` closes the list. Keys pressed
950
+ while an IME is composing belong to the IME.
951
+
952
+ With a `name`, the selection is submitted through a visually hidden
953
+ `<select multiple>`: one entry per selected value, and `required` means at
954
+ least one. It is always present — nothing selected included — so native
955
+ validation and a form library's rules reach it, and when a form moves focus to
956
+ it after a failure, focus goes on to the text input.
584
957
 
585
958
  ```tsx
586
959
  import { Autocomplete } from '@k8ordo/ui';
@@ -600,16 +973,123 @@ Props:
600
973
 
601
974
  - `id`: `string` (required)
602
975
  - `options`: `readonly Option[]` (required)
603
- - `defaultValue`: `never`
976
+ - `defaultValue`: `string[]`
604
977
  - `invalid`: `boolean` (default: `false`)
605
978
  - `onChange`: `(value: string[]) => void`
606
979
  - `ref`: `Ref<HTMLInputElement>`
607
980
  - `value`: `string[]`
981
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `type` / `role` / `className` / `style` / `children` / `autoComplete` / `aria-autocomplete` / `aria-controls` / `aria-expanded` / `aria-activedescendant`.
982
+
983
+ ### ColorPicker
984
+
985
+ A color field whose value is `#rrggbb`. The value lives in a text input that
986
+ carries `name`, so it submits and resets like any other field; hue, saturation,
987
+ and lightness sliders and optional `swatches` (toggle buttons named by their
988
+ `label`) write into it and announce the change with an `input` event, as if it
989
+ had been typed. The color chip beside the text input is a native
990
+ `<input type="color">`, named by the built-in `colorPickerOpen` wording: pressing
991
+ it opens the browser's own color picker, which writes into the text input the
992
+ same way, while it is dragged too. It carries no `name`, so only the text input
993
+ submits; with no color yet it opens on the sliders' gray. The chip, sliders,
994
+ and swatches are all disabled while the field is `disabled`, `readOnly`, or its
995
+ form is pending. Typing reports a color only once it has six digits; on blur and
996
+ on Enter the text is tidied to lowercase `#rrggbb`, and a three-digit `#f80` is
997
+ expanded. Emptying the field reports `''`. Spread `@k8ordo/form`'s `input` onto
998
+ it as is: a `.regex()` in the schema arrives as `pattern` and replaces the
999
+ built-in `#[0-9a-fA-F]{6}`. The panel stays horizontal inside vertical writing
1000
+ mode.
1001
+
1002
+ ```tsx
1003
+ import { ColorPicker, FormControl } from '@k8ordo/ui';
1004
+
1005
+ <FormControl
1006
+ label="Accent color"
1007
+ renderInput={(props) => (
1008
+ <ColorPicker
1009
+ {...props}
1010
+ defaultValue="#0d9488"
1011
+ name="accent"
1012
+ swatches={[
1013
+ { value: '#0d9488', label: 'Teal' },
1014
+ { value: '#f97316', label: 'Orange' },
1015
+ ]}
1016
+ />
1017
+ )}
1018
+ />;
1019
+ ```
1020
+
1021
+ Props:
1022
+
1023
+ - `defaultValue`: `string`
1024
+ - `invalid`: `boolean` (default: `false`)
1025
+ - `onChange`: `(value: string) => void`
1026
+ - `ref`: `Ref<HTMLInputElement>`
1027
+ - `swatches`: `readonly ColorPickerSwatch[]`
1028
+ - `type`: `string`
1029
+ - `value`: `string`
1030
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `className` / `style` / `children`.
1031
+
1032
+ ### Combobox
1033
+
1034
+ A text field with a list of options, for picking one (the WAI-ARIA combobox
1035
+ with list autocomplete and manual selection). `value` and `onChange` are the
1036
+ chosen option's `value`, `''` when nothing is chosen. To pick several from a
1037
+ fixed list, use `Autocomplete`.
1038
+
1039
+ Typing opens the list; nothing is chosen until an option is picked with a click
1040
+ or `Enter`, so leaving half-typed text puts the chosen option's label back, and
1041
+ leaving the field empty clears the choice. `ArrowDown` / `ArrowUp` open the list
1042
+ and move through it (`Alt+ArrowDown` opens it without entering it), `Escape`
1043
+ closes it and, pressed again, drops what was typed. Keys pressed while an IME is
1044
+ composing belong to the IME.
1045
+
1046
+ Without `search`, typing filters `options` by label. With `search`, typing
1047
+ calls it for the options instead — each call gets a `signal` that aborts when
1048
+ the text changes again, so pass it to `fetch` — and `options` becomes the list
1049
+ shown before anything is typed and where the current value's label is looked
1050
+ up. While a search runs, the list is marked busy; a failed one says so with
1051
+ `comboboxFailed`, an empty result with `comboboxEmpty`.
1052
+
1053
+ With a `name`, the choice is submitted through a visually hidden `<select>`, the
1054
+ same way `Autocomplete` submits, so `required`, a form library's rules, reset,
1055
+ and moving focus to the field after a failure all work on it.
1056
+
1057
+ ```tsx
1058
+ import { Combobox } from '@k8ordo/ui';
1059
+
1060
+ <Combobox aria-label="Prefecture" name="prefecture" options={prefectures} />;
1061
+
1062
+ // Options from a server
1063
+ <Combobox
1064
+ aria-label="City"
1065
+ name="city"
1066
+ search={async (query, { signal }) => {
1067
+ const response = await fetch(`/api/cities?q=${encodeURIComponent(query)}`, {
1068
+ signal,
1069
+ });
1070
+ return response.json();
1071
+ }}
1072
+ />;
1073
+ ```
1074
+
1075
+ Props:
1076
+
1077
+ - `defaultValue`: `string`
1078
+ - `invalid`: `boolean` (default: `false`)
1079
+ - `onChange`: `(value: string) => void`
1080
+ - `options`: `readonly Option[]`
1081
+ - `ref`: `Ref<HTMLInputElement>`
1082
+ - `search`: `ComboboxSearch`
1083
+ - `type`: `string`
1084
+ - `value`: `string`
1085
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `role` / `className` / `style` / `children` / `autoComplete` / `aria-autocomplete` / `aria-controls` / `aria-expanded` / `aria-activedescendant`.
608
1086
 
609
1087
  ### Checkbox
610
1088
 
611
1089
  The label is passed as the `label` prop, not as children. `onChange` is `(checked, event)`.
612
1090
 
1091
+ `itemValue` is the input's `value`: the string a checked box submits under its `name`. Without it the box renders no `value` and submits the browser's default, `on`. There is no `value` prop — on a checkbox it is easily mistaken for the checked state, which is `checked`.
1092
+
613
1093
  ```tsx
614
1094
  import { Checkbox } from '@k8ordo/ui';
615
1095
 
@@ -618,17 +1098,28 @@ import { Checkbox } from '@k8ordo/ui';
618
1098
 
619
1099
  // Uncontrolled
620
1100
  <Checkbox defaultChecked label="I agree" />
1101
+
1102
+ // Submits inStock=true when checked, instead of inStock=on
1103
+ <Checkbox itemValue="true" label="In stock only" name="inStock" />
621
1104
  ```
622
1105
 
1106
+ `indeterminate` shows the mixed state — some of a set selected — and sets the
1107
+ input's `indeterminate` property, so it is announced as partly checked.
1108
+ `labelHidden` keeps `label` as the accessible name but does not draw it, for a
1109
+ place with no room for text such as a table cell.
1110
+
623
1111
  Props:
624
1112
 
625
1113
  - `label`: `string` (required)
626
1114
  - `checked`: `boolean`
627
- - `defaultChecked`: `never`
1115
+ - `defaultChecked`: `boolean`
1116
+ - `indeterminate`: `boolean` (default: `false`)
628
1117
  - `invalid`: `boolean` (default: `false`)
629
1118
  - `itemValue`: `string`
1119
+ - `labelHidden`: `boolean` (default: `false`)
630
1120
  - `onChange`: `(checked: boolean, event: ChangeEvent<HTMLInputElement>) => void`
631
1121
  - `ref`: `Ref<HTMLInputElement>`
1122
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `type` / `className` / `style` / `value` / `children`.
632
1123
 
633
1124
  ### CheckboxGroup
634
1125
 
@@ -638,6 +1129,10 @@ The group's selection lives in `value` / `onChange` (`string[]`). That is a diff
638
1129
 
639
1130
  It renders a `fieldset[role="group"]`, so `aria-labelledby` is required. Convey that the group is required through the referenced label element (for example `FormControl`'s required marker). `role="group"` does not allow `aria-required`, so do not put it on the group.
640
1131
 
1132
+ Uncontrolled, the selection lives in the checkboxes themselves: `onChange`
1133
+ receives the checked values in document order, and a form reset puts the boxes
1134
+ back to `defaultValue` without leaving a stale selection behind.
1135
+
641
1136
  ```tsx
642
1137
  import { CheckboxGroup } from '@k8ordo/ui';
643
1138
 
@@ -657,31 +1152,38 @@ Props (CheckboxGroup.Item):
657
1152
 
658
1153
  - `label`: `string` (required)
659
1154
  - `checked`: `boolean`
660
- - `defaultChecked`: `never`
1155
+ - `defaultChecked`: `boolean`
1156
+ - `indeterminate`: `boolean` (default: `false`)
661
1157
  - `invalid`: `boolean` (default: `false`)
662
1158
  - `itemValue`: `string`
1159
+ - `labelHidden`: `boolean` (default: `false`)
663
1160
  - `onChange`: `(checked: boolean, event: ChangeEvent<HTMLInputElement>) => void`
664
1161
  - `ref`: `Ref<HTMLInputElement>`
1162
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `type` / `className` / `style` / `value` / `children`.
665
1163
 
666
1164
  Props (CheckboxGroup.Root):
667
1165
 
668
1166
  - `aria-labelledby`: `string` (required)
669
1167
  - `name`: `string` (required)
670
1168
  - `children`: `ReactNode`
671
- - `defaultValue`: `never`
1169
+ - `defaultValue`: `string[]`
672
1170
  - `invalid`: `boolean` (default: `false`)
673
1171
  - `onChange`: `(value: string[]) => void`
674
1172
  - `ref`: `Ref<HTMLFieldSetElement>`
675
1173
  - `value`: `string[]`
1174
+ - Other props are forwarded to `FieldsetHTMLAttributes<HTMLFieldSetElement>`, except `className` / `style` / `role`.
676
1175
 
677
1176
  ### CheckboxCard
678
1177
 
679
- A card-styled checkbox.
1178
+ A card-styled checkbox. Uncontrolled, the selection and its look follow the
1179
+ checkboxes themselves (`:checked`), so a form reset restores both; `onChange`
1180
+ receives the checked values in document order.
680
1181
 
681
1182
  ```tsx
682
1183
  import { CheckboxCard } from '@k8ordo/ui';
683
1184
 
684
1185
  <CheckboxCard
1186
+ aria-labelledby="plan-checkbox"
685
1187
  name="plan"
686
1188
  disabled={false}
687
1189
  options={[
@@ -702,11 +1204,12 @@ Props:
702
1204
 
703
1205
  - `aria-labelledby`: `string` (required)
704
1206
  - `options`: `readonly CheckboxCardOption[]` (required)
705
- - `defaultValue`: `never`
1207
+ - `defaultValue`: `string[]`
706
1208
  - `invalid`: `boolean` (default: `false`)
707
1209
  - `onChange`: `(value: string[]) => void`
708
1210
  - `ref`: `Ref<HTMLFieldSetElement>`
709
1211
  - `value`: `string[]`
1212
+ - Other props are forwarded to `FieldsetHTMLAttributes<HTMLFieldSetElement>`, except `className` / `style` / `children` / `role`.
710
1213
 
711
1214
  ### Radio
712
1215
 
@@ -729,16 +1232,19 @@ Props:
729
1232
 
730
1233
  - `aria-labelledby`: `string` (required)
731
1234
  - `options`: `readonly Option[]` (required)
1235
+ - `defaultValue`: `string`
732
1236
  - `disabled`: `boolean` (default: `false`)
733
1237
  - `invalid`: `boolean` (default: `false`)
734
1238
  - `name`: `string`
735
1239
  - `onChange`: `(value: string, event: ChangeEvent<HTMLInputElement>) => void`
736
1240
  - `ref`: `Ref<HTMLDivElement>`
1241
+ - `required`: `boolean` (default: `false`)
737
1242
  - `value`: `string`
1243
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `role` / `className` / `style` / `children`.
738
1244
 
739
1245
  ### RadioCard
740
1246
 
741
- A card-styled radio button. Real `input[type="radio"]` elements sit inside a `fieldset[role="radiogroup"]`, so arrow-key roving and single selection are left to the browser. Reach them from tests with `getByRole('radio', { checked })`.
1247
+ A card-styled radio button. Real `input[type="radio"]` elements sit inside a `fieldset[role="radiogroup"]`, so arrow-key roving and single selection are left to the browser. Reach them from tests with `getByRole('radio', { checked })`. `required` goes to every radio, and an uncontrolled selection's look follows `:checked`, so a form reset restores it.
742
1248
 
743
1249
  ```tsx
744
1250
  import { RadioCard } from '@k8ordo/ui';
@@ -765,15 +1271,19 @@ Props:
765
1271
 
766
1272
  - `aria-labelledby`: `string` (required)
767
1273
  - `options`: `readonly RadioCardOption[]` (required)
768
- - `defaultValue`: `never`
1274
+ - `defaultValue`: `string`
769
1275
  - `invalid`: `boolean` (default: `false`)
770
1276
  - `onChange`: `(value: string) => void`
771
1277
  - `ref`: `Ref<HTMLFieldSetElement>`
1278
+ - `required`: `boolean` (default: `false`)
772
1279
  - `value`: `string`
1280
+ - Other props are forwarded to `FieldsetHTMLAttributes<HTMLFieldSetElement>`, except `className` / `style` / `children` / `role`.
773
1281
 
774
1282
  ### Slider
775
1283
 
776
- A range slider.
1284
+ A range slider. Uncontrolled, the value lives in the input, so a form reset
1285
+ puts it back to `defaultValue` and the filled track follows. `min`, `max`, and
1286
+ an uncontrolled `defaultValue` also take strings, and `step` takes `'any'`.
777
1287
 
778
1288
  ```tsx
779
1289
  import { Slider } from '@k8ordo/ui';
@@ -792,14 +1302,58 @@ import { Slider } from '@k8ordo/ui';
792
1302
 
793
1303
  Props:
794
1304
 
795
- - `defaultValue`: `never`
1305
+ - `defaultValue`: `number` | `string`
796
1306
  - `invalid`: `boolean` (default: `false`)
797
- - `max`: `number` (default: `100`)
798
- - `min`: `number` (default: `0`)
1307
+ - `max`: `number` | `string`
1308
+ - `min`: `number` | `string`
799
1309
  - `onChange`: `(value: number) => void`
800
1310
  - `ref`: `Ref<HTMLInputElement>`
801
- - `step`: `number` (default: `1`)
1311
+ - `step`: `number` | `'any'` (default: `1`)
802
1312
  - `value`: `number`
1313
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `type` / `className` / `style` / `children`.
1314
+
1315
+ ### RangeSlider
1316
+
1317
+ A slider with two thumbs for picking a range (the WAI-ARIA multi-thumb slider).
1318
+ Each thumb is a real `<input type="range">`, so the keyboard behaves as the
1319
+ browser's own slider does, and neither thumb can pass the other: the lower
1320
+ thumb's `aria-valuemax` is the upper value and the upper thumb's
1321
+ `aria-valuemin` the lower one. `value` / `defaultValue` / `onChange` carry the
1322
+ pair `[lower, upper]`.
1323
+
1324
+ `name` is a pair too: the two thumbs submit as two form fields. Uncontrolled,
1325
+ the thumbs keep their values in the DOM, so a form's reset and its dirty check
1326
+ (value against default value) work on them as on any input. The whole slider is
1327
+ a `role="group"`: name it with `aria-label` or `aria-labelledby`
1328
+ (`FormControl`'s `renderInput` props work), and each thumb is read as that name
1329
+ followed by the built-in `rangeSliderStart` / `rangeSliderEnd` wording.
1330
+
1331
+ ```tsx
1332
+ import { RangeSlider } from '@k8ordo/ui';
1333
+
1334
+ <RangeSlider
1335
+ aria-label="Price"
1336
+ defaultValue={[20, 80]}
1337
+ max={100}
1338
+ min={0}
1339
+ name={['priceMin', 'priceMax']}
1340
+ />;
1341
+ ```
1342
+
1343
+ Props:
1344
+
1345
+ - `defaultValue`: `readonly [number, number]`
1346
+ - `disabled`: `boolean` (default: `false`)
1347
+ - `invalid`: `boolean` (default: `false`)
1348
+ - `max`: `number` (default: `100`)
1349
+ - `min`: `number` (default: `0`)
1350
+ - `name`: `readonly [string, string]`
1351
+ - `onChange`: `(value: [number, number]) => void`
1352
+ - `ref`: `Ref<HTMLDivElement>`
1353
+ - `required`: `boolean` (default: `false`)
1354
+ - `step`: `number` (default: `1`)
1355
+ - `value`: `readonly [number, number]`
1356
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style` / `children` / `role`.
803
1357
 
804
1358
  ### Switch
805
1359
 
@@ -822,14 +1376,16 @@ Props:
822
1376
 
823
1377
  - `label`: `string` (required)
824
1378
  - `checked`: `boolean`
825
- - `defaultChecked`: `never`
1379
+ - `defaultChecked`: `boolean`
826
1380
  - `invalid`: `boolean` (default: `false`)
827
1381
  - `onChange`: `(checked: boolean, event: ChangeEvent<HTMLInputElement>) => void`
828
1382
  - `ref`: `Ref<HTMLInputElement>`
1383
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `type` / `role` / `className` / `style` / `value` / `children`.
829
1384
 
830
1385
  ### FileField
831
1386
 
832
- File upload, as a composite pattern.
1387
+ File upload, as a composite pattern. A string `defaultValue` (the type
1388
+ `@k8ordo/form`'s derived attributes carry) is accepted and ignored.
833
1389
 
834
1390
  ```tsx
835
1391
  import { FileField } from '@k8ordo/ui';
@@ -846,15 +1402,53 @@ import { FileField } from '@k8ordo/ui';
846
1402
  </FileField.Root>;
847
1403
  ```
848
1404
 
1405
+ `FileField.Dropzone` is an area files can be dropped onto. Left empty, it holds
1406
+ the built-in `fileFieldDrop` wording and a "choose files" button, so the field
1407
+ stays usable by keyboard; pass children to lay it out yourself (put a
1408
+ `FileField.Trigger` inside). Dropped files are added exactly like picked ones:
1409
+ they respect `multiple` and `maxFiles`, land in the input so they are
1410
+ submitted, and are announced with an `input` event so a form sees the change.
1411
+ A dropped folder is skipped (choose folders through the picker with
1412
+ `webkitDirectory`). The browser applies `accept` only to the picker, so the
1413
+ field applies it to dropped files itself, by the same rules: a file it does not
1414
+ match is left out, and a drop with nothing it matches changes nothing — no
1415
+ `onChange`, no `input` event.
1416
+
1417
+ The input holds exactly what `ItemList` lists, so what is listed is what is
1418
+ submitted:
1419
+
1420
+ - With `multiple` or `webkitDirectory`, each pick or drop adds to the list, up
1421
+ to `maxFiles`, and the input is rewritten to the whole list — the browser
1422
+ alone would keep only the files just picked. A rewrite is announced with an
1423
+ `input` event.
1424
+ - Removing a file from `ItemList` removes it from the input.
1425
+ - A `File[]` `defaultValue` is submitted as well as listed, and a form reset
1426
+ puts both back to it (to an empty list without one).
1427
+ - `onChange` receives that whole list — not only the files just picked or
1428
+ dropped — after every pick, drop, and removal. Only a pick passes the
1429
+ `event`.
1430
+
1431
+ ```tsx
1432
+ <FileField.Root accept="image/*" multiple name="photos">
1433
+ <FileField.Dropzone />
1434
+ <FileField.ItemList clearable />
1435
+ </FileField.Root>
1436
+ ```
1437
+
849
1438
  Props (Root):
850
1439
 
851
1440
  - `children`: `ReactNode`
852
- - `defaultValue`: `File[]`
1441
+ - `defaultValue`: `File[]` | `string`
853
1442
  - `invalid`: `boolean` (default: `false`)
854
1443
  - `maxFiles`: `number`
855
1444
  - `onChange`: `(files: FileList | null, event?: ChangeEvent<HTMLInputElement>) => void`
856
1445
  - `ref`: `Ref<HTMLInputElement>`
857
1446
  - `webkitDirectory`: `boolean` (default: `false`)
1447
+ - Other props are forwarded to `InputHTMLAttributes<HTMLInputElement>`, except `type` / `className` / `style` / `value`.
1448
+
1449
+ Props (FileField.Dropzone):
1450
+
1451
+ - `children`: `ReactNode`
858
1452
 
859
1453
  Props (FileField.ItemList):
860
1454
 
@@ -869,7 +1463,7 @@ Props (FileField.Trigger):
869
1463
 
870
1464
  ### Heading
871
1465
 
872
- A semantic heading. The `type` prop selects the HTML element.
1466
+ A semantic heading. The `level` prop selects the HTML element.
873
1467
 
874
1468
  ```tsx
875
1469
  import { Heading } from '@k8ordo/ui';
@@ -884,6 +1478,7 @@ Props:
884
1478
  - `level`: `'h1'` | `'h2'` | `'h3'` | `'h4'` | `'h5'` | `'h6'` (required)
885
1479
  - `children`: `ReactNode`
886
1480
  - `lineClamp`: `1` | `2` | `3` | `4` | `5` | `6`
1481
+ - Other props are forwarded to `HTMLAttributes<HTMLHeadingElement>`, except `className` / `style`.
887
1482
 
888
1483
  ### Avatar
889
1484
 
@@ -907,6 +1502,7 @@ Props:
907
1502
  - `name`: `string`
908
1503
  - `size`: `'sm'` | `'md'` | `'lg'` (default: `'md'`)
909
1504
  - `src`: `string`
1505
+ - Other props are forwarded to `HTMLAttributes<HTMLSpanElement>`, except `role` / `aria-label` / `className` / `style`.
910
1506
 
911
1507
  ### Badge
912
1508
 
@@ -923,10 +1519,11 @@ import { Badge } from '@k8ordo/ui';
923
1519
  Props:
924
1520
 
925
1521
  - `label`: `string` (required)
926
- - `interactive`: `true`
927
- - `size`: `Size`
928
- - `tone`: `Tone`
929
- - `variant`: `Variant`
1522
+ - `interactive`: `boolean`
1523
+ - `size`: `'sm'` | `'md'` | `'lg'`
1524
+ - `tone`: `'neutral'` | `'info'` | `'success'` | `'warning'` | `'error'`
1525
+ - `variant`: `'solid'` | `'outline'`
1526
+ - Other props are forwarded to `ButtonHTMLAttributes<HTMLButtonElement> | HTMLAttributes<HTMLSpanElement>`, except `children` / `className` / `style` / `type`.
930
1527
 
931
1528
  ### Code
932
1529
 
@@ -941,10 +1538,154 @@ import { Code } from '@k8ordo/ui';
941
1538
  Props:
942
1539
 
943
1540
  - `children`: `string` (required)
1541
+ - Other props are forwarded to `HTMLAttributes<HTMLElement>`, except `className` / `style`.
1542
+
1543
+ ### CodeBlock
1544
+
1545
+ A block of code, highlighted on the server with shiki, with a copy button.
1546
+ It is an async Server Component on its own subpath, `@k8ordo/ui/code-block`,
1547
+ and imports `server-only`: the highlighter never reaches the browser, and
1548
+ importing it from a Client Component fails the build. Only the copy button is a
1549
+ client module.
1550
+
1551
+ ```tsx
1552
+ import { CodeBlock } from '@k8ordo/ui/code-block';
1553
+
1554
+ <CodeBlock code={source} lang="tsx" title="save.tsx" />;
1555
+ ```
1556
+
1557
+ - `lang` is any language shiki bundles (`tsx`, `bash`, `css`, …). A name it
1558
+ does not know renders as plain text rather than failing, so a Markdown fence
1559
+ can pass its info string through as it is. The header shows `title` when
1560
+ given (as the figure's `figcaption`), and the language otherwise.
1561
+ - It looks like the code blocks on k8o's blog: one surface in `bg-surface`
1562
+ with no border, the language (or `title`, in monospace) as a small label at
1563
+ its top, and the copy button over its top corner. The syntax colors are
1564
+ shiki's `one-light` in light and `plastic` in dark, unchanged, written as
1565
+ `light-dark()` so they follow the `color-scheme` the stylesheet sets with
1566
+ `.dark`. Some of them, the comments among them, fall short of 4.5:1 on the
1567
+ surface; the library's own accessibility checks exempt the code's text from
1568
+ the contrast rule and nothing else. Stretched by its parent, the block gives
1569
+ the extra height to the code, never to the label.
1570
+ - `marks` marks lines by their 1-based number, with a bar and a tint in the
1571
+ status tokens: `highlight`, `add` (drawn with a `+`), or `remove` (drawn
1572
+ with a `-`). `callouts` puts a note under a line
1573
+ (an array puts several, in order) as a bubble pointing up at it, indented
1574
+ like the line, and tints the line it points at. A line can carry both; the
1575
+ note's tint wins. Neither is part of the copied text: the button copies
1576
+ `code` exactly.
1577
+
1578
+ ```tsx
1579
+ <CodeBlock
1580
+ callouts={{ 3: 'Guard the division' }}
1581
+ code={source}
1582
+ lang="ts"
1583
+ marks={{ 2: 'remove', 3: 'add' }}
1584
+ />
1585
+ ```
1586
+
1587
+ - Inside a `.writing-v` tree it stays a horizontal island.
1588
+ - It cannot render in a generative-UI spec, which renders on the client; see
1589
+ [generative-ui](generative-ui.md).
1590
+
1591
+ Props:
1592
+
1593
+ - `code`: `string` (required)
1594
+ - `callouts`: `Readonly<Record<number, string | readonly string[]>>`
1595
+ - `lang`: `string` (default: `'text'`)
1596
+ - `marks`: `Readonly<Record<number, 'highlight' | 'add' | 'remove'>>`
1597
+ - `title`: `string`
1598
+ - Other props are forwarded to `HTMLAttributes<HTMLElement>`, except `children` / `className` / `style`.
1599
+
1600
+ ### Kbd
1601
+
1602
+ One keyboard key, drawn as a key cap. A shortcut is several `Kbd` side by side,
1603
+ one per key. When the key is a symbol a screen reader would not say usefully
1604
+ (`⌘`, `⇧`), pass `label`: the symbol stays on screen and the label is what is
1605
+ read out.
1606
+
1607
+ ```tsx
1608
+ import { Kbd } from '@k8ordo/ui';
1609
+
1610
+ <Kbd>Esc</Kbd>
1611
+
1612
+ <Kbd label="Command">⌘</Kbd>
1613
+ <Kbd>K</Kbd>
1614
+ ```
1615
+
1616
+ Props:
1617
+
1618
+ - `children`: `string` (required)
1619
+ - `label`: `string`
1620
+ - Other props are forwarded to `HTMLAttributes<HTMLElement>`, except `className` / `style`.
1621
+
1622
+ ### Carousel
1623
+
1624
+ Slides that scroll along the inline axis, snapping one slide at a time, with
1625
+ previous and next buttons under them. It is built on scroll snapping, so a
1626
+ trackpad, a touch swipe, and the arrow keys (the track takes focus) all move it
1627
+ as well; the buttons move one slide per press and are disabled at either end.
1628
+
1629
+ ```tsx
1630
+ import { Carousel } from '@k8ordo/ui';
1631
+
1632
+ <Carousel.Root label="Featured posts" slideSize="md">
1633
+ {posts.map((post) => (
1634
+ <Carousel.Slide key={post.id} label={post.title}>
1635
+ <PostCard post={post} />
1636
+ </Carousel.Slide>
1637
+ ))}
1638
+ </Carousel.Root>;
1639
+ ```
1640
+
1641
+ `slideSize` sets how much of the track one slide takes: `full` (one at a time),
1642
+ `lg` (the next one peeks in), `md` (two), `sm` (three). With `full` and `lg` the
1643
+ current position is shown as `2 / 5`; with several slides in view there is no
1644
+ single current slide, so no position is shown. The region is announced as a
1645
+ carousel and each slide as a slide (`aria-roledescription`); give a slide a
1646
+ `label` when its content has a title. The slides follow the writing mode, so
1647
+ under `.writing-v` the track scrolls vertically. There is no autoplay.
1648
+
1649
+ Props (Carousel.Root):
1650
+
1651
+ - `label`: `string` (required)
1652
+ - `children`: `ReactNode`
1653
+ - `slideSize`: `'full'` | `'lg'` | `'md'` | `'sm'` (default: `'full'`)
1654
+
1655
+ Props (Carousel.Slide):
1656
+
1657
+ - `children`: `ReactNode`
1658
+ - `label`: `string`
1659
+
1660
+ ### Prose
1661
+
1662
+ A container that puts the typesetting of body text back — for Markdown or MDX
1663
+ rendered to HTML. Only bare elements (no `class`) are typeset, so components
1664
+ placed inside keep their own look; the spacing between blocks applies to
1665
+ everything. Tuned for Japanese: loose leading, emphasis dots for `em`, and a
1666
+ one-character paragraph indent in vertical writing. See
1667
+ [Typography](typography.md#long-form-text-prose) for what it sets.
1668
+
1669
+ ```tsx
1670
+ import { Prose } from '@k8ordo/ui';
1671
+
1672
+ <article>
1673
+ <Prose>
1674
+ <MDXContent components={{ pre: MyCodeBlock }} />
1675
+ </Prose>
1676
+ </article>;
1677
+ ```
1678
+
1679
+ Props:
1680
+
1681
+ - `children`: `ReactNode`
1682
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style`.
944
1683
 
945
1684
  ### Table
946
1685
 
947
- A data table, as a compound component.
1686
+ A data table, as a compound component. `Table.EmptyState` is the row to put in
1687
+ `Table.Body` when there are no rows: it spans `colSpan` columns and draws an
1688
+ `EmptyState` with the rest of its props.
948
1689
 
949
1690
  ```tsx
950
1691
  import { Table } from '@k8ordo/ui';
@@ -968,45 +1709,238 @@ import { Table } from '@k8ordo/ui';
968
1709
  Props (Table.Body):
969
1710
 
970
1711
  - `children`: `ReactNode`
1712
+ - Other props are forwarded to `HTMLAttributes<HTMLTableSectionElement>`, except `className` / `style`.
971
1713
 
972
1714
  Props (Table.Caption):
973
1715
 
974
1716
  - `children`: `ReactNode`
1717
+ - Other props are forwarded to `HTMLAttributes<HTMLTableCaptionElement>`, except `className` / `style`.
975
1718
 
976
1719
  Props (Table.Cell):
977
1720
 
978
1721
  - `align`: `CellAlign` (default: `'left'`)
979
1722
  - `children`: `ReactNode`
980
1723
  - `color`: `'base'` | `'mute'` (default: `'base'`)
1724
+ - Other props are forwarded to `TdHTMLAttributes<HTMLTableCellElement>`, except `className` / `style`.
981
1725
 
982
1726
  Props (Table.EmptyState):
983
1727
 
984
- - `children`: `ReactNode` (required)
985
1728
  - `colSpan`: `number` (required)
1729
+ - `title`: `string` (required)
1730
+ - `action`: `ReactNode`
1731
+ - `description`: `ReactNode`
1732
+ - `icon`: `ReactNode`
1733
+ - Other props are forwarded to `ComponentProps<typeof EmptyState>`.
1734
+
1735
+ Props (Table.Head):
1736
+
1737
+ - `children`: `ReactNode`
1738
+ - Other props are forwarded to `HTMLAttributes<HTMLTableSectionElement>`, except `className` / `style`.
1739
+
1740
+ Props (Table.HeaderCell):
1741
+
1742
+ - `align`: `CellAlign` (default: `'left'`)
1743
+ - `children`: `ReactNode`
1744
+ - `scope`: `'col'` | `'row'` | `'colgroup'` | `'rowgroup'` (default: `'col'`)
1745
+ - Other props are forwarded to `ThHTMLAttributes<HTMLTableCellElement>`, except `className` / `style`.
1746
+
1747
+ Props (Table.Root):
1748
+
1749
+ - `children`: `ReactNode`
1750
+ - Other props are forwarded to `TableHTMLAttributes<HTMLTableElement>`, except `className` / `style`.
1751
+
1752
+ Props (Table.Row):
1753
+
1754
+ - `children`: `ReactNode`
1755
+ - `interactive`: `boolean` (default: `false`)
1756
+ - `selected`: `boolean` (default: `false`)
1757
+ - Other props are forwarded to `HTMLAttributes<HTMLTableRowElement>`, except `className` / `style`.
1758
+
1759
+ ### DataTable
1760
+
1761
+ A table with sorting, row selection, and column visibility. Every piece of state
1762
+ is **controlled** and owned by the caller, so it can live anywhere — component
1763
+ state, or the URL through `@k8ordo/state`'s url slot, which keeps the sort and
1764
+ the page in a shareable link. It takes functions (`cell`, `getRowId`, the
1765
+ handlers), so render it from the Client Component that owns that state.
1766
+
1767
+ ```tsx
1768
+ 'use client';
1769
+ import { DataTable, type DataTableSort } from '@k8ordo/ui';
1770
+
1771
+ const [sort, setSort] = useState<DataTableSort | null>(null);
1772
+ const [selectedIds, setSelectedIds] = useState<string[]>([]);
1773
+ const [hiddenColumnIds, setHiddenColumnIds] = useState<string[]>([]);
1774
+
1775
+ <DataTable
1776
+ columns={[
1777
+ { id: 'name', header: 'Name', cell: (m) => m.name, sortable: true },
1778
+ { id: 'role', header: 'Role', cell: (m) => m.role, hideable: false },
1779
+ ]}
1780
+ getRowId={(m) => m.id}
1781
+ hiddenColumnIds={hiddenColumnIds}
1782
+ label="Members"
1783
+ onHiddenColumnIdsChange={setHiddenColumnIds}
1784
+ onSelectedIdsChange={setSelectedIds}
1785
+ onSortChange={setSort}
1786
+ rows={sortMembers(members, sort)}
1787
+ selectedIds={selectedIds}
1788
+ sort={sort}
1789
+ />;
1790
+ ```
1791
+
1792
+ - A column is a `DataTableColumn<Row>`: `id`, `header` (text), `cell(row)`
1793
+ (what to draw), and optionally `align`, `sortable`, and `hideable`.
1794
+ - **It does not sort, filter, or page.** It draws `rows` in the order given, so
1795
+ the same component works when the server sorts. `sort` is
1796
+ `{ columnId, direction: 'ascending' | 'descending' } | null`; a sortable
1797
+ header cycles ascending → descending → unsorted and carries `aria-sort`.
1798
+ Put `Pagination` under it for pages.
1799
+ - Each feature appears only when you pass its handler: sort buttons on the
1800
+ `sortable` columns with `onSortChange`, a checkbox column with
1801
+ `onSelectedIdsChange`, and a "Columns" menu (every column but those with
1802
+ `hideable: false`) with `onHiddenColumnIdsChange`.
1803
+ - The header checkbox selects or clears the rows on screen and shows the mixed
1804
+ state when some are selected (`Checkbox`'s `indeterminate`). The first column
1805
+ is the row header (`th scope="row"`), and each row's checkbox is named after
1806
+ it ("Select row Aoki").
1807
+ - `emptyState` is drawn in a row spanning the columns when `rows` is empty —
1808
+ pass an `EmptyState`.
1809
+ - Selected rows use `Table.Row`'s `selected`. For bulk actions, render your own
1810
+ bar above the table from `selectedIds`.
1811
+
1812
+ Props:
1813
+
1814
+ - `columns`: `ReadonlyArray<DataTableColumn<Row>>` (required)
1815
+ - `getRowId`: `(row: Row) => string` (required)
1816
+ - `label`: `string` (required)
1817
+ - `rows`: `readonly Row[]` (required)
1818
+ - `emptyState`: `ReactNode`
1819
+ - `hiddenColumnIds`: `readonly string[]` (default: `NONE`)
1820
+ - `onHiddenColumnIdsChange`: `(ids: string[]) => void`
1821
+ - `onSelectedIdsChange`: `(ids: string[]) => void`
1822
+ - `onSortChange`: `(sort: DataTableSort | null) => void`
1823
+ - `selectedIds`: `readonly string[]` (default: `NONE`)
1824
+ - `sort`: `DataTableSort` | `null` (default: `null`)
1825
+
1826
+ ### Tree
1827
+
1828
+ A hierarchy whose parents open and close — files and folders, an outline —
1829
+ with the WAI-ARIA tree view keyboard model. Pass the nodes as a tree of
1830
+ `{ id, label, icon?, children? }`.
1831
+
1832
+ ```tsx
1833
+ import { Tree } from '@k8ordo/ui';
1834
+
1835
+ <Tree
1836
+ defaultExpandedIds={['src']}
1837
+ items={[
1838
+ {
1839
+ id: 'src',
1840
+ label: 'src',
1841
+ children: [{ id: 'index', label: 'index.ts' }],
1842
+ },
1843
+ { id: 'readme', label: 'README.md' },
1844
+ ]}
1845
+ label="Files"
1846
+ onChange={(id) => open(id)}
1847
+ />;
1848
+ ```
1849
+
1850
+ - Which parents are open is controllable (`expandedIds` / `defaultExpandedIds`
1851
+ / `onExpandedChange`), and so is the selected node (`selectedId` /
1852
+ `defaultSelectedId` / `onChange`, called with the node's `id`).
1853
+ - Keyboard: Down / Up move between the visible nodes; Right opens a closed
1854
+ parent, then moves to its first child; Left closes an open parent, or moves
1855
+ to the parent; Home / End go to the first / last visible node; Enter or Space
1856
+ selects; a character moves to the next node whose label starts with it.
1857
+ - Clicking a node selects it and, on a parent, opens or closes it.
1858
+ - One node takes Tab at a time (roving tabindex): the focused one, else the
1859
+ selected one, else the first.
1860
+ - Under `.writing-v` the nodes run right to left, so Left / Right move between
1861
+ them and Down / Up open and close.
1862
+
1863
+ Props:
1864
+
1865
+ - `items`: `readonly TreeItem[]` (required)
1866
+ - `label`: `string` (required)
1867
+ - `defaultExpandedIds`: `readonly string[]`
1868
+ - `defaultSelectedId`: `string` | `null` (default: `null`)
1869
+ - `expandedIds`: `readonly string[]`
1870
+ - `onChange`: `(id: string) => void`
1871
+ - `onExpandedChange`: `(ids: readonly string[]) => void`
1872
+ - `selectedId`: `string` | `null`
1873
+ - Other props are forwarded to `HTMLAttributes<HTMLUListElement>`, except `children` / `className` / `style` / `role` / `aria-label`.
1874
+
1875
+ ### Accordion
1876
+
1877
+ A collapsible section, as a compound component.
1878
+
1879
+ ```tsx
1880
+ import { Accordion } from '@k8ordo/ui';
1881
+
1882
+ <Accordion.Root>
1883
+ <Accordion.Item>
1884
+ <Accordion.Button>Section 1</Accordion.Button>
1885
+ <Accordion.Panel>Content</Accordion.Panel>
1886
+ </Accordion.Item>
1887
+ </Accordion.Root>;
1888
+ ```
1889
+
1890
+ Props (Accordion.Button):
1891
+
1892
+ - `children`: `ReactNode`
986
1893
 
987
- Props (Table.Head):
1894
+ Props (Accordion.Item):
988
1895
 
989
1896
  - `children`: `ReactNode`
1897
+ - `defaultOpen`: `boolean` (default: `false`)
1898
+ - `isOpen`: `boolean`
1899
+ - `onChange`: `(isOpen: boolean) => void`
990
1900
 
991
- Props (Table.HeaderCell):
1901
+ Props (Accordion.Panel):
992
1902
 
993
- - `align`: `CellAlign` (default: `'left'`)
994
1903
  - `children`: `ReactNode`
995
- - `scope`: `'col'` | `'row'` | `'colgroup'` | `'rowgroup'` (default: `'col'`)
996
1904
 
997
- Props (Table.Root):
1905
+ Props (Accordion.Root):
998
1906
 
999
1907
  - `children`: `ReactNode`
1000
1908
 
1001
- Props (Table.Row):
1909
+ ### Card
1910
+
1911
+ A card that groups content.
1912
+
1913
+ ```tsx
1914
+ import { Card } from '@k8ordo/ui';
1915
+
1916
+ // Static card. The card has no padding of its own
1917
+ <Card width="full" variant="shadow">
1918
+ <div style={{ padding: 'calc(var(--spacing) * 6)' }}>Content</div>
1919
+ </Card>
1920
+
1921
+ // Clickable card (scales to 1.02 on hover and to 0.98 while pressed)
1922
+ <Card variant="outline" interactive>
1923
+ <div style={{ padding: 'calc(var(--spacing) * 6)' }}>Content</div>
1924
+ </Card>
1925
+ ```
1926
+
1927
+ Props:
1002
1928
 
1003
1929
  - `children`: `ReactNode`
1004
1930
  - `interactive`: `boolean` (default: `false`)
1931
+ - `variant`: `'shadow'` | `'outline'` (default: `'shadow'`)
1932
+ - `width`: `'full'` | `'fit'` (default: `'full'`)
1933
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style`.
1005
1934
 
1006
1935
  ## Feedback
1007
1936
 
1008
1937
  ### Alert
1009
1938
 
1939
+ Reports a status: the result of something the user did, or a state the
1940
+ application is in. It is a live region (`role="alert"` for `warning` and
1941
+ `error`, `role="status"` otherwise), so a screen reader announces it when it
1942
+ appears. For a note that is part of the content, use `Callout`.
1943
+
1010
1944
  ```tsx
1011
1945
  import { Alert } from '@k8ordo/ui';
1012
1946
 
@@ -1021,6 +1955,63 @@ Props:
1021
1955
  - `action`: `AlertAction`
1022
1956
  - `closeLabel`: `string`
1023
1957
  - `onClose`: `() => void`
1958
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `children` / `role` / `className` / `style`.
1959
+
1960
+ `action` is an `AlertAction`, `{ label: string; renderItem: (props: { children: ReactNode }) => ReactNode }`.
1961
+ `renderItem` receives `label` as `children`; render your own button or link
1962
+ around it.
1963
+
1964
+ ### Callout
1965
+
1966
+ A note that sits in the content: a tip beside a paragraph, a pitfall in a
1967
+ guide, a dated-content notice at the top of an article. Unlike `Alert`, it is
1968
+ not a live region (`role="note"`), so a page that renders one, or a client
1969
+ navigation that brings one in, does not have it announced as a warning. The
1970
+ body is `children`, so it can hold paragraphs, `Code`, and links; `label` is an
1971
+ optional bold line above it.
1972
+
1973
+ ```tsx
1974
+ import { Callout, Code } from '@k8ordo/ui';
1975
+
1976
+ <Callout label="Pitfall" tone="warning">
1977
+ <p>
1978
+ Passing <Code>value</Code> makes the field controlled, and a form reset no
1979
+ longer restores it.
1980
+ </p>
1981
+ </Callout>;
1982
+ ```
1983
+
1984
+ Props:
1985
+
1986
+ - `children`: `ReactNode` (required)
1987
+ - `tone`: `Status` (required)
1988
+ - `label`: `string`
1989
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `role` / `className` / `style`.
1990
+
1991
+ ### EmptyState
1992
+
1993
+ What a list, a table, or a search shows when there is nothing in it: a title,
1994
+ an optional description and icon, and an optional action. Inside a table, use
1995
+ `Table.EmptyState`, which puts the same content in a row spanning the columns.
1996
+
1997
+ ```tsx
1998
+ import { Button, EmptyState, TableIcon } from '@k8ordo/ui';
1999
+
2000
+ <EmptyState
2001
+ action={<Button onClick={clearFilters}>Clear filters</Button>}
2002
+ description="Try removing a filter."
2003
+ icon={<TableIcon size="lg" />}
2004
+ title="No matching posts"
2005
+ />;
2006
+ ```
2007
+
2008
+ Props:
2009
+
2010
+ - `title`: `string` (required)
2011
+ - `action`: `ReactNode`
2012
+ - `description`: `ReactNode`
2013
+ - `icon`: `ReactNode`
2014
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `children` / `className` / `style`.
1024
2015
 
1025
2016
  ### Toast
1026
2017
 
@@ -1031,14 +2022,19 @@ const { open, close, closeAll } = useToast();
1031
2022
 
1032
2023
  open('success', 'Saved');
1033
2024
  open('error', 'Something went wrong');
2025
+
2026
+ const syncingId = open('info', 'Syncing…', {
2027
+ duration: Number.POSITIVE_INFINITY,
2028
+ });
2029
+ close(syncingId);
1034
2030
  ```
1035
2031
 
1036
2032
  `ToastProvider` is already inside `UIProvider`, so no extra wrapper is needed.
1037
2033
 
1038
2034
  What `useToast()` returns:
1039
2035
 
1040
- - `open`: `(tone: Status, message: string, options?: ToastOptions) => void` (`ToastOptions` is `{ duration?: number; action?: ToastAction }`)
1041
- - `close`: `(id: string) => void`
2036
+ - `open`: `(tone: Status, message: string, options?: ToastOptions) => string` (returns the toast's id; `ToastOptions` is `{ duration?: number; action?: ToastAction }`. `duration` is in milliseconds and defaults to `5000`; `Number.POSITIVE_INFINITY` keeps the toast until it is closed. `ToastAction` is the same type as `AlertAction`.)
2037
+ - `close`: `(id: string) => void` (closes only the toast with the id `open` returned)
1042
2038
  - `closeAll`: `() => void`
1043
2039
 
1044
2040
  ### ToastProvider
@@ -1061,19 +2057,28 @@ Props:
1061
2057
 
1062
2058
  ### Progress
1063
2059
 
2060
+ Leave `value` out when how far along it is cannot be known: the bar then slides
2061
+ back and forth, has no `aria-valuenow`, and is named `label` (the built-in
2062
+ `loading` wording when omitted). With reduced motion it stops sliding and
2063
+ pulses across the whole track instead.
2064
+
1064
2065
  ```tsx
1065
2066
  import { Progress } from '@k8ordo/ui';
1066
2067
 
1067
- <Progress value={50} max={100} />
1068
- <Progress value={50} max={100} min={0} label="Progress" />
2068
+ <Progress value={50} />
2069
+ <Progress value={150} max={200} min={100} label="Progress" />
2070
+
2071
+ // Progress that cannot be measured
2072
+ <Progress label="Uploading" />
1069
2073
  ```
1070
2074
 
1071
2075
  Props:
1072
2076
 
1073
- - `max`: `number` (required)
1074
- - `value`: `number` (required)
1075
2077
  - `label`: `string`
2078
+ - `max`: `number` (default: `100`)
1076
2079
  - `min`: `number` (default: `0`)
2080
+ - `value`: `number`
2081
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `children` / `className` / `style`.
1077
2082
 
1078
2083
  ### Spinner
1079
2084
 
@@ -1089,6 +2094,7 @@ Props:
1089
2094
 
1090
2095
  - `label`: `string`
1091
2096
  - `size`: `'sm'` | `'md'` | `'lg'` (default: `'md'`)
2097
+ - Other props are forwarded to `OutputHTMLAttributes<HTMLOutputElement>`, except `children` / `aria-label` / `className` / `style`.
1092
2098
 
1093
2099
  ### Skeleton
1094
2100
 
@@ -1107,6 +2113,7 @@ Props:
1107
2113
  - `animate`: `boolean` (default: `true`)
1108
2114
  - `shape`: `'rect'` | `'circle'` (default: `'rect'`)
1109
2115
  - `size`: `'sm'` | `'md'` | `'lg'` (default: `'md'`)
2116
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `children` / `className` / `style`.
1110
2117
 
1111
2118
  ## Overlays
1112
2119
 
@@ -1174,6 +2181,53 @@ Props (Dialog.Root):
1174
2181
  - `role`: `'dialog'` | `'alertdialog'`
1175
2182
  - `tabIndex`: `number`
1176
2183
 
2184
+ ### CommandPalette
2185
+
2186
+ A command palette: a `Modal` holding a search field and the list of commands it
2187
+ filters. Typing matches each item's `label` and `keywords`, case-insensitively.
2188
+ `ArrowDown` / `ArrowUp` move through the matches and wrap around, `Enter` or a
2189
+ click runs the item's `onSelect` after closing the palette, and `Escape` closes
2190
+ it; focus stays in the search field throughout (the WAI-ARIA combobox, with the
2191
+ list always shown). Items that share a `group` are gathered under that heading,
2192
+ in the order the group first appears, and `shortcut` keys are drawn with `Kbd`.
2193
+ Each opening starts from an empty search. Open and close it like `Modal`
2194
+ (`isOpen` / `defaultOpen` / `onClose`); a key such as ⌘K / Ctrl+K is the
2195
+ application's to wire.
2196
+
2197
+ ```tsx
2198
+ import { CommandPalette } from '@k8ordo/ui';
2199
+
2200
+ <CommandPalette
2201
+ isOpen={isOpen}
2202
+ onClose={() => setIsOpen(false)}
2203
+ items={[
2204
+ {
2205
+ id: 'new-file',
2206
+ label: 'New file',
2207
+ group: 'File',
2208
+ shortcut: ['⌘', 'N'],
2209
+ onSelect: createFile,
2210
+ },
2211
+ {
2212
+ id: 'theme',
2213
+ label: 'Toggle theme',
2214
+ keywords: ['dark', 'light'],
2215
+ onSelect: toggleTheme,
2216
+ },
2217
+ ]}
2218
+ />;
2219
+ ```
2220
+
2221
+ Props:
2222
+
2223
+ - `items`: `readonly CommandPaletteItem[]` (required)
2224
+ - `aria-label`: `string`
2225
+ - `defaultOpen`: `boolean`
2226
+ - `isOpen`: `boolean`
2227
+ - `onClose`: `() => void`
2228
+ - `placeholder`: `string`
2229
+ - `ref`: `Ref<HTMLDialogElement>`
2230
+
1177
2231
  ### Drawer
1178
2232
 
1179
2233
  A side panel. It uses Modal internally.
@@ -1320,6 +2374,66 @@ Props (DropdownMenu.Trigger):
1320
2374
  - `size`: `ComponentProps<typeof Button>['size']` (default: `'md'`)
1321
2375
  - `variant`: `ComponentProps<typeof Button>['variant']` (default: `'solid'`)
1322
2376
 
2377
+ ### ContextMenu
2378
+
2379
+ A menu opened by right-clicking (or by the keyboard's context-menu key or
2380
+ Shift+F10) on an area, shown at the pointer. The inside is `DropdownMenu`'s —
2381
+ `ContextMenu.Content`, `ContextMenu.Item` (`label` / `onAction`), and
2382
+ `ContextMenu.SubMenu` behave exactly like theirs — and only how it opens and
2383
+ where differ. When it closes from inside (an item, Escape), focus returns to
2384
+ the element that had it before it opened; closing by clicking elsewhere leaves
2385
+ focus where the click put it.
2386
+
2387
+ `ContextMenu.Trigger` fills a slot: `renderItem` receives `onContextMenu` to
2388
+ put on the area. To open it from the keyboard, the area (or something in it)
2389
+ has to be able to take focus.
2390
+
2391
+ ```tsx
2392
+ import { ContextMenu } from '@k8ordo/ui';
2393
+
2394
+ <ContextMenu.Root>
2395
+ <ContextMenu.Trigger
2396
+ renderItem={(props) => (
2397
+ <div {...props} tabIndex={0}>
2398
+ report.pdf
2399
+ </div>
2400
+ )}
2401
+ />
2402
+ <ContextMenu.Content>
2403
+ <ContextMenu.Item label="Rename" onAction={rename} />
2404
+ <ContextMenu.SubMenu label="Move to">
2405
+ <ContextMenu.Item label="Archive" onAction={archive} />
2406
+ </ContextMenu.SubMenu>
2407
+ <ContextMenu.Item label="Delete" onAction={remove} />
2408
+ </ContextMenu.Content>
2409
+ </ContextMenu.Root>;
2410
+ ```
2411
+
2412
+ Props (Root):
2413
+
2414
+ - `children`: `ReactNode`
2415
+ - `defaultOpen`: `boolean`
2416
+ - `isOpen`: `boolean`
2417
+ - `onChange`: `(isOpen: boolean) => void`
2418
+
2419
+ Props (ContextMenu.Content):
2420
+
2421
+ - `children`: `ReactNode`
2422
+
2423
+ Props (ContextMenu.Item):
2424
+
2425
+ - `label`: `string` (required)
2426
+ - `onAction`: `() => void` (required)
2427
+
2428
+ Props (ContextMenu.SubMenu):
2429
+
2430
+ - `label`: `string` (required)
2431
+ - `children`: `ReactNode`
2432
+
2433
+ Props (ContextMenu.Trigger):
2434
+
2435
+ - `renderItem`: `(props: { onContextMenu: MouseEventHandler<HTMLElement>; }) => ReactElement` (required)
2436
+
1323
2437
  ### ListBox
1324
2438
 
1325
2439
  List selection, as a compound component. The choices use the same `Option` (`{ value, label }`) as `Select`.
@@ -1371,11 +2485,261 @@ Props (ListBox.Trigger):
1371
2485
  - `label`: `string`
1372
2486
  - `size`: `ComponentProps<typeof Button>['size']` (default: `'md'`)
1373
2487
 
2488
+ ## Layout
2489
+
2490
+ ### Separator
2491
+
2492
+ A dividing rule.
2493
+
2494
+ ```tsx
2495
+ import { Separator } from '@k8ordo/ui';
2496
+
2497
+ <Separator />
2498
+ <Separator color="mute" />
2499
+ <Separator color="subtle" />
2500
+ <Separator orientation="vertical" />
2501
+ ```
2502
+
2503
+ Props:
2504
+
2505
+ - `color`: `'base'` | `'mute'` | `'subtle'` (default: `'base'`)
2506
+ - `orientation`: `'horizontal'` | `'vertical'` (default: `'horizontal'`)
2507
+ - Other props are forwarded to `HTMLAttributes<HTMLSpanElement>`, except `children` / `role` / `aria-orientation` / `className` / `style`.
2508
+
2509
+ ### Stack
2510
+
2511
+ Lays children out along one axis. Pick `gap` from the spacing tokens.
2512
+
2513
+ ```tsx
2514
+ import { Stack } from '@k8ordo/ui';
2515
+
2516
+ <Stack gap="lg">
2517
+ <Card>1</Card>
2518
+ <Card>2</Card>
2519
+ </Stack>
2520
+
2521
+ <Stack direction="row" justify="between" align="center">
2522
+ <Heading level="h2">Title</Heading>
2523
+ <Button>Action</Button>
2524
+ </Stack>
2525
+ ```
2526
+
2527
+ Props:
2528
+
2529
+ - `align`: `'start'` | `'center'` | `'end'` | `'stretch'`
2530
+ - `children`: `ReactNode`
2531
+ - `direction`: `'row'` | `'column'` (default: `'column'`)
2532
+ - `gap`: `GapSize` (default: `'md'`)
2533
+ - `justify`: `'start'` | `'center'` | `'end'` | `'between'`
2534
+ - `padding`: `PaddingSize`
2535
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style`.
2536
+
2537
+ ### Grid
2538
+
2539
+ A grid layout. Passing `'auto-fill'` or `'auto-fit'` to `cols` wraps with `minItemSize` as the lower bound.
2540
+
2541
+ ```tsx
2542
+ import { Grid } from '@k8ordo/ui';
2543
+
2544
+ <Grid cols={3} gap="md">
2545
+ <Card>1</Card>
2546
+ <Card>2</Card>
2547
+ <Card>3</Card>
2548
+ </Grid>
2549
+
2550
+ <Grid cols="auto-fill" minItemSize={64}>
2551
+ {items.map((item) => (
2552
+ <Card key={item.id}>{item.name}</Card>
2553
+ ))}
2554
+ </Grid>
2555
+ ```
2556
+
2557
+ Props:
2558
+
2559
+ - `children`: `ReactNode`
2560
+ - `cols`: `1` | `2` | `3` | `4` | `5` | `6` | `'auto-fill'` | `'auto-fit'` (default: `'auto-fill'`)
2561
+ - `gap`: `GapSize` (default: `'md'`)
2562
+ - `minItemSize`: `24` | `32` | `40` | `48` | `64` | `80` (default: `48`)
2563
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style`.
2564
+
2565
+ ### ResizablePanels
2566
+
2567
+ Two panes with a divider between them that the user drags or moves with the
2568
+ arrow keys (the WAI-ARIA window splitter). `value` is the first pane's share in
2569
+ percent, kept between `min` and `max`; the second pane takes the rest. The root
2570
+ fills its parent, so give that parent a size — a vertical split needs a height.
2571
+ Each pane scrolls on its own.
2572
+
2573
+ The divider is a focusable `separator` whose value is the first pane's size and
2574
+ whose `aria-controls` points at it. The arrow keys move it the way they point
2575
+ on screen — left/right when the panes sit side by side, up/down when they are
2576
+ stacked — so in a right-to-left page `ArrowLeft` widens a first pane that sits
2577
+ on the right; `Home` / `End` jump to `min` / `max`. `orientation="horizontal"`
2578
+ lays the panes along the line of text, so in vertical writing mode they stack.
2579
+ Name the divider after the first pane with `aria-labelledby` when it has a
2580
+ heading; otherwise it falls back to `resizablePanelsHandle` from the dictionary.
2581
+
2582
+ ```tsx
2583
+ import { ResizablePanels } from '@k8ordo/ui';
2584
+
2585
+ <div style={{ height: '24rem' }}>
2586
+ <ResizablePanels.Root defaultValue={25}>
2587
+ <ResizablePanels.Panel>
2588
+ <h2 id="files">Files</h2>
2589
+ </ResizablePanels.Panel>
2590
+ <ResizablePanels.Handle aria-labelledby="files" />
2591
+ <ResizablePanels.Panel>…</ResizablePanels.Panel>
2592
+ </ResizablePanels.Root>
2593
+ </div>;
2594
+ ```
2595
+
2596
+ Props (Root):
2597
+
2598
+ - `children`: `ReactNode`
2599
+ - `defaultValue`: `number` (default: `50`)
2600
+ - `max`: `number` (default: `90`)
2601
+ - `min`: `number` (default: `10`)
2602
+ - `onChange`: `(value: number) => void`
2603
+ - `orientation`: `'horizontal'` | `'vertical'` (default: `'horizontal'`)
2604
+ - `ref`: `Ref<HTMLDivElement>`
2605
+ - `step`: `number` (default: `5`)
2606
+ - `value`: `number`
2607
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style`.
2608
+
2609
+ Props (ResizablePanels.Panel):
2610
+
2611
+ - `children`: `ReactNode`
2612
+ - `ref`: `Ref<HTMLDivElement>`
2613
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style`.
2614
+
2615
+ Props (ResizablePanels.Handle):
2616
+
2617
+ - _No props of its own._
2618
+ - Other props are forwarded to `HTMLAttributes<HTMLDivElement>`, except `className` / `style` / `role` / `children` / `tabIndex` / `aria-orientation` / `aria-valuenow` / `aria-valuemin` / `aria-valuemax` / `aria-controls`.
2619
+
2620
+ ## Observers
2621
+
2622
+ Components that watch their children and report back without adding a wrapper
2623
+ element. They are built on React's Fragment refs, so whatever the children
2624
+ render is what gets observed — including host elements that mount later.
2625
+
2626
+ ### InView
2627
+
2628
+ Reports whether its children are inside the viewport, or inside `root`.
2629
+
2630
+ ```tsx
2631
+ import { InView } from '@k8ordo/ui';
2632
+
2633
+ const [isInView, setIsInView] = useState(false);
2634
+
2635
+ <InView onChange={setIsInView}>
2636
+ <section>…</section>
2637
+ </InView>;
2638
+
2639
+ // inside a scroll container, and only until it has been seen once
2640
+ const [container, setContainer] = useState<HTMLElement | null>(null);
2641
+
2642
+ <div ref={setContainer} style={{ overflowY: 'auto' }}>
2643
+ <InView once onChange={reveal} root={container} rootMargin="0px 0px 24px 0px">
2644
+ <img alt="" src="…" />
2645
+ </InView>
2646
+ </div>;
2647
+ ```
2648
+
2649
+ - `onChange` reports the state as soon as a host element is observed, then again
2650
+ each time it flips. The same value is never reported twice in a row, even
2651
+ when `root` changes and the observer is re-created.
2652
+ - With several host elements, `isInView` is `true` while **any** of them
2653
+ intersects, and it follows children that mount or unmount later. While the
2654
+ children render no host element, `onChange` is not called at all — the first
2655
+ report comes once one mounts.
2656
+ - `once` stops observing after the first `true`.
2657
+ - Hold `root` in state, not a `RefObject`: the observer has to be re-created
2658
+ once the element exists.
2659
+
2660
+ Props:
2661
+
2662
+ - `children`: `ReactNode` (required)
2663
+ - `onChange`: `(isInView: boolean) => void` (required)
2664
+ - `once`: `boolean` (default: `false`)
2665
+ - `root`: `Element` | `null` (default: `null`)
2666
+ - `rootMargin`: `string` (default: `'0px'`)
2667
+ - `threshold`: `number` (default: `0`)
2668
+
2669
+ ### Resize
2670
+
2671
+ Calls `onChange` when the size of its children changes.
2672
+
2673
+ ```tsx
2674
+ import { Resize } from '@k8ordo/ui';
2675
+
2676
+ <Resize onChange={remeasure}>
2677
+ <div>…</div>
2678
+ </Resize>;
2679
+ ```
2680
+
2681
+ - `onChange` is also called once when observation starts, as a native
2682
+ `ResizeObserver` is.
2683
+ - It takes no argument; read what you need from the DOM in the handler.
2684
+
2685
+ Props:
2686
+
2687
+ - `children`: `ReactNode` (required)
2688
+ - `onChange`: `() => void` (required)
2689
+
2690
+ ## Icons
2691
+
2692
+ Decorative icons, all from the root entry. Each renders an `<svg>` with
2693
+ `aria-hidden` and `focusable="false"`, so the accessible name belongs to the
2694
+ control around it — an `IconButton`'s `label`, for example.
2695
+
2696
+ ```tsx
2697
+ import { AlertIcon, ChevronIcon, CloseIcon } from '@k8ordo/ui';
2698
+
2699
+ <CloseIcon />
2700
+ <ChevronIcon direction="down" size="sm" />
2701
+ <AlertIcon status="warning" />
2702
+ ```
2703
+
2704
+ Every icon takes one optional prop and nothing else:
2705
+
2706
+ - `size`: `'xs'` | `'sm'` | `'md'` | `'lg'` | `'xl'` | `'2xl'` | `'3xl'` (default: `'md'`)
2707
+
2708
+ Two of them also require a prop that picks the glyph:
2709
+
2710
+ - `ChevronIcon`: `direction`: `Direction` (`'up'` | `'down'` | `'left'` | `'right'`)
2711
+ - `AlertIcon`: `status`: `Status` (`'success'` | `'info'` | `'warning'` | `'error'`)
2712
+
2713
+ <!-- generated:icons -->
2714
+
2715
+ The icons: `AccessibilityIcon`, `AIIcon`, `AlertIcon`, `AssistantIcon`,
2716
+ `AtomIcon`, `BadIcon`, `BlogIcon`, `BoringIcon`, `CheckIcon`, `ChevronIcon`,
2717
+ `ChromeIcon`, `CloseIcon`, `CodeXmlIcon`, `ColorContrastIcon`, `ColorInfoIcon`,
2718
+ `ColorScaleIcon`, `CopyIcon`, `DarkModeIcon`, `DifficultIcon`, `EasyIcon`,
2719
+ `EdgeIcon`, `ExternalLinkIcon`, `FirefoxIcon`, `FlaskIcon`, `ForkIcon`,
2720
+ `FormIcon`, `FullscreenIcon`, `GitHubIcon`, `GoodIcon`, `HistoryIcon`,
2721
+ `HorizontalWritingIcon`, `InformativeIcon`, `InterestingIcon`, `LightModeIcon`,
2722
+ `LinkIcon`, `ListIcon`, `LocationIcon`, `LockIcon`, `LockOpenIcon`, `LogoIcon`,
2723
+ `MailIcon`, `MinusIcon`, `MixedColorIcon`, `NavigationMenuIcon`, `NewsIcon`,
2724
+ `PackageIcon`, `PaletteIcon`, `PlusIcon`, `PrepareIcon`, `PublishDateIcon`,
2725
+ `QiitaIcon`, `RefreshIcon`, `RSSIcon`, `SafariIcon`, `SendIcon`, `ShallowIcon`,
2726
+ `ShieldCheckIcon`, `SlideIcon`, `SparklesIcon`, `SquircleIcon`, `SubscribeIcon`,
2727
+ `TableIcon`, `TagIcon`, `TwitterIcon`, `UpdateDateIcon`, `VerticalWritingIcon`,
2728
+ `ViewIcon`, `ViewOffIcon`.
2729
+
2730
+ <!-- /generated:icons -->
2731
+
2732
+ `Logo` is the mark `LogoIcon` draws, as a bare `<svg>` without the sizing: it
2733
+ takes `className`, `aria-hidden`, and `focusable` (`Partial<IconRenderProps>`)
2734
+ instead of `size`. The types `BaseIconProps` (`{ size }`) and
2735
+ `IconRenderProps` (`{ className: string; 'aria-hidden': true; focusable: 'false' }`)
2736
+ are exported as well.
2737
+
1374
2738
  ## Providers
1375
2739
 
1376
2740
  ### UIProvider
1377
2741
 
1378
- Wrap the app root once. It includes ToastProvider and the message dictionary (i18n, below).
2742
+ Wrap the app root once. It includes ToastProvider. The components' own wording needs no provider: it follows `@k8ordo/i18n` (i18n, below).
1379
2743
 
1380
2744
  ```tsx
1381
2745
  import { UIProvider } from '@k8ordo/ui';
@@ -1388,11 +2752,14 @@ import { UIProvider } from '@k8ordo/ui';
1388
2752
  Props:
1389
2753
 
1390
2754
  - `children`: `ReactNode`
1391
- - `messages`: `Partial<Messages>`
1392
2755
 
1393
2756
  ### PortalRootProvider
1394
2757
 
1395
- Sets the root element for portals.
2758
+ A context that shares a portal container with your own `createPortal` calls;
2759
+ read it with `usePortalRoot()`. The library's overlays do not read it — they
2760
+ open in the top layer (`<dialog>`, the Popover API), and toasts go where
2761
+ `ToastProvider`'s `portalRef` points. `Modal` passes its own `<dialog>` as the
2762
+ value, so a portal placed through `usePortalRoot()` inside a modal stays in it.
1396
2763
 
1397
2764
  ```tsx
1398
2765
  import { PortalRootProvider, usePortalRoot } from '@k8ordo/ui';
@@ -1402,73 +2769,128 @@ import { PortalRootProvider, usePortalRoot } from '@k8ordo/ui';
1402
2769
 
1403
2770
  Props:
1404
2771
 
2772
+ - `value`: `RefObject<HTMLElement | null>` (required)
1405
2773
  - `children`: `ReactNode`
1406
- - `value`: `RefObject<HTMLElement | null>`
1407
2774
 
1408
2775
  ## i18n (message dictionary)
1409
2776
 
1410
- The wording components own internally (close, required, loading, …) comes from a dictionary. **It defaults to Japanese**, and works without a provider and without passing `messages`.
2777
+ The wording components own internally (close, required, loading, …) comes from a dictionary, picked by `@k8ordo/i18n`'s current locale. There is no provider and nothing to pass.
1411
2778
 
1412
- To switch to English, pass `en` from `@k8ordo/ui/i18n`.
2779
+ - An application that defines its locale set with `defineLocales` gets the locale its messages render in: the one the URL names, or the set's default.
2780
+ - An application that defines no set gets **English**, whatever its URL starts with, so the server and the browser agree.
2781
+ - The module defining the set has to be loaded in the browser as well; where no set is defined the components speak English.
1413
2782
 
1414
- ```tsx
1415
- import { UIProvider } from '@k8ordo/ui';
1416
- import { en } from '@k8ordo/ui/i18n';
1417
-
1418
- <UIProvider messages={en}>
1419
- <App />
1420
- </UIProvider>;
1421
- ```
1422
-
1423
- To replace only part of it, spread the dictionary and override those keys (`Partial<Messages>`, so you need not fill in every key).
2783
+ `ja` and `en` ship with the library. Register any other locale — or replace a built-in one — with `registerMessages`, next to where the set is defined:
1424
2784
 
1425
2785
  ```tsx
1426
- <UIProvider messages={{ ...en, close: 'Dismiss' }}>
1427
- <App />
1428
- </UIProvider>
1429
- ```
2786
+ import { en, registerMessages } from '@k8ordo/ui/i18n';
2787
+ import type { Messages } from '@k8ordo/ui/i18n';
1430
2788
 
1431
- To stay in Japanese and change only one string, pass just that key.
2789
+ const fr: Messages = { close: 'Fermer' /* …every key */ };
2790
+ registerMessages('fr', fr);
1432
2791
 
1433
- ```tsx
1434
- <UIProvider messages={{ close: '閉じる(Esc)' }}>
1435
- <App />
1436
- </UIProvider>
2792
+ registerMessages('en', { ...en, close: 'Dismiss' });
1437
2793
  ```
1438
2794
 
2795
+ A regional tag without a dictionary of its own (`en-US`) reads its language's (`en`). Rendering in a locale nothing has text for throws, naming the locale and `registerMessages`.
2796
+
1439
2797
  ### Resolution order
1440
2798
 
1441
- **Component prop > the dictionary passed to the provider > the built-in default (Japanese)**.
2799
+ **Component prop > registered dictionary > built-in dictionary**.
1442
2800
 
1443
2801
  A component with a wording prop of its own — `Spinner`'s `label`, `Alert`'s `closeLabel`, `PasswordInput`'s `showLabel` / `hideLabel`, `Pagination`'s `prevLabel` / `nextLabel` — takes that prop over the dictionary.
1444
2802
 
1445
2803
  ```tsx
1446
- // Even with the en dictionary, this one Spinner reads 「保存中」
2804
+ // Whatever the locale, this one Spinner reads 「保存中」
1447
2805
  <Spinner label="保存中" />
1448
2806
  ```
1449
2807
 
1450
2808
  ### Exports
1451
2809
 
1452
2810
  ```tsx
1453
- import { en, ja, type Messages } from '@k8ordo/ui/i18n';
2811
+ import {
2812
+ en,
2813
+ getMessages,
2814
+ ja,
2815
+ messageUsage,
2816
+ registerMessages,
2817
+ type Messages,
2818
+ } from '@k8ordo/ui/i18n';
1454
2819
  ```
1455
2820
 
1456
- `ja` and `en` are exported only from the `@k8ordo/ui/i18n` subpath, not the root, so the dictionaries stay out of the main bundle.
2821
+ ### Reading the wording in your own elements
2822
+
2823
+ `getMessages` returns the wording in effect: the dictionary for the current locale, a registered one before a built-in one. Read from it in an element you draw through `renderItem`, or in a component of your own that sits beside the library, and it follows the same language and replacements as the components do. It is not a hook, so a Server Component calls it too.
2824
+
2825
+ ```tsx
2826
+ import { getMessages } from '@k8ordo/ui/i18n';
2827
+
2828
+ function DismissButton({ onDismiss }) {
2829
+ const { close } = getMessages();
2830
+ return (
2831
+ <button aria-label={close} onClick={onDismiss} type="button">
2832
+ ×
2833
+ </button>
2834
+ );
2835
+ }
2836
+ ```
1457
2837
 
1458
2838
  ### Key list
1459
2839
 
1460
- Every key in the `Messages` type. All values are `string`.
1461
-
1462
- | Category | Keys |
1463
- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
1464
- | Common | `close`, `required`, `loading`, `avatar`, `color` |
1465
- | Alert | `alertSuccess`, `alertInfo`, `alertWarning`, `alertError` |
1466
- | Toast | `toastRegion` |
1467
- | Autocomplete | `autocompletePlaceholder`, `autocompleteRemoveTag`, `autocompleteClear`, `autocompleteEmpty` |
1468
- | FileField | `fileFieldRemove` |
1469
- | NumberField | `numberFieldIncrement`, `numberFieldDecrement` |
1470
- | PasswordInput | `passwordShow`, `passwordHide` |
1471
- | ListBox | `listBoxPlaceholder` |
1472
- | Breadcrumb | `breadcrumb` |
1473
- | Pagination | `paginationLabel`, `paginationPrevious`, `paginationNext` |
1474
- | AI chat | `chat`, `scrollToLatest`, `reasoning`, `reasoningStreaming`, `suggestions`, `send`, `stop`, `toolInput`, `toolOutput`, `toolError` |
2840
+ Every key in the `Messages` type, grouped by the components that draw it —
2841
+ the same mapping `messageUsage` exports. All values are `string`.
2842
+
2843
+ <!-- generated:message-keys -->
2844
+
2845
+ - Alert / Dialog / Drawer / Response: `close`
2846
+ - FormControl: `required`
2847
+ - Spinner / Progress / Combobox: `loading`
2848
+ - Avatar: `avatar`
2849
+ - Code: `color`
2850
+ - Alert / Callout: `alertSuccess`, `alertInfo`, `alertWarning`, `alertError`
2851
+ - ToastProvider: `toastRegion`
2852
+ - CopyButton / Message.Copy: `copy`
2853
+ - CopyButton / CodeBlock / Message.Copy: `copied`, `copyFailed`
2854
+ - Autocomplete: `autocompletePlaceholder`, `autocompleteRemoveTag`, `autocompleteClear`, `autocompleteEmpty`
2855
+ - Combobox: `comboboxToggle`, `comboboxEmpty`, `comboboxFailed`
2856
+ - FileField: `fileFieldRemove`
2857
+ - FileField.Dropzone / FileField (generative UI): `fileFieldTrigger`, `fileFieldDrop`
2858
+ - NumberField: `numberFieldIncrement`, `numberFieldDecrement`, `numberFieldRangeUnderflow` (`{min}` is replaced), `numberFieldRangeOverflow` (`{max}` is replaced)
2859
+ - RangeSlider: `rangeSliderStart`, `rangeSliderEnd`
2860
+ - Calendar / DatePicker: `calendarPreviousMonth`, `calendarNextMonth`
2861
+ - DatePicker: `datePickerOpen`, `datePickerDialog`
2862
+ - ColorPicker: `colorPickerHue`, `colorPickerSaturation`, `colorPickerLightness`, `colorPickerSwatches`, `colorPickerOpen`
2863
+ - PasswordInput: `passwordShow`, `passwordHide`
2864
+ - ListBox: `listBoxPlaceholder`
2865
+ - Breadcrumb: `breadcrumb`
2866
+ - Tabs (generative UI): `tabList`
2867
+ - Pagination: `paginationLabel`, `paginationPrevious`, `paginationNext`
2868
+ - Stepper: `stepperComplete`
2869
+ - DataTable: `dataTableColumns`, `dataTableSelectAll`, `dataTableSelectRow`
2870
+ - CodeBlock: `codeBlockCopy`
2871
+ - Carousel: `carousel`, `carouselPrevious`, `carouselNext`
2872
+ - Carousel.Slide: `carouselSlide`
2873
+ - ResizablePanels.Handle: `resizablePanelsHandle`
2874
+ - TableOfContents: `tableOfContents`
2875
+ - CommandPalette: `commandPalette`, `commandPaletteSearch`, `commandPaletteEmpty`
2876
+ - Conversation.Messages: `chat`
2877
+ - Conversation.ScrollButton: `scrollToLatest`
2878
+ - Reasoning: `reasoning`, `reasoningStreaming`
2879
+ - Suggestion.List: `suggestions`
2880
+ - PromptInput: `send`, `stop`
2881
+ - PromptInput.Attach: `attach`
2882
+ - Attachment.List / PromptInput.Attachments: `attachments`
2883
+ - PromptInput.Attachments: `attachmentRemove`
2884
+ - Attachment.Item / PromptInput.Attachments: `attachmentImage`
2885
+ - Source.List: `sources`
2886
+ - Message.Actions: `messageActions`
2887
+ - Message.Regenerate: `regenerate`
2888
+ - Message.Feedback: `feedbackPositive`, `feedbackNegative`
2889
+ - ToolInvocation: `toolInput`, `toolOutput`, `toolError`, `toolDenied`, `toolApprovalRequest`, `toolApprove`, `toolDeny`
2890
+ - Response: `responseCopied`, `responseCopyCode`, `responseCopyLink`, `responseCopyTable`, `responseCopyTableAsCsv`, `responseCopyTableAsMarkdown`, `responseCopyTableAsTsv`, `responseDownloadDiagram`, `responseDownloadDiagramAsMmd`, `responseDownloadDiagramAsPng`, `responseDownloadDiagramAsSvg`, `responseDownloadFile`, `responseDownloadImage`, `responseDownloadTable`, `responseDownloadTableAsCsv`, `responseDownloadTableAsMarkdown`, `responseExitFullscreen`, `responseViewFullscreen`, `responseImageNotAvailable`, `responseOpenExternalLink`, `responseExternalLinkWarning`, `responseOpenLink`
2891
+
2892
+ <!-- /generated:message-keys -->
2893
+
2894
+ `fileFieldTrigger` is the button text of an empty `FileField.Dropzone`, and
2895
+ with `tabList` it is also what the generative-UI renderers fall back to when a
2896
+ spec leaves the trigger text or the tab-list name out.