@logicplanes/kaeso-ui 0.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 (660) hide show
  1. package/AGENTS.md +124 -0
  2. package/CLAUDE.md +3 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE.md +5 -0
  5. package/README.md +43 -0
  6. package/ai/component-guides.md +4760 -0
  7. package/ai/component-registry.json +12626 -0
  8. package/ai/component-registry.md +2950 -0
  9. package/ai/icons.md +28 -0
  10. package/ai/tokens.md +207 -0
  11. package/dist/blocks/delete-resource.js +35 -0
  12. package/dist/blocks/delete-resource.js.map +1 -0
  13. package/dist/blocks/pages.js +70 -0
  14. package/dist/blocks/pages.js.map +1 -0
  15. package/dist/blocks/resource-list.js +68 -0
  16. package/dist/blocks/resource-list.js.map +1 -0
  17. package/dist/chunks/app-frame-BBIMch8X.js +224 -0
  18. package/dist/chunks/app-frame-BBIMch8X.js.map +1 -0
  19. package/dist/chunks/arrow-focus-BcySmwwU.js +11 -0
  20. package/dist/chunks/arrow-focus-BcySmwwU.js.map +1 -0
  21. package/dist/chunks/avatar-D2nJNbtC.js +83 -0
  22. package/dist/chunks/avatar-D2nJNbtC.js.map +1 -0
  23. package/dist/chunks/badge-Dkyhs0_j.js +26 -0
  24. package/dist/chunks/badge-Dkyhs0_j.js.map +1 -0
  25. package/dist/chunks/banner-BiziJuBZ.js +85 -0
  26. package/dist/chunks/banner-BiziJuBZ.js.map +1 -0
  27. package/dist/chunks/button-BHKAE6ui.js +164 -0
  28. package/dist/chunks/button-BHKAE6ui.js.map +1 -0
  29. package/dist/chunks/calendar-B5SSTE56.js +104 -0
  30. package/dist/chunks/calendar-B5SSTE56.js.map +1 -0
  31. package/dist/chunks/checkbox-BgRdbfCE.js +124 -0
  32. package/dist/chunks/checkbox-BgRdbfCE.js.map +1 -0
  33. package/dist/chunks/cn-CidVUKMm.js +669 -0
  34. package/dist/chunks/cn-CidVUKMm.js.map +1 -0
  35. package/dist/chunks/code-DDyOlIFO.js +23 -0
  36. package/dist/chunks/code-DDyOlIFO.js.map +1 -0
  37. package/dist/chunks/code-block-BbK6E4Eo.js +273 -0
  38. package/dist/chunks/code-block-BbK6E4Eo.js.map +1 -0
  39. package/dist/chunks/collapsible-Cw_1aem5.js +87 -0
  40. package/dist/chunks/collapsible-Cw_1aem5.js.map +1 -0
  41. package/dist/chunks/combobox-D8pQk22r.js +291 -0
  42. package/dist/chunks/combobox-D8pQk22r.js.map +1 -0
  43. package/dist/chunks/context-BELWW79w.js +20 -0
  44. package/dist/chunks/context-BELWW79w.js.map +1 -0
  45. package/dist/chunks/copy-button-mAzHjYXq.js +109 -0
  46. package/dist/chunks/copy-button-mAzHjYXq.js.map +1 -0
  47. package/dist/chunks/core-Bgefcs3z.js +467 -0
  48. package/dist/chunks/core-Bgefcs3z.js.map +1 -0
  49. package/dist/chunks/date-popup-Er1oQwtU.js +35 -0
  50. package/dist/chunks/date-popup-Er1oQwtU.js.map +1 -0
  51. package/dist/chunks/de-BCg1eRVd.js +384 -0
  52. package/dist/chunks/de-BCg1eRVd.js.map +1 -0
  53. package/dist/chunks/dev-CR1p7gCm.js +30 -0
  54. package/dist/chunks/dev-CR1p7gCm.js.map +1 -0
  55. package/dist/chunks/dialog-CVqSVk3T.js +283 -0
  56. package/dist/chunks/dialog-CVqSVk3T.js.map +1 -0
  57. package/dist/chunks/empty-DIbBpytW.js +38 -0
  58. package/dist/chunks/empty-DIbBpytW.js.map +1 -0
  59. package/dist/chunks/field-d8V94bvZ.js +119 -0
  60. package/dist/chunks/field-d8V94bvZ.js.map +1 -0
  61. package/dist/chunks/field-press-DwzBFafd.js +10 -0
  62. package/dist/chunks/field-press-DwzBFafd.js.map +1 -0
  63. package/dist/chunks/i18n-0RW0IAf3.js +10 -0
  64. package/dist/chunks/i18n-0RW0IAf3.js.map +1 -0
  65. package/dist/chunks/icon-Bha63LaN.js +90 -0
  66. package/dist/chunks/icon-Bha63LaN.js.map +1 -0
  67. package/dist/chunks/icon-CeNQtobn.js +47 -0
  68. package/dist/chunks/icon-CeNQtobn.js.map +1 -0
  69. package/dist/chunks/icon-button-Bw67oKtx.js +50 -0
  70. package/dist/chunks/icon-button-Bw67oKtx.js.map +1 -0
  71. package/dist/chunks/identity-DQinp-Ep.js +342 -0
  72. package/dist/chunks/identity-DQinp-Ep.js.map +1 -0
  73. package/dist/chunks/input-Biakz6GB.js +114 -0
  74. package/dist/chunks/input-Biakz6GB.js.map +1 -0
  75. package/dist/chunks/input-group-DQqEPFj_.js +87 -0
  76. package/dist/chunks/input-group-DQqEPFj_.js.map +1 -0
  77. package/dist/chunks/inset-BLMdxX4I.js +18 -0
  78. package/dist/chunks/inset-BLMdxX4I.js.map +1 -0
  79. package/dist/chunks/iso-field-CznqNVrH.js +31 -0
  80. package/dist/chunks/iso-field-CznqNVrH.js.map +1 -0
  81. package/dist/chunks/json-view-B6KHIWzh.js +174 -0
  82. package/dist/chunks/json-view-B6KHIWzh.js.map +1 -0
  83. package/dist/chunks/kbd-CBcBDvqU.js +132 -0
  84. package/dist/chunks/kbd-CBcBDvqU.js.map +1 -0
  85. package/dist/chunks/labelled-D5udSPUI.js +16 -0
  86. package/dist/chunks/labelled-D5udSPUI.js.map +1 -0
  87. package/dist/chunks/link-provider---9JdxTg.js +27 -0
  88. package/dist/chunks/link-provider---9JdxTg.js.map +1 -0
  89. package/dist/chunks/link-tTapNTpM.js +47 -0
  90. package/dist/chunks/link-tTapNTpM.js.map +1 -0
  91. package/dist/chunks/list-CN8pO8lv.js +475 -0
  92. package/dist/chunks/list-CN8pO8lv.js.map +1 -0
  93. package/dist/chunks/loader-CUehxvSZ.js +57 -0
  94. package/dist/chunks/loader-CUehxvSZ.js.map +1 -0
  95. package/dist/chunks/logo-VmXYhIJL.js +149 -0
  96. package/dist/chunks/logo-VmXYhIJL.js.map +1 -0
  97. package/dist/chunks/markdown-C-iJsYnd.js +313 -0
  98. package/dist/chunks/markdown-C-iJsYnd.js.map +1 -0
  99. package/dist/chunks/menu-OURWt_hD.js +447 -0
  100. package/dist/chunks/menu-OURWt_hD.js.map +1 -0
  101. package/dist/chunks/message-BxTkh1oA.js +49 -0
  102. package/dist/chunks/message-BxTkh1oA.js.map +1 -0
  103. package/dist/chunks/meter-DWhyzFGQ.js +84 -0
  104. package/dist/chunks/meter-DWhyzFGQ.js.map +1 -0
  105. package/dist/chunks/page-header-BIRswR73.js +41 -0
  106. package/dist/chunks/page-header-BIRswR73.js.map +1 -0
  107. package/dist/chunks/pagination-DYdLm18-.js +85 -0
  108. package/dist/chunks/pagination-DYdLm18-.js.map +1 -0
  109. package/dist/chunks/popover-BE7VO-a4.js +50 -0
  110. package/dist/chunks/popover-BE7VO-a4.js.map +1 -0
  111. package/dist/chunks/popover-root-D3q89WNF.js +26 -0
  112. package/dist/chunks/popover-root-D3q89WNF.js.map +1 -0
  113. package/dist/chunks/portal-context-BBbzDKmY.js +33 -0
  114. package/dist/chunks/portal-context-BBbzDKmY.js.map +1 -0
  115. package/dist/chunks/radio-CUxbLCvD.js +110 -0
  116. package/dist/chunks/radio-CUxbLCvD.js.map +1 -0
  117. package/dist/chunks/range-popup-AbRzgHtY.js +111 -0
  118. package/dist/chunks/range-popup-AbRzgHtY.js.map +1 -0
  119. package/dist/chunks/restore-focus-Cap1Q-R3.js +19 -0
  120. package/dist/chunks/restore-focus-Cap1Q-R3.js.map +1 -0
  121. package/dist/chunks/rights-CXo7zEf6.js +279 -0
  122. package/dist/chunks/rights-CXo7zEf6.js.map +1 -0
  123. package/dist/chunks/safe-href-G5DBNVwm.js +13 -0
  124. package/dist/chunks/safe-href-G5DBNVwm.js.map +1 -0
  125. package/dist/chunks/scroll-parent-WXvzQcUo.js +14 -0
  126. package/dist/chunks/scroll-parent-WXvzQcUo.js.map +1 -0
  127. package/dist/chunks/section-XStLtXKa.js +87 -0
  128. package/dist/chunks/section-XStLtXKa.js.map +1 -0
  129. package/dist/chunks/segmented-CDTx2n29.js +113 -0
  130. package/dist/chunks/segmented-CDTx2n29.js.map +1 -0
  131. package/dist/chunks/select-Ca3HGsN1.js +223 -0
  132. package/dist/chunks/select-Ca3HGsN1.js.map +1 -0
  133. package/dist/chunks/separator-D5AzVFTS.js +45 -0
  134. package/dist/chunks/separator-D5AzVFTS.js.map +1 -0
  135. package/dist/chunks/sidebar-C4fL_KMm.js +599 -0
  136. package/dist/chunks/sidebar-C4fL_KMm.js.map +1 -0
  137. package/dist/chunks/sidebar-menu-sd9vCpDy.js +33 -0
  138. package/dist/chunks/sidebar-menu-sd9vCpDy.js.map +1 -0
  139. package/dist/chunks/skeleton-kfuqdl1a.js +36 -0
  140. package/dist/chunks/skeleton-kfuqdl1a.js.map +1 -0
  141. package/dist/chunks/slider-DbhjJ3ML.js +57 -0
  142. package/dist/chunks/slider-DbhjJ3ML.js.map +1 -0
  143. package/dist/chunks/status-dot-CBoQpyL4.js +25 -0
  144. package/dist/chunks/status-dot-CBoQpyL4.js.map +1 -0
  145. package/dist/chunks/styles-Btvg2DCf.js +21 -0
  146. package/dist/chunks/styles-Btvg2DCf.js.map +1 -0
  147. package/dist/chunks/swap-text-BhPI8DOf.js +28 -0
  148. package/dist/chunks/swap-text-BhPI8DOf.js.map +1 -0
  149. package/dist/chunks/switch-CztLhA8F.js +59 -0
  150. package/dist/chunks/switch-CztLhA8F.js.map +1 -0
  151. package/dist/chunks/table-DYKa86af.js +67 -0
  152. package/dist/chunks/table-DYKa86af.js.map +1 -0
  153. package/dist/chunks/tag-BsmQ1xPj.js +87 -0
  154. package/dist/chunks/tag-BsmQ1xPj.js.map +1 -0
  155. package/dist/chunks/text-BPglS3vq.js +63 -0
  156. package/dist/chunks/text-BPglS3vq.js.map +1 -0
  157. package/dist/chunks/textarea-Bvk7N2bh.js +156 -0
  158. package/dist/chunks/textarea-Bvk7N2bh.js.map +1 -0
  159. package/dist/chunks/theme-provider-mgUaQK7L.js +69 -0
  160. package/dist/chunks/theme-provider-mgUaQK7L.js.map +1 -0
  161. package/dist/chunks/tone-CWpsHPb6.js +11 -0
  162. package/dist/chunks/tone-CWpsHPb6.js.map +1 -0
  163. package/dist/chunks/toolbar-Dw3uxpsE.js +36 -0
  164. package/dist/chunks/toolbar-Dw3uxpsE.js.map +1 -0
  165. package/dist/chunks/tooltip-CDu00nzg.js +124 -0
  166. package/dist/chunks/tooltip-CDu00nzg.js.map +1 -0
  167. package/dist/chunks/tooltip-root-DWhtAtFb.js +60 -0
  168. package/dist/chunks/tooltip-root-DWhtAtFb.js.map +1 -0
  169. package/dist/chunks/use-controllable-B5Fd12Bx.js +13 -0
  170. package/dist/chunks/use-controllable-B5Fd12Bx.js.map +1 -0
  171. package/dist/chunks/use-copy-Dm2ncvMX.js +24 -0
  172. package/dist/chunks/use-copy-Dm2ncvMX.js.map +1 -0
  173. package/dist/chunks/use-layout-width-DZaLOQMT.js +27 -0
  174. package/dist/chunks/use-layout-width-DZaLOQMT.js.map +1 -0
  175. package/dist/chunks/use-scroll-margin-DWcfIXac.js +25 -0
  176. package/dist/chunks/use-scroll-margin-DWcfIXac.js.map +1 -0
  177. package/dist/chunks/use-truncated-DmQoePvG.js +25 -0
  178. package/dist/chunks/use-truncated-DmQoePvG.js.map +1 -0
  179. package/dist/chunks/utils-CdH72WEp.js +8 -0
  180. package/dist/chunks/utils-CdH72WEp.js.map +1 -0
  181. package/dist/chunks/virtual-rows-BgMdVelU.js +37 -0
  182. package/dist/chunks/virtual-rows-BgMdVelU.js.map +1 -0
  183. package/dist/chunks/when-idle-Cy3pinqO.js +12 -0
  184. package/dist/chunks/when-idle-Cy3pinqO.js.map +1 -0
  185. package/dist/chunks/zh-CN-cPjO1OOo.js +384 -0
  186. package/dist/chunks/zh-CN-cPjO1OOo.js.map +1 -0
  187. package/dist/components/app-frame.js +2 -0
  188. package/dist/components/avatar.js +2 -0
  189. package/dist/components/badge.js +2 -0
  190. package/dist/components/banner.js +2 -0
  191. package/dist/components/button-group.js +69 -0
  192. package/dist/components/button-group.js.map +1 -0
  193. package/dist/components/button.js +2 -0
  194. package/dist/components/calendar.js +2 -0
  195. package/dist/components/charts/bar.js +231 -0
  196. package/dist/components/charts/bar.js.map +1 -0
  197. package/dist/components/charts/donut.js +179 -0
  198. package/dist/components/charts/donut.js.map +1 -0
  199. package/dist/components/charts/gauge.js +70 -0
  200. package/dist/components/charts/gauge.js.map +1 -0
  201. package/dist/components/charts/heatmap.js +83 -0
  202. package/dist/components/charts/heatmap.js.map +1 -0
  203. package/dist/components/charts/sankey.js +110 -0
  204. package/dist/components/charts/sankey.js.map +1 -0
  205. package/dist/components/charts/sparkline.js +72 -0
  206. package/dist/components/charts/sparkline.js.map +1 -0
  207. package/dist/components/charts/timeseries.js +253 -0
  208. package/dist/components/charts/timeseries.js.map +1 -0
  209. package/dist/components/charts.js +10 -0
  210. package/dist/components/checkbox.js +2 -0
  211. package/dist/components/code-block.js +4 -0
  212. package/dist/components/code.js +2 -0
  213. package/dist/components/collapsible.js +2 -0
  214. package/dist/components/combobox.js +2 -0
  215. package/dist/components/command-palette.js +216 -0
  216. package/dist/components/command-palette.js.map +1 -0
  217. package/dist/components/copy-button.js +2 -0
  218. package/dist/components/data-table.js +399 -0
  219. package/dist/components/data-table.js.map +1 -0
  220. package/dist/components/date-picker.js +327 -0
  221. package/dist/components/date-picker.js.map +1 -0
  222. package/dist/components/dialog.js +178 -0
  223. package/dist/components/dialog.js.map +1 -0
  224. package/dist/components/display-options.js +112 -0
  225. package/dist/components/display-options.js.map +1 -0
  226. package/dist/components/empty.js +2 -0
  227. package/dist/components/field.js +2 -0
  228. package/dist/components/filter-chip.js +120 -0
  229. package/dist/components/filter-chip.js.map +1 -0
  230. package/dist/components/filter-row.js +35 -0
  231. package/dist/components/filter-row.js.map +1 -0
  232. package/dist/components/flow.js +111 -0
  233. package/dist/components/flow.js.map +1 -0
  234. package/dist/components/heading.js +28 -0
  235. package/dist/components/heading.js.map +1 -0
  236. package/dist/components/icon-button.js +2 -0
  237. package/dist/components/icon.js +4 -0
  238. package/dist/components/input.js +181 -0
  239. package/dist/components/input.js.map +1 -0
  240. package/dist/components/kaeso/conversation.js +204 -0
  241. package/dist/components/kaeso/conversation.js.map +1 -0
  242. package/dist/components/kaeso/identity.js +2 -0
  243. package/dist/components/kaeso/moments.js +122 -0
  244. package/dist/components/kaeso/moments.js.map +1 -0
  245. package/dist/components/kaeso/rights.js +2 -0
  246. package/dist/components/kaeso/usage.js +101 -0
  247. package/dist/components/kaeso/usage.js.map +1 -0
  248. package/dist/components/kaeso.js +186 -0
  249. package/dist/components/kaeso.js.map +1 -0
  250. package/dist/components/kbd.js +2 -0
  251. package/dist/components/key-value.js +73 -0
  252. package/dist/components/key-value.js.map +1 -0
  253. package/dist/components/label.js +26 -0
  254. package/dist/components/label.js.map +1 -0
  255. package/dist/components/layout.js +72 -0
  256. package/dist/components/layout.js.map +1 -0
  257. package/dist/components/link.js +2 -0
  258. package/dist/components/list.js +2 -0
  259. package/dist/components/loader.js +38 -0
  260. package/dist/components/loader.js.map +1 -0
  261. package/dist/components/logo.js +2 -0
  262. package/dist/components/markdown.js +4 -0
  263. package/dist/components/menu.js +4 -0
  264. package/dist/components/meter.js +2 -0
  265. package/dist/components/page-header.js +2 -0
  266. package/dist/components/pagination.js +2 -0
  267. package/dist/components/playground.js +813 -0
  268. package/dist/components/playground.js.map +1 -0
  269. package/dist/components/popover.js +53 -0
  270. package/dist/components/popover.js.map +1 -0
  271. package/dist/components/portal.js +15 -0
  272. package/dist/components/portal.js.map +1 -0
  273. package/dist/components/qr-code.js +38 -0
  274. package/dist/components/qr-code.js.map +1 -0
  275. package/dist/components/radio.js +2 -0
  276. package/dist/components/scroll-area.js +35 -0
  277. package/dist/components/scroll-area.js.map +1 -0
  278. package/dist/components/section.js +2 -0
  279. package/dist/components/segmented.js +2 -0
  280. package/dist/components/select.js +2 -0
  281. package/dist/components/separator.js +2 -0
  282. package/dist/components/settings.js +99 -0
  283. package/dist/components/settings.js.map +1 -0
  284. package/dist/components/sheet.js +94 -0
  285. package/dist/components/sheet.js.map +1 -0
  286. package/dist/components/sidebar.js +2 -0
  287. package/dist/components/skeleton.js +2 -0
  288. package/dist/components/slider.js +2 -0
  289. package/dist/components/stat.js +195 -0
  290. package/dist/components/stat.js.map +1 -0
  291. package/dist/components/status-dot.js +2 -0
  292. package/dist/components/surface.js +26 -0
  293. package/dist/components/surface.js.map +1 -0
  294. package/dist/components/swap-text.js +2 -0
  295. package/dist/components/switch.js +2 -0
  296. package/dist/components/table.js +2 -0
  297. package/dist/components/tabs.js +96 -0
  298. package/dist/components/tabs.js.map +1 -0
  299. package/dist/components/tag.js +2 -0
  300. package/dist/components/text.js +2 -0
  301. package/dist/components/theme-menu.js +50 -0
  302. package/dist/components/theme-menu.js.map +1 -0
  303. package/dist/components/timeline.js +58 -0
  304. package/dist/components/timeline.js.map +1 -0
  305. package/dist/components/toast.js +214 -0
  306. package/dist/components/toast.js.map +1 -0
  307. package/dist/components/toc.js +48 -0
  308. package/dist/components/toc.js.map +1 -0
  309. package/dist/components/toolbar.js +2 -0
  310. package/dist/components/tooltip.js +18 -0
  311. package/dist/components/tooltip.js.map +1 -0
  312. package/dist/components/visually-hidden.js +17 -0
  313. package/dist/components/visually-hidden.js.map +1 -0
  314. package/dist/fonts/LICENSE-GeistMono.txt +93 -0
  315. package/dist/fonts/LICENSE-Inter.txt +93 -0
  316. package/dist/fonts/geist-mono-latin-ext-wght-normal.woff2 +0 -0
  317. package/dist/fonts/geist-mono-latin-wght-normal.woff2 +0 -0
  318. package/dist/fonts/inter-latin-ext-opsz-normal.woff2 +0 -0
  319. package/dist/fonts/inter-latin-opsz-normal.woff2 +0 -0
  320. package/dist/fonts.css +40 -0
  321. package/dist/format.js +178 -0
  322. package/dist/format.js.map +1 -0
  323. package/dist/i18n-locales.js +23 -0
  324. package/dist/i18n-locales.js.map +1 -0
  325. package/dist/i18n-provider.js +30 -0
  326. package/dist/i18n-provider.js.map +1 -0
  327. package/dist/i18n.js +7 -0
  328. package/dist/index.js +153 -0
  329. package/dist/kaeso.css +1099 -0
  330. package/dist/styles.css +2 -0
  331. package/dist/tailwind.css +10 -0
  332. package/dist/theme.js +2 -0
  333. package/dist/types/blocks/delete-resource/delete-resource.d.ts +32 -0
  334. package/dist/types/blocks/delete-resource/docs.d.ts +17 -0
  335. package/dist/types/blocks/delete-resource/index.d.ts +1 -0
  336. package/dist/types/blocks/pages/docs.d.ts +42 -0
  337. package/dist/types/blocks/pages/index.d.ts +1 -0
  338. package/dist/types/blocks/pages/pages.d.ts +65 -0
  339. package/dist/types/blocks/resource-list/docs.d.ts +15 -0
  340. package/dist/types/blocks/resource-list/index.d.ts +1 -0
  341. package/dist/types/blocks/resource-list/resource-list.d.ts +36 -0
  342. package/dist/types/component-docs.d.ts +7 -0
  343. package/dist/types/components/app-frame/app-frame.d.ts +79 -0
  344. package/dist/types/components/app-frame/docs.d.ts +23 -0
  345. package/dist/types/components/app-frame/index.d.ts +1 -0
  346. package/dist/types/components/avatar/avatar.d.ts +60 -0
  347. package/dist/types/components/avatar/docs.d.ts +21 -0
  348. package/dist/types/components/avatar/index.d.ts +1 -0
  349. package/dist/types/components/badge/badge.d.ts +41 -0
  350. package/dist/types/components/badge/docs.d.ts +9 -0
  351. package/dist/types/components/badge/index.d.ts +1 -0
  352. package/dist/types/components/banner/banner.d.ts +51 -0
  353. package/dist/types/components/banner/docs.d.ts +14 -0
  354. package/dist/types/components/banner/index.d.ts +1 -0
  355. package/dist/types/components/button/button.d.ts +116 -0
  356. package/dist/types/components/button/docs.d.ts +23 -0
  357. package/dist/types/components/button/index.d.ts +1 -0
  358. package/dist/types/components/button-group/button-group.d.ts +55 -0
  359. package/dist/types/components/button-group/docs.d.ts +11 -0
  360. package/dist/types/components/button-group/index.d.ts +1 -0
  361. package/dist/types/components/calendar/calendar.d.ts +32 -0
  362. package/dist/types/components/calendar/docs.d.ts +16 -0
  363. package/dist/types/components/calendar/index.d.ts +1 -0
  364. package/dist/types/components/charts/bar.d.ts +39 -0
  365. package/dist/types/components/charts/core.d.ts +167 -0
  366. package/dist/types/components/charts/docs.d.ts +174 -0
  367. package/dist/types/components/charts/donut.d.ts +67 -0
  368. package/dist/types/components/charts/gauge.d.ts +20 -0
  369. package/dist/types/components/charts/heatmap.d.ts +24 -0
  370. package/dist/types/components/charts/index.d.ts +8 -0
  371. package/dist/types/components/charts/sankey.d.ts +29 -0
  372. package/dist/types/components/charts/sparkline.d.ts +18 -0
  373. package/dist/types/components/charts/timeseries.d.ts +47 -0
  374. package/dist/types/components/checkbox/checkbox.d.ts +53 -0
  375. package/dist/types/components/checkbox/docs.d.ts +58 -0
  376. package/dist/types/components/checkbox/index.d.ts +1 -0
  377. package/dist/types/components/code/code.d.ts +24 -0
  378. package/dist/types/components/code/docs.d.ts +8 -0
  379. package/dist/types/components/code/index.d.ts +1 -0
  380. package/dist/types/components/code-block/code-block.d.ts +37 -0
  381. package/dist/types/components/code-block/docs.d.ts +41 -0
  382. package/dist/types/components/code-block/highlighter.d.ts +23 -0
  383. package/dist/types/components/code-block/index.d.ts +4 -0
  384. package/dist/types/components/code-block/json-view.d.ts +23 -0
  385. package/dist/types/components/code-block/kaeso-theme.d.ts +66 -0
  386. package/dist/types/components/collapsible/collapsible.d.ts +39 -0
  387. package/dist/types/components/collapsible/docs.d.ts +21 -0
  388. package/dist/types/components/collapsible/index.d.ts +1 -0
  389. package/dist/types/components/combobox/combobox.d.ts +86 -0
  390. package/dist/types/components/combobox/docs.d.ts +62 -0
  391. package/dist/types/components/combobox/index.d.ts +1 -0
  392. package/dist/types/components/command-palette/command-palette.d.ts +44 -0
  393. package/dist/types/components/command-palette/docs.d.ts +17 -0
  394. package/dist/types/components/command-palette/index.d.ts +1 -0
  395. package/dist/types/components/copy-button/copy-button.d.ts +85 -0
  396. package/dist/types/components/copy-button/docs.d.ts +30 -0
  397. package/dist/types/components/copy-button/index.d.ts +1 -0
  398. package/dist/types/components/data-table/columns.d.ts +47 -0
  399. package/dist/types/components/data-table/data-table.d.ts +80 -0
  400. package/dist/types/components/data-table/docs.d.ts +32 -0
  401. package/dist/types/components/data-table/index.d.ts +3 -0
  402. package/dist/types/components/date-picker/date-picker.d.ts +55 -0
  403. package/dist/types/components/date-picker/date-popup.d.ts +9 -0
  404. package/dist/types/components/date-picker/docs.d.ts +73 -0
  405. package/dist/types/components/date-picker/index.d.ts +2 -0
  406. package/dist/types/components/date-picker/iso-field.d.ts +5 -0
  407. package/dist/types/components/date-picker/range-popup.d.ts +13 -0
  408. package/dist/types/components/date-picker/time-input.d.ts +39 -0
  409. package/dist/types/components/dialog/dialog.d.ts +118 -0
  410. package/dist/types/components/dialog/docs.d.ts +70 -0
  411. package/dist/types/components/dialog/focus.d.ts +2 -0
  412. package/dist/types/components/dialog/index.d.ts +2 -0
  413. package/dist/types/components/dialog/step-dialog.d.ts +26 -0
  414. package/dist/types/components/display-options/display-options.d.ts +55 -0
  415. package/dist/types/components/display-options/docs.d.ts +14 -0
  416. package/dist/types/components/display-options/index.d.ts +1 -0
  417. package/dist/types/components/empty/docs.d.ts +12 -0
  418. package/dist/types/components/empty/empty.d.ts +29 -0
  419. package/dist/types/components/empty/index.d.ts +1 -0
  420. package/dist/types/components/field/docs.d.ts +20 -0
  421. package/dist/types/components/field/field.d.ts +78 -0
  422. package/dist/types/components/field/index.d.ts +1 -0
  423. package/dist/types/components/filter-chip/docs.d.ts +19 -0
  424. package/dist/types/components/filter-chip/filter-chip.d.ts +37 -0
  425. package/dist/types/components/filter-chip/index.d.ts +1 -0
  426. package/dist/types/components/filter-row/docs.d.ts +11 -0
  427. package/dist/types/components/filter-row/filter-row.d.ts +23 -0
  428. package/dist/types/components/filter-row/index.d.ts +1 -0
  429. package/dist/types/components/flow/docs.d.ts +12 -0
  430. package/dist/types/components/flow/flow.d.ts +34 -0
  431. package/dist/types/components/flow/index.d.ts +1 -0
  432. package/dist/types/components/heading/docs.d.ts +10 -0
  433. package/dist/types/components/heading/heading.d.ts +30 -0
  434. package/dist/types/components/heading/index.d.ts +1 -0
  435. package/dist/types/components/icon/docs.d.ts +10 -0
  436. package/dist/types/components/icon/icon.d.ts +37 -0
  437. package/dist/types/components/icon/index.d.ts +2 -0
  438. package/dist/types/components/icon-button/docs.d.ts +17 -0
  439. package/dist/types/components/icon-button/icon-button.d.ts +75 -0
  440. package/dist/types/components/icon-button/index.d.ts +1 -0
  441. package/dist/types/components/input/decimal.d.ts +11 -0
  442. package/dist/types/components/input/docs.d.ts +149 -0
  443. package/dist/types/components/input/index.d.ts +8 -0
  444. package/dist/types/components/input/input-group.d.ts +55 -0
  445. package/dist/types/components/input/input.d.ts +45 -0
  446. package/dist/types/components/input/number-input.d.ts +28 -0
  447. package/dist/types/components/input/otp-input.d.ts +27 -0
  448. package/dist/types/components/input/sensitive-input.d.ts +21 -0
  449. package/dist/types/components/input/tag-input.d.ts +27 -0
  450. package/dist/types/components/input/textarea.d.ts +25 -0
  451. package/dist/types/components/kaeso/conversation.d.ts +58 -0
  452. package/dist/types/components/kaeso/docs.d.ts +139 -0
  453. package/dist/types/components/kaeso/effective-access.d.ts +21 -0
  454. package/dist/types/components/kaeso/identity.d.ts +70 -0
  455. package/dist/types/components/kaeso/index.d.ts +8 -0
  456. package/dist/types/components/kaeso/moments.d.ts +30 -0
  457. package/dist/types/components/kaeso/provider-marks.d.ts +1 -0
  458. package/dist/types/components/kaeso/rights-logic.d.ts +13 -0
  459. package/dist/types/components/kaeso/rights.d.ts +69 -0
  460. package/dist/types/components/kaeso/role-picker.d.ts +38 -0
  461. package/dist/types/components/kaeso/usage.d.ts +35 -0
  462. package/dist/types/components/kbd/docs.d.ts +16 -0
  463. package/dist/types/components/kbd/index.d.ts +1 -0
  464. package/dist/types/components/kbd/kbd.d.ts +31 -0
  465. package/dist/types/components/key-value/docs.d.ts +10 -0
  466. package/dist/types/components/key-value/index.d.ts +1 -0
  467. package/dist/types/components/key-value/key-value.d.ts +50 -0
  468. package/dist/types/components/label/docs.d.ts +10 -0
  469. package/dist/types/components/label/index.d.ts +1 -0
  470. package/dist/types/components/label/label.d.ts +22 -0
  471. package/dist/types/components/layout/docs.d.ts +30 -0
  472. package/dist/types/components/layout/index.d.ts +1 -0
  473. package/dist/types/components/layout/layout.d.ts +100 -0
  474. package/dist/types/components/link/docs.d.ts +11 -0
  475. package/dist/types/components/link/index.d.ts +1 -0
  476. package/dist/types/components/link/link.d.ts +27 -0
  477. package/dist/types/components/list/docs.d.ts +75 -0
  478. package/dist/types/components/list/index.d.ts +1 -0
  479. package/dist/types/components/list/list.d.ts +119 -0
  480. package/dist/types/components/list/row-checkbox.d.ts +5 -0
  481. package/dist/types/components/list/virtual-rows.d.ts +10 -0
  482. package/dist/types/components/loader/docs.d.ts +21 -0
  483. package/dist/types/components/loader/index.d.ts +2 -0
  484. package/dist/types/components/loader/loader.d.ts +35 -0
  485. package/dist/types/components/loader/status.d.ts +55 -0
  486. package/dist/types/components/logo/docs.d.ts +10 -0
  487. package/dist/types/components/logo/index.d.ts +1 -0
  488. package/dist/types/components/logo/logo.d.ts +59 -0
  489. package/dist/types/components/logo/paths.d.ts +16 -0
  490. package/dist/types/components/markdown/docs.d.ts +15 -0
  491. package/dist/types/components/markdown/index.d.ts +1 -0
  492. package/dist/types/components/markdown/markdown.d.ts +54 -0
  493. package/dist/types/components/menu/docs.d.ts +19 -0
  494. package/dist/types/components/menu/index.d.ts +2 -0
  495. package/dist/types/components/menu/menu.d.ts +213 -0
  496. package/dist/types/components/menu/styles.d.ts +7 -0
  497. package/dist/types/components/meter/docs.d.ts +23 -0
  498. package/dist/types/components/meter/index.d.ts +1 -0
  499. package/dist/types/components/meter/meter.d.ts +51 -0
  500. package/dist/types/components/page-header/docs.d.ts +12 -0
  501. package/dist/types/components/page-header/index.d.ts +1 -0
  502. package/dist/types/components/page-header/page-header.d.ts +22 -0
  503. package/dist/types/components/pagination/docs.d.ts +17 -0
  504. package/dist/types/components/pagination/index.d.ts +1 -0
  505. package/dist/types/components/pagination/pagination.d.ts +33 -0
  506. package/dist/types/components/playground/compare-grid.d.ts +41 -0
  507. package/dist/types/components/playground/composer.d.ts +40 -0
  508. package/dist/types/components/playground/docs.d.ts +141 -0
  509. package/dist/types/components/playground/index.d.ts +7 -0
  510. package/dist/types/components/playground/model-picker.d.ts +37 -0
  511. package/dist/types/components/playground/parameter-panel.d.ts +52 -0
  512. package/dist/types/components/playground/prompt-suggestions.d.ts +23 -0
  513. package/dist/types/components/playground/response-metrics.d.ts +30 -0
  514. package/dist/types/components/playground/response-view.d.ts +55 -0
  515. package/dist/types/components/popover/docs.d.ts +21 -0
  516. package/dist/types/components/popover/hover-card.d.ts +24 -0
  517. package/dist/types/components/popover/index.d.ts +2 -0
  518. package/dist/types/components/popover/popover-root.d.ts +24 -0
  519. package/dist/types/components/popover/popover.d.ts +40 -0
  520. package/dist/types/components/portal/docs.d.ts +9 -0
  521. package/dist/types/components/portal/index.d.ts +1 -0
  522. package/dist/types/components/portal/portal.d.ts +11 -0
  523. package/dist/types/components/qr-code/docs.d.ts +10 -0
  524. package/dist/types/components/qr-code/index.d.ts +1 -0
  525. package/dist/types/components/qr-code/qr-code.d.ts +22 -0
  526. package/dist/types/components/radio/docs.d.ts +42 -0
  527. package/dist/types/components/radio/index.d.ts +1 -0
  528. package/dist/types/components/radio/labelled.d.ts +13 -0
  529. package/dist/types/components/radio/radio.d.ts +51 -0
  530. package/dist/types/components/scroll-area/docs.d.ts +11 -0
  531. package/dist/types/components/scroll-area/index.d.ts +1 -0
  532. package/dist/types/components/scroll-area/scroll-area.d.ts +28 -0
  533. package/dist/types/components/section/docs.d.ts +28 -0
  534. package/dist/types/components/section/index.d.ts +1 -0
  535. package/dist/types/components/section/section.d.ts +37 -0
  536. package/dist/types/components/segmented/docs.d.ts +15 -0
  537. package/dist/types/components/segmented/index.d.ts +1 -0
  538. package/dist/types/components/segmented/segmented.d.ts +37 -0
  539. package/dist/types/components/select/docs.d.ts +32 -0
  540. package/dist/types/components/select/index.d.ts +1 -0
  541. package/dist/types/components/select/select.d.ts +82 -0
  542. package/dist/types/components/separator/docs.d.ts +10 -0
  543. package/dist/types/components/separator/index.d.ts +1 -0
  544. package/dist/types/components/separator/separator.d.ts +33 -0
  545. package/dist/types/components/settings/docs.d.ts +32 -0
  546. package/dist/types/components/settings/index.d.ts +1 -0
  547. package/dist/types/components/settings/settings.d.ts +45 -0
  548. package/dist/types/components/sheet/docs.d.ts +20 -0
  549. package/dist/types/components/sheet/index.d.ts +1 -0
  550. package/dist/types/components/sheet/sheet.d.ts +51 -0
  551. package/dist/types/components/sidebar/docs.d.ts +20 -0
  552. package/dist/types/components/sidebar/index.d.ts +1 -0
  553. package/dist/types/components/sidebar/sidebar-menu.d.ts +16 -0
  554. package/dist/types/components/sidebar/sidebar.d.ts +178 -0
  555. package/dist/types/components/skeleton/docs.d.ts +14 -0
  556. package/dist/types/components/skeleton/index.d.ts +1 -0
  557. package/dist/types/components/skeleton/skeleton.d.ts +31 -0
  558. package/dist/types/components/slider/docs.d.ts +20 -0
  559. package/dist/types/components/slider/index.d.ts +1 -0
  560. package/dist/types/components/slider/slider.d.ts +29 -0
  561. package/dist/types/components/stat/docs.d.ts +35 -0
  562. package/dist/types/components/stat/index.d.ts +1 -0
  563. package/dist/types/components/stat/stat.d.ts +68 -0
  564. package/dist/types/components/status-dot/docs.d.ts +18 -0
  565. package/dist/types/components/status-dot/index.d.ts +1 -0
  566. package/dist/types/components/status-dot/status-dot.d.ts +36 -0
  567. package/dist/types/components/surface/docs.d.ts +8 -0
  568. package/dist/types/components/surface/index.d.ts +1 -0
  569. package/dist/types/components/surface/surface.d.ts +44 -0
  570. package/dist/types/components/swap-text/docs.d.ts +10 -0
  571. package/dist/types/components/swap-text/index.d.ts +1 -0
  572. package/dist/types/components/swap-text/swap-text.d.ts +22 -0
  573. package/dist/types/components/switch/docs.d.ts +21 -0
  574. package/dist/types/components/switch/index.d.ts +1 -0
  575. package/dist/types/components/switch/switch.d.ts +39 -0
  576. package/dist/types/components/table/docs.d.ts +6 -0
  577. package/dist/types/components/table/index.d.ts +1 -0
  578. package/dist/types/components/table/table.d.ts +30 -0
  579. package/dist/types/components/tabs/docs.d.ts +22 -0
  580. package/dist/types/components/tabs/index.d.ts +1 -0
  581. package/dist/types/components/tabs/tabs.d.ts +61 -0
  582. package/dist/types/components/tag/docs.d.ts +30 -0
  583. package/dist/types/components/tag/index.d.ts +1 -0
  584. package/dist/types/components/tag/tag.d.ts +63 -0
  585. package/dist/types/components/text/docs.d.ts +15 -0
  586. package/dist/types/components/text/index.d.ts +1 -0
  587. package/dist/types/components/text/text.d.ts +111 -0
  588. package/dist/types/components/theme-menu/docs.d.ts +9 -0
  589. package/dist/types/components/theme-menu/index.d.ts +1 -0
  590. package/dist/types/components/theme-menu/theme-menu.d.ts +22 -0
  591. package/dist/types/components/timeline/docs.d.ts +8 -0
  592. package/dist/types/components/timeline/index.d.ts +1 -0
  593. package/dist/types/components/timeline/timeline.d.ts +41 -0
  594. package/dist/types/components/toast/docs.d.ts +26 -0
  595. package/dist/types/components/toast/index.d.ts +1 -0
  596. package/dist/types/components/toast/toast.d.ts +91 -0
  597. package/dist/types/components/toc/docs.d.ts +10 -0
  598. package/dist/types/components/toc/index.d.ts +1 -0
  599. package/dist/types/components/toc/toc.d.ts +27 -0
  600. package/dist/types/components/toolbar/docs.d.ts +9 -0
  601. package/dist/types/components/toolbar/index.d.ts +1 -0
  602. package/dist/types/components/toolbar/toolbar.d.ts +28 -0
  603. package/dist/types/components/tooltip/docs.d.ts +33 -0
  604. package/dist/types/components/tooltip/index.d.ts +2 -0
  605. package/dist/types/components/tooltip/tooltip-provider.d.ts +8 -0
  606. package/dist/types/components/tooltip/tooltip-root.d.ts +9 -0
  607. package/dist/types/components/tooltip/tooltip.d.ts +60 -0
  608. package/dist/types/components/tooltip/trigger-props.d.ts +23 -0
  609. package/dist/types/components/visually-hidden/docs.d.ts +6 -0
  610. package/dist/types/components/visually-hidden/index.d.ts +1 -0
  611. package/dist/types/components/visually-hidden/visually-hidden.d.ts +4 -0
  612. package/dist/types/format/index.d.ts +26 -0
  613. package/dist/types/hooks/scroll-parent.d.ts +1 -0
  614. package/dist/types/hooks/use-copy.d.ts +4 -0
  615. package/dist/types/hooks/use-indicator.d.ts +9 -0
  616. package/dist/types/hooks/use-layout-width.d.ts +3 -0
  617. package/dist/types/hooks/use-scroll-margin.d.ts +2 -0
  618. package/dist/types/hooks/use-stored-state.d.ts +1 -0
  619. package/dist/types/hooks/use-truncated.d.ts +2 -0
  620. package/dist/types/i18n/context.d.ts +11 -0
  621. package/dist/types/i18n/index.d.ts +5 -0
  622. package/dist/types/i18n/locale-names.d.ts +2 -0
  623. package/dist/types/i18n/locale-tags.d.ts +4 -0
  624. package/dist/types/i18n/locales/de.d.ts +2 -0
  625. package/dist/types/i18n/locales/en.d.ts +2 -0
  626. package/dist/types/i18n/locales/zh-CN.d.ts +2 -0
  627. package/dist/types/i18n/message.d.ts +2 -0
  628. package/dist/types/i18n/provider.d.ts +10 -0
  629. package/dist/types/icons/groups.d.ts +8 -0
  630. package/dist/types/icons/vocabulary.d.ts +413 -0
  631. package/dist/types/index.d.ts +79 -0
  632. package/dist/types/theme/index.d.ts +1 -0
  633. package/dist/types/theme/theme-provider.d.ts +22 -0
  634. package/dist/types/utils/arrow-focus.d.ts +2 -0
  635. package/dist/types/utils/cn.d.ts +370 -0
  636. package/dist/types/utils/dev.d.ts +4 -0
  637. package/dist/types/utils/field-press.d.ts +2 -0
  638. package/dist/types/utils/index.d.ts +8 -0
  639. package/dist/types/utils/inset.d.ts +6 -0
  640. package/dist/types/utils/lazy-module.d.ts +8 -0
  641. package/dist/types/utils/link-provider.d.ts +15 -0
  642. package/dist/types/utils/portal-context.d.ts +10 -0
  643. package/dist/types/utils/restore-focus.d.ts +4 -0
  644. package/dist/types/utils/safe-href.d.ts +1 -0
  645. package/dist/types/utils/tone.d.ts +2 -0
  646. package/dist/types/utils/use-controllable.d.ts +1 -0
  647. package/dist/types/utils/variants.d.ts +5 -0
  648. package/dist/types/utils/when-idle.d.ts +1 -0
  649. package/dist/utils.js +9 -0
  650. package/foundations/accessibility.md +41 -0
  651. package/foundations/color.md +40 -0
  652. package/foundations/elevation.md +23 -0
  653. package/foundations/icons.md +14 -0
  654. package/foundations/motion.md +39 -0
  655. package/foundations/psychology.md +36 -0
  656. package/foundations/shape.md +41 -0
  657. package/foundations/tokens.md +24 -0
  658. package/foundations/typography.md +35 -0
  659. package/foundations/writing.md +39 -0
  660. package/package.json +144 -0
@@ -0,0 +1,4760 @@
1
+ # @logicplanes/kaeso-ui component guides
2
+
3
+ Version 0.1.0. Generated from the showcase's demo modules (apps/showcase/src/demos); do not edit. For each component: what it is for, its import, keyboard, accessibility, do and don't, and every example's code. Props and variants: `component-registry.md`.
4
+
5
+ ## AlertDialog
6
+
7
+ Confirms an irreversible action: names it, says the consequence, can ask for typed confirmation.
8
+
9
+ `import { AlertDialog } from "@logicplanes/kaeso-ui";`
10
+
11
+ ### Keyboard
12
+
13
+ | Key | Action |
14
+ |---|---|
15
+ | Escape | Cancels |
16
+ | Enter | Confirms once the typed text matches |
17
+
18
+ ### Accessibility
19
+
20
+ - role=alertdialog; focus starts on Cancel.
21
+ - The confirm names the verb and object (“Delete key”).
22
+
23
+ ### Examples
24
+
25
+ #### Delete a key
26
+
27
+ ```tsx
28
+ <AlertDialog
29
+ trigger={<Button variant="danger-secondary">Delete key</Button>}
30
+ title="Delete this key?"
31
+ description="Applications using checkout-web will stop authenticating with the gateway."
32
+ confirmLabel="Delete key"
33
+ onConfirm={() => new Promise((r) => setTimeout(r, 900))}
34
+ />
35
+ ```
36
+
37
+ #### Typed confirmation
38
+
39
+ ```tsx
40
+ <AlertDialog
41
+ trigger={<Button variant="danger-secondary">Delete organization</Button>}
42
+ title="Delete Northwind Labs?"
43
+ description="Its projects, keys and logs are deleted after 30 days. Until then an instance admin can restore it."
44
+ confirmLabel="Delete organization"
45
+ confirmText="Northwind Labs"
46
+ onConfirm={() => undefined}
47
+ />
48
+ ```
49
+
50
+ ## AppFrame
51
+
52
+ The console frame: the sidebar on the grey chrome, content in an inset rounded panel that scrolls by itself.
53
+
54
+ `import { AppFrame, NavSheet } from "@logicplanes/kaeso-ui";`
55
+
56
+ ### Accessibility
57
+
58
+ - The sidebar is a nav landmark and the panel is main.
59
+ - The first Tab stop is Skip to content, shown only while it has focus; it moves the focus to the panel, so the next Tab reaches the page's first control without passing the navigation.
60
+ - Below 1024px a bar above the panel shows the wordmark alone at the top left, 28px in and 28px down, and the navigation toggle at the right; the toggle is named Open navigation, says whether the sheet is open (aria-expanded), and takes the focus back when the sheet closes.
61
+ - A collapsed sidebar peeks when its expand button is held under the pointer for 750ms.
62
+
63
+ ### Examples
64
+
65
+ #### App
66
+
67
+ ```tsx
68
+ <Frame />
69
+ ```
70
+
71
+ #### Settings mode: a settings list
72
+
73
+ ```tsx
74
+ <Frame settings page={<MembersPage />} />
75
+ ```
76
+
77
+ ## Autocomplete
78
+
79
+ Free text with suggestions.
80
+
81
+ `import { Autocomplete } from "@logicplanes/kaeso-ui";`
82
+
83
+ ### Keyboard
84
+
85
+ | Key | Action |
86
+ |---|---|
87
+ | Up Down | Moves through suggestions |
88
+ | Enter | Takes a suggestion |
89
+
90
+ ### Examples
91
+
92
+ #### Basic
93
+
94
+ ```tsx
95
+ <div className="w-320">
96
+ <Autocomplete
97
+ label="Endpoint"
98
+ leadingIcon={icons.route}
99
+ suggestions={["/v1/responses", "/v1/chat/completions", "/v1/messages", "/v1/embeddings"]}
100
+ placeholder="/v1/…"
101
+ />
102
+ </div>
103
+ ```
104
+
105
+ ## Avatar
106
+
107
+ People round with initials or a picture; organizations and projects at radius 8.
108
+
109
+ `import { Avatar, AvatarGroup } from "@logicplanes/kaeso-ui";`
110
+
111
+ ### Examples
112
+
113
+ #### Sizes
114
+
115
+ ```tsx
116
+ <div className="flex flex-wrap items-end gap-12">
117
+ {([20, 24, 28, 32, 40] as const).map((s) => (
118
+ <Avatar key={s} name="Ada Okafor" size={s} />
119
+ ))}
120
+ {([20, 24, 32, 40] as const).map((s) => (
121
+ <Avatar key={`o${s}`} name="Northwind Labs" kind="org" size={s} />
122
+ ))}
123
+ </div>
124
+ ```
125
+
126
+ #### Group
127
+
128
+ ```tsx
129
+ <AvatarGroup people={members} max={3} size={28} />
130
+ ```
131
+
132
+ ## Badge
133
+
134
+ A small pill label: 22px, 12/500. Words carry the meaning.
135
+
136
+ `import { Badge } from "@logicplanes/kaeso-ui";`
137
+
138
+ ### Examples
139
+
140
+ #### Tones
141
+
142
+ ```tsx
143
+ <div className="flex flex-wrap items-center gap-8">
144
+ <Badge>Inference</Badge>
145
+ <Badge tone="success">Active</Badge>
146
+ <Badge tone="warning">Expiring</Badge>
147
+ <Badge tone="danger">Failed</Badge>
148
+ <Badge tone="info">Syncing</Badge>
149
+ <Badge tone="outline">Platform</Badge>
150
+ <Badge tone="ink">New</Badge>
151
+ </div>
152
+ ```
153
+
154
+ ## Banner
155
+
156
+ A standing notice about a state: a tint, an icon, the text and actions; errors are toasts.
157
+
158
+ `import { Banner } from "@logicplanes/kaeso-ui";`
159
+
160
+ ### Accessibility
161
+
162
+ - Danger banners are alerts; others are status regions.
163
+ - Only informational banners can be dismissed.
164
+
165
+ ### Examples
166
+
167
+ #### Tones
168
+
169
+ ```tsx
170
+ <div className="flex w-560 max-w-full flex-col gap-12">
171
+ <Banner tone="info" title="New models are available" actions={<Button size="sm">Review</Button>} onDismiss={() => undefined}>
172
+ models.dev added 4 models since the last sync.
173
+ </Banner>
174
+ <Banner tone="warning">This key expires in 5 days.</Banner>
175
+ <Banner tone="danger" title="This organization is scheduled for deletion" actions={<Button size="sm">Restore organization</Button>}>
176
+ Its keys stopped working; on 20 Oct 2026 it is deleted for good.
177
+ </Banner>
178
+ <Banner tone="success">Single sign-on is on for northwind.example.</Banner>
179
+ <Banner>Content logging is off for this project.</Banner>
180
+ </div>
181
+ ```
182
+
183
+ #### Full width
184
+
185
+ Across the top of the panel, its sides on the page's 24px inset: the action ends where the page header's actions end. On a phone it goes under the text.
186
+
187
+ ```tsx
188
+ <Panel />
189
+ ```
190
+
191
+ ## BarChart
192
+
193
+ Grouped and stacked bars, slender with a 4px top radius and 2px gaps between stacked segments; horizontal for rankings.
194
+
195
+ `import { BarChart } from "@logicplanes/kaeso-ui";`
196
+
197
+ ### Keyboard
198
+
199
+ | Key | Action |
200
+ |---|---|
201
+ | Tab | Moves to the plot and highlights the newest day (a ranking's first row) |
202
+ | ← → | Moves the highlight and tooltip a category at a time (↑ ↓ in a ranking) |
203
+ | Home End | Jumps to the first or last category |
204
+ | Escape | Hides the highlight |
205
+
206
+ ### Accessibility
207
+
208
+ - A hidden data table carries every value; “View as table” shows it.
209
+ - The category under the pointer or the arrow keys sits on a quiet fill with its tooltip; its values are announced politely. Nothing moves.
210
+
211
+ ### Examples
212
+
213
+ #### Tokens by kind, stacked
214
+
215
+ ```tsx
216
+ function Stacked() {
217
+ const series = useSeries();
218
+ return (
219
+ <div className="w-full">
220
+ <BarChart aria-label="Tokens by kind per day" categories={days} series={series} layout="stacked" formatValue={formatTokens} />
221
+ </div>
222
+ );
223
+ }
224
+ ```
225
+
226
+ #### Grouped
227
+
228
+ ```tsx
229
+ function Grouped() {
230
+ const series = useSeries();
231
+ return (
232
+ <div className="w-full">
233
+ <BarChart aria-label="Input and output tokens per day" categories={days} series={series.slice(0, 2)} formatValue={formatTokens} />
234
+ </div>
235
+ );
236
+ }
237
+ ```
238
+
239
+ #### Ranking
240
+
241
+ ```tsx
242
+ <div className="w-full">
243
+ <BarChart
244
+ aria-label="Spend by key"
245
+ layout="horizontal"
246
+ height={200}
247
+ categories={["checkout-web", "checkout-worker", "ci-runner", "staging"]}
248
+ series={[{ id: "spend", label: "Spend", color: "var(--k-viz-1)", values: [84.2, 51.9, 12.4, 3.1] }]}
249
+ formatValue={(v) => formatUSD(v.toFixed(2))}
250
+ />
251
+ </div>
252
+ ```
253
+
254
+ #### Months across a year
255
+
256
+ ```tsx
257
+ function Months() {
258
+ const { months, monthAxis } = useAxes();
259
+ const monthTokens = useInputTokens(monthValues);
260
+ return (
261
+ <div className="w-full">
262
+ <BarChart
263
+ aria-label="Input tokens per month"
264
+ categories={months}
265
+ axisLabels={monthAxis}
266
+ dates={monthDays}
267
+ series={monthTokens}
268
+ formatValue={formatTokens}
269
+ />
270
+ </div>
271
+ );
272
+ }
273
+ ```
274
+
275
+ ## Button
276
+
277
+ Starts an action. Every button is a pill; exactly one per view is ink.
278
+
279
+ `import { Button } from "@logicplanes/kaeso-ui";`
280
+
281
+ ### Keyboard
282
+
283
+ | Key | Action |
284
+ |---|---|
285
+ | Enter | Activates the button |
286
+ | Space | Activates the button |
287
+ | Tab | Moves focus to the next control; a disabled button with a reason stays focusable |
288
+
289
+ ### Accessibility
290
+
291
+ - Renders a native <button type="button">, or an <a> when href is set.
292
+ - Icon-only buttons are circles and require aria-label at the type level; use IconButton, which also shows the name as a tooltip.
293
+ - Loading sets aria-busy at once and keeps the button focusable; clicks are ignored until it finishes. The spinner shows once the work has taken 300ms, for at least 400ms. The width never changes.
294
+ - With disabledReason, the button uses aria-disabled instead of disabled so keyboard users can reach the tooltip that explains why.
295
+ - Primary and danger labels measure at least 4.5:1 on their fills in both themes.
296
+
297
+ ### Do and don't
298
+
299
+ Do (one primary):
300
+
301
+ ```tsx
302
+ <Inline gap={8}>
303
+ <Button variant="ghost">Cancel</Button>
304
+ <Button variant="primary">Create key</Button>
305
+ </Inline>
306
+ ```
307
+
308
+ Don't (two primaries):
309
+
310
+ ```tsx
311
+ <Inline gap={8}>
312
+ <Button variant="primary">Cancel</Button>
313
+ <Button variant="primary">Create key</Button>
314
+ </Inline>
315
+ ```
316
+
317
+ ### Examples
318
+
319
+ #### Basic
320
+
321
+ ```tsx
322
+ <Button variant="primary">Create key</Button>
323
+ ```
324
+
325
+ #### Primary
326
+
327
+ Ink with a faint top light. Use it for the one action a view exists for.
328
+
329
+ ```tsx
330
+ <Inline gap={12}>
331
+ <Button variant="primary">Create key</Button>
332
+ <Button variant="primary" leadingIcon={icons.add}>
333
+ Add model
334
+ </Button>
335
+ </Inline>
336
+ ```
337
+
338
+ #### Secondary
339
+
340
+ The default: raised, with a ring and a shadow you feel more than see.
341
+
342
+ ```tsx
343
+ <Inline gap={12}>
344
+ <Button>Edit limits</Button>
345
+ <Button leadingIcon={icons.download}>Export CSV</Button>
346
+ <Button trailingIcon={icons.chevronDown}>Last 30 days</Button>
347
+ </Inline>
348
+ ```
349
+
350
+ #### Ghost
351
+
352
+ Transparent until touched; for toolbars, rows and quiet actions.
353
+
354
+ ```tsx
355
+ <Inline gap={12}>
356
+ <Button variant="ghost">Cancel</Button>
357
+ <Button variant="ghost" leadingIcon={icons.add}>
358
+ Add filter
359
+ </Button>
360
+ </Inline>
361
+ ```
362
+
363
+ #### Soft
364
+
365
+ A fill without a ring, for the active pill in a header or toolbar.
366
+
367
+ ```tsx
368
+ <Inline gap={12}>
369
+ <Button variant="soft" size="sm">
370
+ Errors only
371
+ </Button>
372
+ <Button variant="soft" size="sm" leadingIcon={icons.filter}>
373
+ Filter
374
+ </Button>
375
+ </Inline>
376
+ ```
377
+
378
+ #### Danger
379
+
380
+ The confirm of a destructive flow. Its trigger is usually danger-secondary.
381
+
382
+ ```tsx
383
+ <Inline gap={12}>
384
+ <Button variant="danger-secondary">Archive project</Button>
385
+ <Button variant="danger">Delete key</Button>
386
+ </Inline>
387
+ ```
388
+
389
+ #### Link
390
+
391
+ An action inside a sentence.
392
+
393
+ ```tsx
394
+ <p className="text-body text-fg-secondary">
395
+ No keys yet.{" "}
396
+ <Button variant="link" size="sm">
397
+ Create the first key
398
+ </Button>
399
+ </p>
400
+ ```
401
+
402
+ #### Sizes
403
+
404
+ 24 for chips inside rows, 32 in toolbars, 36 by default, 40 for prominent actions, 48 in sign-in and setup forms.
405
+
406
+ ```tsx
407
+ <Inline gap={12} align="center">
408
+ <Button size="xs">Extra small</Button>
409
+ <Button size="sm">Small</Button>
410
+ <Button size="md">Medium</Button>
411
+ <Button size="lg">Large</Button>
412
+ <Button size="xl">Extra large</Button>
413
+ </Inline>
414
+ ```
415
+
416
+ #### With icons
417
+
418
+ A leading icon takes 2px off its side's padding so the label looks centered.
419
+
420
+ ```tsx
421
+ <Inline gap={12}>
422
+ <Button variant="primary" leadingIcon={icons.key}>
423
+ Create key
424
+ </Button>
425
+ <Button leadingIcon={icons.copy}>Copy key</Button>
426
+ <Button trailingIcon={icons.externalLink}>Open docs</Button>
427
+ </Inline>
428
+ ```
429
+
430
+ #### Icon only
431
+
432
+ A circle of the same height. Prefer IconButton, which adds the tooltip.
433
+
434
+ ```tsx
435
+ <Inline gap={12}>
436
+ <Button shape="circle" icon={icons.filter} aria-label="Filter" />
437
+ <Button shape="circle" variant="ghost" icon={icons.more} aria-label="More actions" />
438
+ <Button shape="circle" variant="primary" icon={icons.add} aria-label="Create key" />
439
+ </Inline>
440
+ ```
441
+
442
+ #### Loading
443
+
444
+ Busy at once; the spinner shows once the work has taken 300ms, then for at least 400ms, replacing the leading icon or covering the label. The width never changes.
445
+
446
+ ```tsx
447
+ function Loading() {
448
+ const [saving, setSaving] = useState(false);
449
+ return (
450
+ <Inline gap={12}>
451
+ <Button
452
+ variant="primary"
453
+ loading={saving}
454
+ onClick={() => {
455
+ setSaving(true);
456
+ setTimeout(() => setSaving(false), 1600);
457
+ }}
458
+ >
459
+ Save changes
460
+ </Button>
461
+ <Button loading leadingIcon={icons.refresh}>
462
+ Syncing models
463
+ </Button>
464
+ </Inline>
465
+ );
466
+ }
467
+ ```
468
+
469
+ #### Disabled
470
+
471
+ Give a reason when you can: it shows as a tooltip and keeps the button focusable.
472
+
473
+ ```tsx
474
+ <Inline gap={12}>
475
+ <Button variant="primary" disabled>
476
+ Create key
477
+ </Button>
478
+ <Button disabled disabledReason="Add a provider key first">
479
+ Add model
480
+ </Button>
481
+ </Inline>
482
+ ```
483
+
484
+ #### Count and shortcut
485
+
486
+ ```tsx
487
+ <Inline gap={12}>
488
+ <Button count={12}>Requests</Button>
489
+ <Button size="lg" variant="primary" shortcut={["mod", "enter"]}>
490
+ Create key
491
+ </Button>
492
+ </Inline>
493
+ ```
494
+
495
+ #### As a link
496
+
497
+ With href the button renders a link through LinkProvider.
498
+
499
+ ```tsx
500
+ <Button href="https://ui.kaeso.app" trailingIcon={icons.externalLink}>
501
+ Read the gateway setup guide
502
+ </Button>
503
+ ```
504
+
505
+ #### Full width
506
+
507
+ ```tsx
508
+ <div className="w-320">
509
+ <Button variant="primary" size="xl" fullWidth>
510
+ Sign in
511
+ </Button>
512
+ </div>
513
+ ```
514
+
515
+ ## ButtonGroup
516
+
517
+ Related actions side by side: connected pills, or spaced.
518
+
519
+ `import { ButtonGroup } from "@logicplanes/kaeso-ui";`
520
+
521
+ ### Keyboard
522
+
523
+ | Key | Action |
524
+ |---|---|
525
+ | Tab | Moves between the buttons |
526
+
527
+ ### Accessibility
528
+
529
+ - The group has role=group; give it an aria-label when the buttons alone do not explain it.
530
+ - Icon-only items need an aria-label and show it as a tooltip.
531
+
532
+ ### Examples
533
+
534
+ #### Connected
535
+
536
+ ```tsx
537
+ <ButtonGroup aria-label="Export">
538
+ <ButtonGroup.Item>CSV</ButtonGroup.Item>
539
+ <ButtonGroup.Item>JSON</ButtonGroup.Item>
540
+ <ButtonGroup.Item>NDJSON</ButtonGroup.Item>
541
+ </ButtonGroup>
542
+ ```
543
+
544
+ #### Spaced
545
+
546
+ ```tsx
547
+ <ButtonGroup attached={false} aria-label="Key actions">
548
+ <ButtonGroup.Item>Disable</ButtonGroup.Item>
549
+ <ButtonGroup.Item>Rotate</ButtonGroup.Item>
550
+ </ButtonGroup>
551
+ ```
552
+
553
+ #### Icons
554
+
555
+ ```tsx
556
+ <ButtonGroup size="sm" aria-label="Zoom">
557
+ <ButtonGroup.Item icon={icons.minus} aria-label="Zoom out" />
558
+ <ButtonGroup.Item>100%</ButtonGroup.Item>
559
+ <ButtonGroup.Item icon={icons.add} aria-label="Zoom in" />
560
+ </ButtonGroup>
561
+ ```
562
+
563
+ ## Checkbox
564
+
565
+ An 18px box: raised and ringed, ink when checked, with a check that draws itself.
566
+
567
+ `import { Checkbox, CheckboxGroup } from "@logicplanes/kaeso-ui";`
568
+
569
+ ### Keyboard
570
+
571
+ | Key | Action |
572
+ |---|---|
573
+ | Space | Toggles |
574
+
575
+ ### Accessibility
576
+
577
+ - The whole row is the target: clicking the label toggles it.
578
+ - Indeterminate is announced as mixed.
579
+
580
+ ### Examples
581
+
582
+ #### Basic
583
+
584
+ ```tsx
585
+ <Checkbox label="Log request content" description="Prompts and completions are stored for 30 days." defaultChecked />
586
+ ```
587
+
588
+ #### Group
589
+
590
+ ```tsx
591
+ <CheckboxGroup defaultValue={["text"]} aria-label="Input types">
592
+ <Checkbox value="text" label="Text" />
593
+ <Checkbox value="image" label="Images" />
594
+ <Checkbox value="pdf" label="PDF" />
595
+ </CheckboxGroup>
596
+ ```
597
+
598
+ ## ClipboardText
599
+
600
+ Copy a value: a read-only pill field, a copy circle, or inline text that copies itself.
601
+
602
+ `import { ClipboardText, CopyButton, InlineCopyText } from "@logicplanes/kaeso-ui";`
603
+
604
+ ### Keyboard
605
+
606
+ | Key | Action |
607
+ |---|---|
608
+ | Enter | Copies |
609
+ | Space | Copies |
610
+
611
+ ### Accessibility
612
+
613
+ - The copy circle's name changes to “Copied” for 1.2 seconds and the tooltip says it.
614
+ - InlineCopyText announces “Copied” through a polite live region.
615
+ - Nothing changes size when the state changes.
616
+
617
+ ### Examples
618
+
619
+ #### ClipboardText
620
+
621
+ A read-only pill with the value in mono and a copy circle inside.
622
+
623
+ ```tsx
624
+ <div className="w-400">
625
+ <ClipboardText value="https://gateway.northwind.example/v1" label="Copy base URL" />
626
+ </div>
627
+ ```
628
+
629
+ #### CopyButton
630
+
631
+ ```tsx
632
+ <Inline gap={12}>
633
+ <CopyButton value="sk-kso-AbC123…wxyz" label="Copy redacted key" />
634
+ <CopyButton value="req_01JAR4X2" label="Copy id" variant="secondary" />
635
+ </Inline>
636
+ ```
637
+
638
+ #### InlineCopyText
639
+
640
+ ```tsx
641
+ <p className="text-body text-fg-secondary">
642
+ Request <InlineCopyText value="req_01JAR4X2">req_01JAR4X2</InlineCopyText> took 1.2 s.
643
+ </p>
644
+ ```
645
+
646
+ ## Code
647
+
648
+ Inline code: Geist Mono 13 on a soft fill. A pill for one word.
649
+
650
+ `import { Code } from "@logicplanes/kaeso-ui";`
651
+
652
+ ### Accessibility
653
+
654
+ - Code is a <code> element; screen readers read it as text.
655
+
656
+ ### Examples
657
+
658
+ #### Basic
659
+
660
+ ```tsx
661
+ <p className="max-w-prose text-body text-fg-secondary">
662
+ Set <Code>KAESO_MASTER_KEY</Code>, then call <Code>POST /v1/responses</Code> with the model <Code>gpt-5-mini</Code>.
663
+ </p>
664
+ ```
665
+
666
+ ## CodeBlock
667
+
668
+ Highlighted code on a well in Kaeso's monochrome theme: copy on hover, tabs, line numbers, collapse.
669
+
670
+ `import { CodeBlock } from "@logicplanes/kaeso-ui";`
671
+
672
+ ### Accessibility
673
+
674
+ - The copy circle is named “Copy code” and confirms with “Copied”.
675
+
676
+ ### Examples
677
+
678
+ #### SDK tabs
679
+
680
+ ```tsx
681
+ <div className="w-full">
682
+ <CodeBlock tabs={sdkSnippets({ baseUrl: "https://gateway.northwind.example" })} />
683
+ </div>
684
+ ```
685
+
686
+ #### File with highlighted lines
687
+
688
+ ```tsx
689
+ <div className="w-full">
690
+ <CodeBlock
691
+ title="compose.yaml"
692
+ lang="yaml"
693
+ lineNumbers
694
+ highlightLines={[5, 6]}
695
+ code={`services:\n gateway:\n image: ghcr.io/logicplanes/kaeso-gateway:0.4.0\n environment:\n KAESO_MASTER_KEY: \${KAESO_MASTER_KEY}\n KAESO_DATABASE_URL: postgres://kaeso@db/kaeso\n ports:\n - "8080:8080"`}
696
+ />
697
+ </div>
698
+ ```
699
+
700
+ #### Collapsed beyond 24 lines
701
+
702
+ ```tsx
703
+ <div className="w-full">
704
+ <CodeBlock lang="json" code={`[\n${long}\n]`} />
705
+ </div>
706
+ ```
707
+
708
+ ## Collapsible
709
+
710
+ Disclosure rows with a chevron that turns in 180ms and a measured height animation; Accordion groups them.
711
+
712
+ `import { Collapsible, Accordion } from "@logicplanes/kaeso-ui";`
713
+
714
+ ### Keyboard
715
+
716
+ | Key | Action |
717
+ |---|---|
718
+ | Enter Space | Opens and closes |
719
+
720
+ ### Examples
721
+
722
+ #### Collapsible
723
+
724
+ ```tsx
725
+ <div className="w-480">
726
+ <Collapsible title="Advanced" hint="2 settings">
727
+ <p className="text-body text-fg-secondary">Timeout overrides and the egress dialect.</p>
728
+ </Collapsible>
729
+ </div>
730
+ ```
731
+
732
+ #### Accordion
733
+
734
+ ```tsx
735
+ <Accordion
736
+ className="w-480"
737
+ defaultValue={["retention"]}
738
+ items={[
739
+ { value: "retention", title: "How long are logs kept?", content: "The organization sets retention, 30 days by default." },
740
+ { value: "content", title: "Is request content stored?", content: "Only when content logging is on for the project." },
741
+ ]}
742
+ />
743
+ ```
744
+
745
+ ## Combobox
746
+
747
+ A pill input with a filtered list: matches highlighted, async results, chips for many, create.
748
+
749
+ `import { Combobox } from "@logicplanes/kaeso-ui";`
750
+
751
+ ### Keyboard
752
+
753
+ | Key | Action |
754
+ |---|---|
755
+ | Up Down | Moves through results |
756
+ | Enter | Chooses |
757
+ | Escape | Closes |
758
+ | Backspace | Removes the last chip |
759
+
760
+ ### Accessibility
761
+
762
+ - role=combobox with a listbox; the result count is announced through a status region.
763
+
764
+ ### Examples
765
+
766
+ #### Single
767
+
768
+ ```tsx
769
+ <div className="w-320">
770
+ <Combobox label="Model" options={options} placeholder="Search models" emptyText="No models match" defaultValue="gpt-5" />
771
+ </div>
772
+ ```
773
+
774
+ #### Multiple with chips
775
+
776
+ ```tsx
777
+ <div className="w-400">
778
+ <Combobox multiple label="Models this key may use" options={options} placeholder="Add a model" defaultValue={["gpt-5-mini", "claude-haiku-4-5"]} />
779
+ </div>
780
+ ```
781
+
782
+ #### Create
783
+
784
+ ```tsx
785
+ function Create() {
786
+ const [opts, setOpts] = useState<ComboboxOption[]>([
787
+ { value: "checkout", label: "checkout" },
788
+ { value: "support", label: "support" },
789
+ ]);
790
+ return (
791
+ <div className="w-320">
792
+ <Combobox label="Alias" options={opts} placeholder="Find or create an alias" onCreate={(l) => setOpts((o) => [...o, { value: l, label: l }])} />
793
+ </div>
794
+ );
795
+ }
796
+ ```
797
+
798
+ ## CommandPalette
799
+
800
+ ⌘K: every page and action, grouped; nested pages; async groups with a quiet progress line.
801
+
802
+ `import { CommandPalette } from "@logicplanes/kaeso-ui";`
803
+
804
+ ### Keyboard
805
+
806
+ | Key | Action |
807
+ |---|---|
808
+ | mod K | Opens |
809
+ | Up Down | Moves |
810
+ | Enter | Opens the item or its page |
811
+ | Backspace | Goes back a page when the search is empty |
812
+ | Escape | Closes |
813
+
814
+ ### Accessibility
815
+
816
+ - The input is a combobox controlling the results listbox; the active result is announced.
817
+
818
+ ### Examples
819
+
820
+ #### Basic
821
+
822
+ ```tsx
823
+ function Basic() {
824
+ const [open, setOpen] = useState(false);
825
+ return (
826
+ <>
827
+ <Button leadingIcon={icons.search} onClick={() => setOpen(true)} size="lg">
828
+ Search
829
+ </Button>
830
+ <CommandPalette open={open} onOpenChange={setOpen} groups={groups} hotkey={false} />
831
+ </>
832
+ );
833
+ }
834
+ ```
835
+
836
+ ## CompareGrid
837
+
838
+ Models side by side: one to four equal columns, each with a header, a thread that scrolls on its own and a footer. One column per screen on a phone.
839
+
840
+ `import { CompareGrid } from "@logicplanes/kaeso-ui/playground";`
841
+
842
+ ### Keyboard
843
+
844
+ | Key | Action |
845
+ |---|---|
846
+ | Tab | Reaches each column's thread, which scrolls with the arrow keys |
847
+
848
+ ### Accessibility
849
+
850
+ - The grid is a named group; each column is a region named after its model, and its thread a region of its own.
851
+ - On a phone, Previous column and Next column move between columns, and “2 of 3” is announced.
852
+ - A thread stays at its end while a response grows, unless the person scrolled up to read.
853
+
854
+ ### Examples
855
+
856
+ #### Compare models
857
+
858
+ Send a prompt to all columns: responses stream in, reasoning first. Add Claude Haiku 4.5 to see a rate-limit error and Retry.
859
+
860
+ ```tsx
861
+ <div className="h-720 w-full overflow-hidden rounded-well shadow-ring">
862
+ <PlaygroundWorkspace initial={answered(["gpt-5", "claude-sonnet-4-5", "gpt-5-mini"], 1)} />
863
+ </div>
864
+ ```
865
+
866
+ #### Two columns
867
+
868
+ ```tsx
869
+ <Columns columns={answered(["gpt-5", "claude-sonnet-4-5"], 1)} />
870
+ ```
871
+
872
+ ## Composer
873
+
874
+ The prompt box: grows with what is written, then scrolls; Send, which becomes Stop while a response streams.
875
+
876
+ `import { Composer } from "@logicplanes/kaeso-ui/playground";`
877
+
878
+ ### Keyboard
879
+
880
+ | Key | Action |
881
+ |---|---|
882
+ | Enter | Sends; nothing while a response streams or the prompt is empty (with submitOn="mod-enter", adds a line) |
883
+ | Shift Enter | Adds a line |
884
+ | ⌘ Enter | Sends (Ctrl Enter on Windows and Linux) |
885
+
886
+ ### Accessibility
887
+
888
+ - The text box is named by aria-label (“Prompt” by default) and announces its shortcuts through aria-keyshortcuts; no hint is drawn.
889
+ - Enter never sends while an input method is composing (Chinese, Japanese, Korean): the Enter that picks a candidate stays the input method's.
890
+ - On a touch screen Enter adds a line and the Send button sends, as in the messaging apps there; elsewhere the on-screen keyboard's Enter key reads Send (enterkeyhint).
891
+ - Send and Stop are one button that keeps its place and width, so the focus stays on it when a response starts or ends.
892
+
893
+ ### Examples
894
+
895
+ #### Send and stop
896
+
897
+ ```tsx
898
+ function Basic() {
899
+ const [streaming, setStreaming] = useState(false);
900
+ return (
901
+ <Composer
902
+ className="w-full max-w-640"
903
+ streaming={streaming}
904
+ onSubmit={() => {
905
+ setStreaming(true);
906
+ setTimeout(() => setStreaming(false), 2400);
907
+ }}
908
+ onStop={() => setStreaming(false)}
909
+ />
910
+ );
911
+ }
912
+ ```
913
+
914
+ #### With actions
915
+
916
+ Controls at the start of the button row.
917
+
918
+ ```tsx
919
+ <Composer
920
+ className="w-full max-w-640"
921
+ aria-label="Message all models"
922
+ placeholder="Message 3 models"
923
+ actions={
924
+ <Button size="sm" variant="ghost" leadingIcon={icons.displayOptions}>
925
+ Parameters
926
+ </Button>
927
+ }
928
+ />
929
+ ```
930
+
931
+ #### Send with ⌘ Enter
932
+
933
+ submitOn="mod-enter": Enter adds a line, ⌘ Enter or Ctrl Enter sends. For long prompts written in several lines.
934
+
935
+ ```tsx
936
+ <Composer className="w-full max-w-640" submitOn="mod-enter" placeholder="Write the system prompt" />
937
+ ```
938
+
939
+ ## ContextMenu
940
+
941
+ The same menu on right-click and the context-menu key.
942
+
943
+ `import { ContextMenu } from "@logicplanes/kaeso-ui";`
944
+
945
+ ### Keyboard
946
+
947
+ | Key | Action |
948
+ |---|---|
949
+ | Shift F10 | Opens the menu for the focused element |
950
+
951
+ ### Accessibility
952
+
953
+ - Every action in a context menu must also be reachable another way.
954
+
955
+ ### Examples
956
+
957
+ #### On a row
958
+
959
+ ```tsx
960
+ <ContextMenu>
961
+ <ContextMenu.Trigger className="flex h-44 w-400 items-center rounded-item bg-fill-1 px-16 text-body text-fg">
962
+ Right-click this request
963
+ </ContextMenu.Trigger>
964
+ <ContextMenu.Content>
965
+ <ContextMenu.Item icon={icons.openExternal}>Open in a sheet</ContextMenu.Item>
966
+ <ContextMenu.Item icon={icons.copy}>Copy request id</ContextMenu.Item>
967
+ <ContextMenu.Item icon={icons.filter}>Filter by this model</ContextMenu.Item>
968
+ </ContextMenu.Content>
969
+ </ContextMenu>
970
+ ```
971
+
972
+ ## ConversationView
973
+
974
+ A model conversation from a request log: roles, prose, tool calls and results, reasoning, images, refusals.
975
+
976
+ `import { ConversationView } from "@logicplanes/kaeso-ui";`
977
+
978
+ ### Examples
979
+
980
+ #### Checkout conversation
981
+
982
+ ```tsx
983
+ <ConversationView messages={conversation} className="w-640" />
984
+ ```
985
+
986
+ #### Text as Markdown
987
+
988
+ markdown draws text, reasoning and prose tool results as Markdown, safely; the renderer loads the first time it is needed.
989
+
990
+ ```tsx
991
+ <ConversationView
992
+ markdown
993
+ className="w-full max-w-640"
994
+ messages={[
995
+ { role: "user", content: "Which models failed most today? Answer with a table.\n\n<system-reminder>Keep it short.</system-reminder>" },
996
+ {
997
+ role: "assistant",
998
+ content:
999
+ "Two did:\n\n| Model | Errors | Rate |\n|---|--:|--:|\n| gpt-5 | 41 | 0.9% |\n| claude-haiku-4-5 | 12 | 0.3% |\n\nMost were **429s** from the project's limit. See the [request logs](https://kaeso.example.com/docs/logs).",
1000
+ },
1001
+ ]}
1002
+ />
1003
+ ```
1004
+
1005
+ ## DataTable
1006
+
1007
+ The console's table: a 36px header, 40px rows without lines, numbers right, sorting, resizing, selection, virtualization.
1008
+
1009
+ `import { DataTable, col } from "@logicplanes/kaeso-ui";`
1010
+
1011
+ ### Keyboard
1012
+
1013
+ | Key | Action |
1014
+ |---|---|
1015
+ | Tab | Moves to the next row |
1016
+ | Enter | Opens the row |
1017
+ | shift F10 | Opens the row menu |
1018
+
1019
+ ### Accessibility
1020
+
1021
+ - role=table with column headers; sorted columns carry aria-sort.
1022
+ - Columns declare a priority; low-priority columns hide first when space is short. A growing column then shrinks to its readable minimum (minWidth, 136px by default) and truncates. On a screen narrower still, the other priority 1 columns give way from the row's end backwards, keeping its last column (the actions) and the selection boxes; only a row with nothing left to give way scrolls sideways, its last column pinned at the end.
1023
+ - Over 100 rows render virtually; the row count is in aria-rowcount.
1024
+
1025
+ ### Examples
1026
+
1027
+ #### Request logs
1028
+
1029
+ ```tsx
1030
+ function Logs() {
1031
+ const [open, setOpen] = useState<LogRow | null>(null);
1032
+ const [n, setN] = useState(60);
1033
+ const data = useMemo(() => logs.slice(0, n), [n]);
1034
+ return (
1035
+ <div className="h-480 w-full overflow-y-auto scrollbar-quiet">
1036
+ <DataTable
1037
+ aria-label="Request logs"
1038
+ data={data}
1039
+ columns={columns}
1040
+ getRowId={(r) => r.id}
1041
+ selectable
1042
+ activeRowId={open?.id}
1043
+ onRowClick={setOpen}
1044
+ rowMenu={(r) => (
1045
+ <>
1046
+ <Menu.Item icon={icons.openExternal} onClick={() => setOpen(r)}>
1047
+ Open request
1048
+ </Menu.Item>
1049
+ <Menu.Item icon={icons.copy}>Copy request id</Menu.Item>
1050
+ <Menu.Item icon={icons.filter}>Filter by {r.model}</Menu.Item>
1051
+ </>
1052
+ )}
1053
+ hasMore={n < logs.length}
1054
+ onLoadMore={() => setN((x) => x + 60)}
1055
+ />
1056
+ <Sheet
1057
+ open={!!open}
1058
+ onOpenChange={(o) => !o && setOpen(null)}
1059
+ title={open ? `${open.method} ${open.endpoint}` : ""}
1060
+ subtitle={open?.id}
1061
+ size="md"
1062
+ modal={false}
1063
+ >
1064
+ {open && (
1065
+ <div className="flex flex-col gap-24">
1066
+ <RequestTimeline ttfbMs={open.ttfbMs} totalMs={open.latencyMs} />
1067
+ <KeyValue
1068
+ items={[
1069
+ { label: "Status", value: <StatusCode code={open.status} showText /> },
1070
+ { label: "Model", value: open.model, mono: true },
1071
+ { label: "Key", value: open.key },
1072
+ { label: "Cost", value: `$${open.cost}` },
1073
+ ]}
1074
+ />
1075
+ </div>
1076
+ )}
1077
+ </Sheet>
1078
+ </div>
1079
+ );
1080
+ }
1081
+ ```
1082
+
1083
+ #### Any width
1084
+
1085
+ Narrow the window: the key, latency and cost drop first, the model keeps a readable width with its name cut short, and the row's actions stay at its end.
1086
+
1087
+ ```tsx
1088
+ <div className="flex w-full flex-col gap-32">
1089
+ <DataTable aria-label="Request logs" data={logs.slice(0, 6)} columns={phoneColumns} getRowId={(r) => r.id} />
1090
+ <DataTable aria-label="Models" data={models} columns={modelColumns} getRowId={(m) => m.id} />
1091
+ </div>
1092
+ ```
1093
+
1094
+ #### Tones
1095
+
1096
+ A column's tone colors the words that already carry a status: an expired or disabled key. Use it sparingly.
1097
+
1098
+ ```tsx
1099
+ <div className="w-full">
1100
+ <DataTable aria-label="Keys" data={keys} columns={keyColumns} getRowId={(k) => k.id} />
1101
+ </div>
1102
+ ```
1103
+
1104
+ #### Loading, empty and error
1105
+
1106
+ A failed load says so where the rows would be and offers Try again; the error itself is a toast.
1107
+
1108
+ ```tsx
1109
+ <div className="flex w-full flex-col gap-32">
1110
+ <DataTable aria-label="Loading" data={[]} columns={columns.slice(0, 5)} loading />
1111
+ <DataTable aria-label="Empty" data={[]} columns={columns.slice(0, 5)} />
1112
+ <DataTable
1113
+ aria-label="Error"
1114
+ data={[]}
1115
+ columns={columns.slice(0, 5)}
1116
+ error="The gateway answered 503. Check the gateway logs."
1117
+ onRetry={() => undefined}
1118
+ />
1119
+ </div>
1120
+ ```
1121
+
1122
+ ## DatePicker
1123
+
1124
+ A pill showing the date; a calendar with circles, today ringed, and a typed ISO field.
1125
+
1126
+ `import { DatePicker, DateRangePicker, Calendar, TimeInput } from "@logicplanes/kaeso-ui";`
1127
+
1128
+ ### Keyboard
1129
+
1130
+ | Key | Action |
1131
+ |---|---|
1132
+ | Arrows | Move between days |
1133
+ | Page Up Page Down | Previous and next month |
1134
+ | Enter | Chooses the day |
1135
+
1136
+ ### Accessibility
1137
+
1138
+ - The calendar is a grid with day names; typed ISO fields (YYYY-MM-DD) are an alternative to pointing.
1139
+ - The calendar, the trigger and the typed fields name the same day, in the viewer's calendar.
1140
+ - An aria-label or a field's label names the trigger; the date or range it shows is its description.
1141
+
1142
+ ### Examples
1143
+
1144
+ #### Date
1145
+
1146
+ ```tsx
1147
+ <DatePicker label="Expires" defaultValue={new Date(2027, 0, 1)} />
1148
+ ```
1149
+
1150
+ #### Range with presets
1151
+
1152
+ Presets first: Last 24 hours, 7 days, 30 days, This month, Last month, Custom. A preset applies at once; chosen days with Apply.
1153
+
1154
+ ```tsx
1155
+ <DateRangePicker aria-label="Period" defaultValue={{ from: new Date(2026, 9, 1), to: new Date(2026, 9, 12) }} />
1156
+ ```
1157
+
1158
+ #### Time
1159
+
1160
+ ```tsx
1161
+ <TimeInput label="Daily report at" defaultValue="09:30" utc />
1162
+ ```
1163
+
1164
+ ## DeleteResource
1165
+
1166
+ The destructive flow: the object and the consequence, typed confirmation, a danger button.
1167
+
1168
+ `import { DeleteResource } from "@logicplanes/kaeso-ui";`
1169
+
1170
+ ### Examples
1171
+
1172
+ #### Archive a project
1173
+
1174
+ ```tsx
1175
+ <DeleteResource
1176
+ kind="project"
1177
+ verb="Archive"
1178
+ name="Checkout assistant"
1179
+ consequence="Its keys stop working. Logs stay for 30 days, then they are deleted."
1180
+ onConfirm={() => new Promise((r) => setTimeout(r, 900))}
1181
+ trigger={<Button variant="danger-secondary">Archive project</Button>}
1182
+ />
1183
+ ```
1184
+
1185
+ ## Dialog
1186
+
1187
+ A modal dialog: 440, 560 or 720 wide, radius 20; title, description, body, actions. ⌘Enter submits. Longer forms use StepDialog: one question per step.
1188
+
1189
+ `import { Dialog } from "@logicplanes/kaeso-ui";`
1190
+
1191
+ ### Keyboard
1192
+
1193
+ | Key | Action |
1194
+ |---|---|
1195
+ | Escape | Closes and returns focus to the trigger |
1196
+ | Tab | Moves within the dialog |
1197
+ | mod Enter | Submits |
1198
+
1199
+ ### Accessibility
1200
+
1201
+ - Focus moves into the dialog and returns to the trigger when it closes. It lands on the first field (or the control marked data-autofocus); without one, on the primary action, or on the safe one, such as Cancel, when the primary action destroys or is unavailable; then on the first control. Never on the dialog box itself.
1202
+ - The title names the dialog; the description is its accessible description.
1203
+ - The footer line appears only while content scrolls under it.
1204
+ - In steps, Continue and Back move focus to the new step's first field, or by the same rule to its primary or safe action when it has none, and the step's number and question are announced.
1205
+ - In steps, the dialog takes each step's height and keeps its top; with reduced motion the step and the height change at once.
1206
+
1207
+ ### Examples
1208
+
1209
+ #### Create a key
1210
+
1211
+ ```tsx
1212
+ function Form() {
1213
+ const [open, setOpen] = useState(false);
1214
+ const create = () => {
1215
+ setOpen(false);
1216
+ toast.success("Key created");
1217
+ };
1218
+ return (
1219
+ <>
1220
+ <Dialog
1221
+ open={open}
1222
+ onOpenChange={setOpen}
1223
+ trigger={<Button variant="primary">Create key</Button>}
1224
+ title="Create key"
1225
+ description="Applications use this key to call the gateway."
1226
+ onSubmit={create}
1227
+ footer={
1228
+ <>
1229
+ <Dialog.Close>
1230
+ <Button>Cancel</Button>
1231
+ </Dialog.Close>
1232
+ <Button variant="primary" onClick={create} shortcut={["mod", "enter"]}>
1233
+ Create key
1234
+ </Button>
1235
+ </>
1236
+ }
1237
+ >
1238
+ <div className="flex flex-col gap-20">
1239
+ <Input label="Name" placeholder="checkout-web" autoFocus />
1240
+ <Select
1241
+ label="Expires"
1242
+ options={[
1243
+ { value: "never", label: "Never" },
1244
+ { value: "90", label: "In 90 days" },
1245
+ ]}
1246
+ defaultValue="90"
1247
+ fullWidth
1248
+ />
1249
+ </div>
1250
+ </Dialog>
1251
+ </>
1252
+ );
1253
+ }
1254
+ ```
1255
+
1256
+ #### In steps, one question at a time
1257
+
1258
+ ```tsx
1259
+ function Steps() {
1260
+ const [open, setOpen] = useState(false);
1261
+ const [name, setName] = useState("");
1262
+ return (
1263
+ <>
1264
+ <Button variant="primary" onClick={() => setOpen(true)}>
1265
+ Create key
1266
+ </Button>
1267
+ <StepDialog
1268
+ open={open}
1269
+ onOpenChange={setOpen}
1270
+ title="Create key"
1271
+ submitLabel="Create key"
1272
+ onSubmit={() => {
1273
+ setOpen(false);
1274
+ toast.success(`Key ${name} created`);
1275
+ }}
1276
+ steps={[
1277
+ {
1278
+ id: "name",
1279
+ title: "What is it for?",
1280
+ description: "Shown in request logs and alerts.",
1281
+ blockedReason: name.trim() ? undefined : "Enter a name",
1282
+ content: <Input aria-label="Name" placeholder="checkout-web" value={name} onChange={(e) => setName(e.target.value)} autoFocus />,
1283
+ },
1284
+ {
1285
+ id: "expiry",
1286
+ title: "When does it expire?",
1287
+ description: "Choose the shortest time the key is needed.",
1288
+ content: <Select aria-label="Expires" options={expiry} defaultValue="90" fullWidth />,
1289
+ },
1290
+ ]}
1291
+ />
1292
+ </>
1293
+ );
1294
+ }
1295
+ ```
1296
+
1297
+ #### Steps of different heights
1298
+
1299
+ ```tsx
1300
+ function StepHeights() {
1301
+ const [open, setOpen] = useState(false);
1302
+ const [level, setLevel] = useState("project");
1303
+ return (
1304
+ <>
1305
+ <Button variant="primary" onClick={() => setOpen(true)}>
1306
+ Add limit
1307
+ </Button>
1308
+ <StepDialog
1309
+ open={open}
1310
+ onOpenChange={setOpen}
1311
+ title="Add limit"
1312
+ size="md"
1313
+ submitLabel="Add limit"
1314
+ onSubmit={() => {
1315
+ setOpen(false);
1316
+ toast.success("Limit added");
1317
+ }}
1318
+ steps={limitSteps(level, setLevel)}
1319
+ />
1320
+ </>
1321
+ );
1322
+ }
1323
+ ```
1324
+
1325
+ #### Sizes
1326
+
1327
+ ```tsx
1328
+ <div className="flex gap-12">
1329
+ {(["sm", "md", "lg"] as const).map((s) => (
1330
+ <Dialog
1331
+ key={s}
1332
+ size={s}
1333
+ trigger={<Button>{s}</Button>}
1334
+ title={`A ${s} dialog`}
1335
+ description="Sizes are 440, 560 and 720 pixels."
1336
+ footer={
1337
+ <Dialog.Close>
1338
+ <Button>Close</Button>
1339
+ </Dialog.Close>
1340
+ }
1341
+ />
1342
+ ))}
1343
+ </div>
1344
+ ```
1345
+
1346
+ ## DisplayOptions
1347
+
1348
+ View, grouping and ordering, toggles, and the properties to show, behind one circle at the end of a toolbar.
1349
+
1350
+ `import { DisplayOptions } from "@logicplanes/kaeso-ui";`
1351
+
1352
+ ### Accessibility
1353
+
1354
+ - The trigger is named “Display options”; the popover holds labelled controls.
1355
+
1356
+ ### Examples
1357
+
1358
+ #### Basic
1359
+
1360
+ ```tsx
1361
+ <DisplayOptions {...useLogDisplayOptions()} />
1362
+ ```
1363
+
1364
+ ## Donut
1365
+
1366
+ A ring with rounded caps and 2px gaps, the total in the center; ShareBar is the same split in one pill.
1367
+
1368
+ `import { Donut, ShareBar } from "@logicplanes/kaeso-ui";`
1369
+
1370
+ ### Examples
1371
+
1372
+ #### Spend by model
1373
+
1374
+ ```tsx
1375
+ <Donut aria-label="Spend by model" slices={slices} formatValue={money} totalLabel="Total spend" />
1376
+ ```
1377
+
1378
+ #### ShareBar
1379
+
1380
+ ```tsx
1381
+ <div className="w-480">
1382
+ <ShareBar
1383
+ aria-label="Requests by provider"
1384
+ slices={[
1385
+ { id: "openai", label: "OpenAI", value: 7420 },
1386
+ { id: "anthropic", label: "Anthropic", value: 5060 },
1387
+ ]}
1388
+ />
1389
+ </div>
1390
+ ```
1391
+
1392
+ ## EffectiveAccess
1393
+
1394
+ What someone can do at a glance: a row per group, in words, under the roles it comes from.
1395
+
1396
+ `import { EffectiveAccess } from "@logicplanes/kaeso-ui";`
1397
+
1398
+ ### Accessibility
1399
+
1400
+ - A definition list: each group is a term, what is granted its description.
1401
+ - A source chip with a detail says it in a tooltip.
1402
+
1403
+ ### Examples
1404
+
1405
+ #### A member in a project
1406
+
1407
+ ```tsx
1408
+ <EffectiveAccess
1409
+ className="w-560"
1410
+ aria-label="Maya Chen's access in Support bot"
1411
+ sources={[
1412
+ { label: "Developer", detail: "Project role" },
1413
+ { label: "Billing", detail: "Organization role" },
1414
+ ]}
1415
+ groups={withGrants(PROJECT_GROUPS, { Developer: DEVELOPER })}
1416
+ action={<Button size="sm">See every right</Button>}
1417
+ />
1418
+ ```
1419
+
1420
+ ## Empty
1421
+
1422
+ What is absent and the next step: an icon circle, a title, one sentence, one or two actions.
1423
+
1424
+ `import { Empty } from "@logicplanes/kaeso-ui";`
1425
+
1426
+ ### Examples
1427
+
1428
+ #### In a page
1429
+
1430
+ ```tsx
1431
+ <Empty
1432
+ title="No keys yet"
1433
+ description="Create a key so an application can call the gateway."
1434
+ actions={
1435
+ <>
1436
+ <Button variant="primary">Create key</Button>
1437
+ <Button>Read about keys</Button>
1438
+ </>
1439
+ }
1440
+ />
1441
+ ```
1442
+
1443
+ #### In a table
1444
+
1445
+ ```tsx
1446
+ <Empty size="compact" title="No requests match" description="Try a wider time range." />
1447
+ ```
1448
+
1449
+ ## Field
1450
+
1451
+ A label, a control and its help, wired together; errors are toasts, never text under the field.
1452
+
1453
+ `import { Field, Fieldset } from "@logicplanes/kaeso-ui";`
1454
+
1455
+ ### Accessibility
1456
+
1457
+ - The label points at the control; help is in aria-describedby.
1458
+ - invalid sets aria-invalid and the danger ring; say what is wrong in a toast. A deprecated error stays the control's description for a screen reader but isn't drawn.
1459
+ - Optional fields are marked, not required ones.
1460
+
1461
+ ### Examples
1462
+
1463
+ #### Vertical
1464
+
1465
+ ```tsx
1466
+ <Fieldset legend="New key" description="Applications use this key to call the gateway." className="w-400">
1467
+ <Field label="Name" description="Shown in logs and the audit log.">
1468
+ <Input placeholder="checkout-web" />
1469
+ </Field>
1470
+ <Field label="Expires" optional>
1471
+ <Select
1472
+ options={[
1473
+ { value: "never", label: "Never" },
1474
+ { value: "30", label: "In 30 days" },
1475
+ { value: "90", label: "In 90 days" },
1476
+ ]}
1477
+ defaultValue="90"
1478
+ />
1479
+ </Field>
1480
+ </Fieldset>
1481
+ ```
1482
+
1483
+ #### Horizontal
1484
+
1485
+ ```tsx
1486
+ <div className="flex w-640 flex-col gap-24">
1487
+ <Field label="Name" layout="horizontal" description="Shown in logs.">
1488
+ <Input defaultValue="checkout-web" />
1489
+ </Field>
1490
+ <Field label="Monthly budget" layout="horizontal" invalid>
1491
+ <Input defaultValue="-20" suffix="USD" />
1492
+ </Field>
1493
+ </div>
1494
+ ```
1495
+
1496
+ ## FilterChip
1497
+
1498
+ A filter as one pill, “Status is 2xx”: property, operator and value, each opening a menu.
1499
+
1500
+ `import { FilterChip } from "@logicplanes/kaeso-ui";`
1501
+
1502
+ ### Keyboard
1503
+
1504
+ | Key | Action |
1505
+ |---|---|
1506
+ | Tab | Moves between the parts |
1507
+ | Enter | Opens the part's menu |
1508
+
1509
+ ### Accessibility
1510
+
1511
+ - The chip is a group named after its property (“Filter: Status”); the remove circle says which filter it removes (“Remove the Status filter”).
1512
+
1513
+ ### Examples
1514
+
1515
+ #### Filters in a toolbar
1516
+
1517
+ ```tsx
1518
+ function Basic() {
1519
+ const [status, setStatus] = useState(["2xx"]);
1520
+ const [op, setOp] = useState("is");
1521
+ return (
1522
+ <div className="flex items-center gap-8">
1523
+ <FilterChip
1524
+ property="Status"
1525
+ propertyIcon={icons.chart}
1526
+ operator={op}
1527
+ operators={["is", "is not"]}
1528
+ onOperatorChange={setOp}
1529
+ value={status.length > 1 ? `${status.length} classes` : (status[0] ?? "any")}
1530
+ multiple
1531
+ values={[
1532
+ { value: "2xx", label: "2xx" },
1533
+ { value: "4xx", label: "4xx" },
1534
+ { value: "5xx", label: "5xx" },
1535
+ ]}
1536
+ selected={status}
1537
+ onSelectedChange={setStatus}
1538
+ onRemove={() => setStatus([])}
1539
+ />
1540
+ <Button variant="ghost" size="sm" leadingIcon={icons.add}>
1541
+ Add filter
1542
+ </Button>
1543
+ </div>
1544
+ );
1545
+ }
1546
+ ```
1547
+
1548
+ #### A long list of values, searched
1549
+
1550
+ ```tsx
1551
+ function Searched() {
1552
+ const [model, setModel] = useState<string | null>(null);
1553
+ const name = MODELS.find(([id]) => id === model)?.[1];
1554
+ return (
1555
+ <FilterChip
1556
+ property="Model"
1557
+ operator="is"
1558
+ value={name ?? "any"}
1559
+ values={MODELS.map(([id, label]) => ({ value: id, label, textValue: `${label} ${id}` }))}
1560
+ selected={model ? [model] : []}
1561
+ onSelectedChange={(v) => setModel(v[0] ?? null)}
1562
+ onRemove={() => setModel(null)}
1563
+ />
1564
+ );
1565
+ }
1566
+ ```
1567
+
1568
+ ## FilterRow
1569
+
1570
+ Pill tabs at the start and filters at the end, between a page's header and its list. Grows with its content and wraps on a narrow screen.
1571
+
1572
+ `import { FilterRow } from "@logicplanes/kaeso-ui";`
1573
+
1574
+ ### Keyboard
1575
+
1576
+ | Key | Action |
1577
+ |---|---|
1578
+ | Left Right | Moves between tabs, or between filters |
1579
+ | Tab | From the tabs to the filters |
1580
+
1581
+ ### Accessibility
1582
+
1583
+ - The filters are a toolbar of their own (named Filters by default), so its arrow keys never reach the tablist and the tabs keep theirs.
1584
+ - Without filters there is no toolbar: the row is layout only.
1585
+
1586
+ ### Examples
1587
+
1588
+ #### In a settings column
1589
+
1590
+ ```tsx
1591
+ function Settings() {
1592
+ const [state, setState] = useState("all");
1593
+ const [preset, setPreset] = useState("all");
1594
+ const items: ListItemData[] = members
1595
+ .filter((m) => preset === "all" || m.preset === preset)
1596
+ .filter(() => state !== "suspended")
1597
+ .map((m) => ({ id: m.id, title: m.name, subtitle: m.email, meta: <Badge>{m.preset}</Badge> }));
1598
+ return (
1599
+ <Column>
1600
+ <FilterRow tabs={stateTabs(state, setState)}>
1601
+ <Select aria-label="Preset" size="sm" value={preset} onValueChange={(v) => setPreset(String(v))} options={presetOptions} />
1602
+ </FilterRow>
1603
+ <List
1604
+ aria-label="Members"
1605
+ items={items}
1606
+ empty={<Empty size="md" title="No suspended members" description="Suspended members can't sign in. Everyone in Northwind Labs is active." />}
1607
+ />
1608
+ </Column>
1609
+ );
1610
+ }
1611
+ ```
1612
+
1613
+ #### On an edge-to-edge page
1614
+
1615
+ ```tsx
1616
+ function Page() {
1617
+ const [state, setState] = useState("all");
1618
+ return (
1619
+ <div className="w-full rounded-well bg-panel shadow-frame">
1620
+ <FilterRow tabs={stateTabs(state, setState)}>
1621
+ <div className="w-240">
1622
+ <SearchInput aria-label="Search members" placeholder="Name or email" />
1623
+ </div>
1624
+ <Select
1625
+ aria-label="Project"
1626
+ size="sm"
1627
+ defaultValue="all"
1628
+ options={[{ value: "all", label: "Every project" }, ...projects.map((p) => ({ value: p.id, label: p.name }))]}
1629
+ />
1630
+ </FilterRow>
1631
+ </div>
1632
+ );
1633
+ }
1634
+ ```
1635
+
1636
+ ## Flow
1637
+
1638
+ A node-and-connector diagram for the route: app → Kaeso → provider and model.
1639
+
1640
+ `import { Flow } from "@logicplanes/kaeso-ui";`
1641
+
1642
+ ### Examples
1643
+
1644
+ #### Route
1645
+
1646
+ ```tsx
1647
+ <Flow columns={columns} edges={edges} aria-label="Requests go from your app through the Kaeso gateway to gpt-5 or claude-sonnet-4-5" />
1648
+ ```
1649
+
1650
+ ## Gauge
1651
+
1652
+ A semicircle for “budget used”; thresholds as ticks.
1653
+
1654
+ `import { Gauge } from "@logicplanes/kaeso-ui";`
1655
+
1656
+ ### Examples
1657
+
1658
+ #### Budget
1659
+
1660
+ ```tsx
1661
+ <Gauge aria-label="Monthly budget used" value={0.64} label="of $500.00 budget" valueLabel="$320.00" />
1662
+ ```
1663
+
1664
+ ## Grid
1665
+
1666
+ Grid, Stack and Inline: layout with gaps from the spacing scale only.
1667
+
1668
+ `import { Grid, Stack, Inline } from "@logicplanes/kaeso-ui";`
1669
+
1670
+ ### Examples
1671
+
1672
+ #### Grid
1673
+
1674
+ ```tsx
1675
+ <Grid columns={3} gap={12} className="w-full">
1676
+ {[1, 2, 3, 4, 5, 6].map((i) => (
1677
+ <Box key={i}>{i}</Box>
1678
+ ))}
1679
+ </Grid>
1680
+ ```
1681
+
1682
+ #### Stack and Inline
1683
+
1684
+ ```tsx
1685
+ <Stack gap={12} className="w-320">
1686
+ <Box>Stack</Box>
1687
+ <Inline gap={8}>
1688
+ <Box>Inline</Box>
1689
+ <Box>items</Box>
1690
+ </Inline>
1691
+ </Stack>
1692
+ ```
1693
+
1694
+ ## Heading
1695
+
1696
+ Headings on the title tokens, balanced across lines.
1697
+
1698
+ `import { Heading } from "@logicplanes/kaeso-ui";`
1699
+
1700
+ ### Accessibility
1701
+
1702
+ - The document level follows from the size; set level when the outline needs another one.
1703
+
1704
+ ### Examples
1705
+
1706
+ #### Sizes
1707
+
1708
+ ```tsx
1709
+ <div className="flex flex-col gap-16">
1710
+ <Heading size="display">Your gateway</Heading>
1711
+ <Heading size="title-lg">Preferences</Heading>
1712
+ <Heading size="title">No keys yet</Heading>
1713
+ <Heading size="title-sm">Interface and theme</Heading>
1714
+ </div>
1715
+ ```
1716
+
1717
+ ## Heatmap
1718
+
1719
+ Hour by weekday on a five-step ramp from fill to series 1; never a rainbow.
1720
+
1721
+ `import { Heatmap } from "@logicplanes/kaeso-ui";`
1722
+
1723
+ ### Examples
1724
+
1725
+ #### Requests by hour
1726
+
1727
+ ```tsx
1728
+ <Heatmap aria-label="Requests by hour and weekday" rows={days} columns={hours} values={values} />
1729
+ ```
1730
+
1731
+ ## Icon
1732
+
1733
+ Hugeicons free, Stroke Rounded, in currentColor. One icon per Kaeso concept.
1734
+
1735
+ `import { Icon, icons } from "@logicplanes/kaeso-ui";`
1736
+
1737
+ ### Accessibility
1738
+
1739
+ - Icons are decorative by default (aria-hidden). Pass label when the icon alone carries meaning.
1740
+
1741
+ ### Examples
1742
+
1743
+ #### Sizes
1744
+
1745
+ 14 (1.75 stroke), 16, 18, 20, 24.
1746
+
1747
+ ```tsx
1748
+ <div className="flex items-end gap-16 text-fg">
1749
+ {(["xs", "sm", "md", "lg", "xl"] as const).map((s) => (
1750
+ <Icon key={s} icon={icons.key} size={s} />
1751
+ ))}
1752
+ </div>
1753
+ ```
1754
+
1755
+ #### Vocabulary
1756
+
1757
+ ```tsx
1758
+ <div className="grid w-full grid-cols-2 gap-x-16 gap-y-4 sm:grid-cols-4">
1759
+ {(Object.keys(icons) as IconName[]).map((n) => (
1760
+ <div key={n} className="flex h-32 items-center gap-8 text-small text-fg-secondary">
1761
+ <Icon icon={icons[n]} size="sm" className="text-fg" />
1762
+ <span className="mono truncate">{n}</span>
1763
+ </div>
1764
+ ))}
1765
+ </div>
1766
+ ```
1767
+
1768
+ ## IconButton
1769
+
1770
+ A circle with an icon, a required name, and a tooltip that says it.
1771
+
1772
+ `import { IconButton } from "@logicplanes/kaeso-ui";`
1773
+
1774
+ ### Keyboard
1775
+
1776
+ | Key | Action |
1777
+ |---|---|
1778
+ | Enter | Activates |
1779
+ | Space | Activates |
1780
+
1781
+ ### Accessibility
1782
+
1783
+ - aria-label is required by the types; it is also the tooltip, so sighted and screen-reader users get the same name.
1784
+ - Shortcuts appear in the tooltip as key caps.
1785
+ - Disabled icon buttons have no tooltip unless they carry a disabledReason.
1786
+
1787
+ ### Examples
1788
+
1789
+ #### Basic
1790
+
1791
+ ```tsx
1792
+ <IconButton icon={icons.filter} aria-label="Filter" />
1793
+ ```
1794
+
1795
+ #### Variants
1796
+
1797
+ Ghost in rows and toolbars; secondary for the raised circles in page headers.
1798
+
1799
+ ```tsx
1800
+ <Inline gap={12}>
1801
+ <IconButton icon={icons.filter} aria-label="Filter" variant="secondary" />
1802
+ <IconButton icon={icons.displayOptions} aria-label="Display options" variant="secondary" />
1803
+ <IconButton icon={icons.sidePanel} aria-label="Toggle side panel" variant="secondary" />
1804
+ <IconButton icon={icons.more} aria-label="More actions" />
1805
+ <IconButton icon={icons.add} aria-label="Create key" variant="primary" />
1806
+ </Inline>
1807
+ ```
1808
+
1809
+ #### Sizes
1810
+
1811
+ ```tsx
1812
+ <Inline gap={12}>
1813
+ {(["xs", "sm", "md", "lg", "xl"] as ButtonSize[]).map((s) => (
1814
+ <IconButton key={s} size={s} icon={icons.add} aria-label="Add" variant="secondary" />
1815
+ ))}
1816
+ </Inline>
1817
+ ```
1818
+
1819
+ #### With a shortcut
1820
+
1821
+ ```tsx
1822
+ <IconButton icon={icons.search} aria-label="Search" shortcut={["mod", "k"]} variant="secondary" />
1823
+ ```
1824
+
1825
+ ## Input
1826
+
1827
+ A pill text field with the secondary material and a soft focus halo.
1828
+
1829
+ `import { Input } from "@logicplanes/kaeso-ui";`
1830
+
1831
+ ### Keyboard
1832
+
1833
+ | Key | Action |
1834
+ |---|---|
1835
+ | Tab | Moves focus in and out |
1836
+
1837
+ ### Accessibility
1838
+
1839
+ - Pass label and description and the input wraps itself in Field, wiring aria-describedby; invalid sets aria-invalid. Errors are toasts, never text under the field.
1840
+ - Placeholders are hints, never labels.
1841
+ - Read-only fields stay selectable and focusable.
1842
+
1843
+ ### Examples
1844
+
1845
+ #### Basic
1846
+
1847
+ ```tsx
1848
+ <div className="w-320">
1849
+ <Input aria-label="Key name" placeholder="checkout-web" />
1850
+ </div>
1851
+ ```
1852
+
1853
+ #### With label and help
1854
+
1855
+ An invalid value gets the danger ring; what is wrong goes in a toast.
1856
+
1857
+ ```tsx
1858
+ <div className="flex w-360 flex-col gap-24">
1859
+ <Input label="Key name" description="Shown in logs and the audit log." placeholder="checkout-web" />
1860
+ <Input label="Monthly budget" defaultValue="-20" suffix="USD" invalid />
1861
+ </div>
1862
+ ```
1863
+
1864
+ #### Sizes
1865
+
1866
+ ```tsx
1867
+ <div className="flex w-320 flex-col gap-12">
1868
+ {(["sm", "md", "lg", "xl"] as InputSize[]).map((s) => (
1869
+ <Input key={s} size={s} aria-label={`Size ${s}`} placeholder={`Size ${s}`} />
1870
+ ))}
1871
+ </div>
1872
+ ```
1873
+
1874
+ #### Icon, prefix and suffix
1875
+
1876
+ ```tsx
1877
+ <div className="flex w-360 flex-col gap-12">
1878
+ <Input aria-label="Email" leadingIcon={icons.mail} placeholder="ada@northwind.example" />
1879
+ <Input aria-label="Gateway URL" prefix="https://" placeholder="gateway.northwind.example" />
1880
+ <Input aria-label="Rate limit" suffix="/ min" defaultValue="600" align="end" />
1881
+ </div>
1882
+ ```
1883
+
1884
+ #### Read-only and disabled
1885
+
1886
+ ```tsx
1887
+ <div className="flex w-320 flex-col gap-12">
1888
+ <Input aria-label="Project id" readOnly defaultValue="proj_01J9Z5A" />
1889
+ <Input aria-label="Disabled" disabled defaultValue="Managed by the organization" />
1890
+ </div>
1891
+ ```
1892
+
1893
+ ## InputGroup
1894
+
1895
+ Addons inside the pill: text, icons, a ghost circle or a key cap, sharing one focus halo.
1896
+
1897
+ `import { InputGroup } from "@logicplanes/kaeso-ui";`
1898
+
1899
+ ### Accessibility
1900
+
1901
+ - Addons are text the input's label should not repeat; buttons inside need their own names.
1902
+
1903
+ ### Examples
1904
+
1905
+ #### Addons
1906
+
1907
+ ```tsx
1908
+ <div className="flex w-400 flex-col gap-12">
1909
+ <InputGroup>
1910
+ <InputGroup.Addon>https://</InputGroup.Addon>
1911
+ <InputGroup.Input aria-label="Host" placeholder="gateway.northwind.example" />
1912
+ <InputGroup.Addon>/v1</InputGroup.Addon>
1913
+ </InputGroup>
1914
+ <InputGroup trailingButton>
1915
+ <InputGroup.Input aria-label="Token" defaultValue="sk-kso-AbC123…wxyz" className="mono text-small" />
1916
+ <InputGroup.Button icon={icons.copy} aria-label="Copy" />
1917
+ </InputGroup>
1918
+ <InputGroup leadingIcon>
1919
+ <InputGroup.Addon icon={icons.search} />
1920
+ <InputGroup.Input aria-label="Search" placeholder="Search models" />
1921
+ </InputGroup>
1922
+ </div>
1923
+ ```
1924
+
1925
+ ## JsonView
1926
+
1927
+ A folding JSON tree: copy any path, highlight a search.
1928
+
1929
+ `import { JsonView } from "@logicplanes/kaeso-ui";`
1930
+
1931
+ ### Examples
1932
+
1933
+ #### Request body
1934
+
1935
+ ```tsx
1936
+ function Basic() {
1937
+ const [q, setQ] = useState("");
1938
+ return (
1939
+ <div className="flex w-560 flex-col gap-12">
1940
+ <SearchInput aria-label="Search JSON" placeholder="Search keys and values" value={q} onChange={(e) => setQ(e.target.value)} onClear={() => setQ("")} />
1941
+ <JsonView value={body} query={q || undefined} />
1942
+ </div>
1943
+ );
1944
+ }
1945
+ ```
1946
+
1947
+ ## Kbd
1948
+
1949
+ Key caps and shortcuts: ⌘ ⌥ ⇧ ⌃ on macOS, words elsewhere; sequences like G then M.
1950
+
1951
+ `import { Kbd, KbdSequence } from "@logicplanes/kaeso-ui";`
1952
+
1953
+ ### Accessibility
1954
+
1955
+ - Modifier symbols carry an aria-label with the key's name.
1956
+
1957
+ ### Examples
1958
+
1959
+ #### Keys
1960
+
1961
+ ```tsx
1962
+ <div className="flex items-center gap-8">
1963
+ <Kbd>mod</Kbd>
1964
+ <Kbd>shift</Kbd>
1965
+ <Kbd>alt</Kbd>
1966
+ <Kbd>enter</Kbd>
1967
+ <Kbd>esc</Kbd>
1968
+ <Kbd>k</Kbd>
1969
+ </div>
1970
+ ```
1971
+
1972
+ #### Sequences
1973
+
1974
+ ```tsx
1975
+ <div className="flex flex-col items-start gap-12">
1976
+ <KbdSequence keys={["mod", "k"]} />
1977
+ <KbdSequence keys={[["g"], ["l"]]} />
1978
+ </div>
1979
+ ```
1980
+
1981
+ ## KeyDisplay
1982
+
1983
+ Kaeso's identities: a redacted key with its state, a model with its provider and capabilities, an HTTP status, a request's timing.
1984
+
1985
+ `import { KeyDisplay, ModelLabel, ProviderBadge, ProviderCard, StatusCode, RequestTimeline } from "@logicplanes/kaeso-ui";`
1986
+
1987
+ ### Accessibility
1988
+
1989
+ - Providers show their mark in the text color, so it follows the theme; others a letter. The name is always next to it or its accessible label.
1990
+ - Capability icons have tooltips and are summarized in an accessible label.
1991
+ - Status codes carry their phrase for screen readers.
1992
+
1993
+ ### Examples
1994
+
1995
+ #### KeyDisplay
1996
+
1997
+ ```tsx
1998
+ <div className="flex flex-col gap-12">
1999
+ {keys.slice(0, 4).map((k) => (
2000
+ <KeyDisplay key={k.id} redacted={k.redacted} state={k.state} access={k.access} />
2001
+ ))}
2002
+ <KeyDisplay redacted="sk-kso-Rt82Vn…3c1d" access="both" />
2003
+ </div>
2004
+ ```
2005
+
2006
+ #### ModelLabel and ProviderBadge
2007
+
2008
+ ```tsx
2009
+ <div className="flex flex-col gap-12">
2010
+ {models.map((m) => (
2011
+ <ModelLabel key={m.id} provider={m.provider} model={m.model} name={m.name} capabilities={m.capabilities} />
2012
+ ))}
2013
+ <div className="flex gap-12">
2014
+ <ProviderBadge provider="openai" withName />
2015
+ <ProviderBadge provider="anthropic" withName />
2016
+ </div>
2017
+ </div>
2018
+ ```
2019
+
2020
+ #### StatusCode and RequestTimeline
2021
+
2022
+ ```tsx
2023
+ <div className="flex flex-col gap-16">
2024
+ <div className="flex gap-16">
2025
+ <StatusCode code={200} />
2026
+ <StatusCode code={429} />
2027
+ <StatusCode code={502} showText />
2028
+ </div>
2029
+ <RequestTimeline ttfbMs={412} totalMs={1840} />
2030
+ </div>
2031
+ ```
2032
+
2033
+ #### ProviderCard
2034
+
2035
+ ```tsx
2036
+ <div className="grid w-full grid-cols-1 gap-16 md:grid-cols-2">
2037
+ <ProviderCard
2038
+ provider="openai"
2039
+ connected
2040
+ status="The organization's key"
2041
+ keyLabel="Production …4f2a"
2042
+ detail="Last used 2 min ago"
2043
+ actions={<Button size="sm">Use another key here</Button>}
2044
+ />
2045
+ <ProviderCard
2046
+ provider="anthropic"
2047
+ connected={false}
2048
+ status="No key yet: requests to Anthropic fail"
2049
+ actions={
2050
+ <Button size="sm" variant="primary">
2051
+ Connect Anthropic
2052
+ </Button>
2053
+ }
2054
+ />
2055
+ </div>
2056
+ ```
2057
+
2058
+ ## KeyValue
2059
+
2060
+ Label and value pairs for sheets: 13 secondary labels, 14 values, copyable, in columns where it's wide.
2061
+
2062
+ `import { KeyValue } from "@logicplanes/kaeso-ui";`
2063
+
2064
+ ### Examples
2065
+
2066
+ #### Basic
2067
+
2068
+ ```tsx
2069
+ <KeyValue items={items} className="w-400" />
2070
+ ```
2071
+
2072
+ #### Long values that wrap
2073
+
2074
+ ```tsx
2075
+ <KeyValue
2076
+ className="w-400"
2077
+ items={[
2078
+ { label: "Error", value: "Rate limited" },
2079
+ { label: "Message", value: "Rate limit reached: the project's request rate of 600 requests per minute. Retry in 50 seconds.", wrap: true },
2080
+ { label: "Request id", value: "req_01JAR4X2KQ", mono: true, copy: "req_01JAR4X2KQ" },
2081
+ ]}
2082
+ />
2083
+ ```
2084
+
2085
+ #### Grid: label above value, two or more to a row
2086
+
2087
+ ```tsx
2088
+ <div className="flex w-full max-w-720 flex-col gap-24">
2089
+ <KeyValue layout="grid" items={route} />
2090
+ <KeyValue layout="grid" items={sender} />
2091
+ <KeyValue layout="grid" columns={4} items={stream} />
2092
+ </div>
2093
+ ```
2094
+
2095
+ ## Label
2096
+
2097
+ The name of a field: 14/500, with an optional “Optional”.
2098
+
2099
+ `import { Label } from "@logicplanes/kaeso-ui";`
2100
+
2101
+ ### Accessibility
2102
+
2103
+ - Connect it to its control with htmlFor; Field does this for you.
2104
+
2105
+ ### Examples
2106
+
2107
+ #### Basic
2108
+
2109
+ ```tsx
2110
+ <div className="flex w-320 flex-col gap-8">
2111
+ <Label htmlFor="name">Key name</Label>
2112
+ <Input id="name" placeholder="checkout-web" />
2113
+ </div>
2114
+ ```
2115
+
2116
+ #### Optional
2117
+
2118
+ ```tsx
2119
+ <Label optional>Description</Label>
2120
+ ```
2121
+
2122
+ ## LayerDialog
2123
+
2124
+ A second layer slides over the first (add model → review values); Back returns.
2125
+
2126
+ `import { LayerDialog } from "@logicplanes/kaeso-ui";`
2127
+
2128
+ ### Keyboard
2129
+
2130
+ | Key | Action |
2131
+ |---|---|
2132
+ | Escape | Closes and returns focus to the trigger |
2133
+ | Tab | Moves within the layer shown |
2134
+ | mod Enter | Calls onSubmit, on every layer |
2135
+
2136
+ ### Accessibility
2137
+
2138
+ - The layers below the one shown stay mounted but hidden: they keep their state, and the focus never reaches them.
2139
+ - Moving to a layer puts the focus in it by the dialog's rule: the control marked data-autofocus, the first field, the primary or safe action, the first control, then Close. Never on the dialog box or the page, so ⌘/Ctrl+Enter keeps reaching onSubmit.
2140
+ - Back returns the focus to the control that opened the layer, such as the row or the button, and the layer below to where it was scrolled; when that control is gone, the focus goes to the first control of the layer shown by the same rule.
2141
+ - A new layer starts at its top. The title and description follow the layer shown and name the dialog.
2142
+
2143
+ ### Examples
2144
+
2145
+ #### Add a model
2146
+
2147
+ ```tsx
2148
+ function Wizard() {
2149
+ const [open, setOpen] = useState(false);
2150
+ const [view, setView] = useState<"pick" | "review" | "custom">("pick");
2151
+ const [model, setModel] = useState("");
2152
+ const [custom, setCustom] = useState("");
2153
+ const m = models.find((x) => x.model === model);
2154
+ const close = () => {
2155
+ setOpen(false);
2156
+ setView("pick");
2157
+ setCustom("");
2158
+ };
2159
+ const add = (id: string) => {
2160
+ close();
2161
+ toast.success(`Added ${id}`);
2162
+ };
2163
+ const addCustom = () => custom.trim() && add(custom.trim());
2164
+ const pick = {
2165
+ title: "Add model",
2166
+ description: "Pick a model from the models.dev catalogue.",
2167
+ content: (
2168
+ <div className="flex flex-col gap-12">
2169
+ <List
2170
+ aria-label="Models on models.dev"
2171
+ density="compact"
2172
+ items={models.map((x) => ({ id: x.model, textValue: x.model, title: <ModelLabel provider={x.provider} model={x.model} name={x.name} /> }))}
2173
+ onOpen={(item) => {
2174
+ setModel(item.id);
2175
+ setView("review");
2176
+ }}
2177
+ />
2178
+ <Text variant="small" tone="secondary">
2179
+ Not on models.dev?{" "}
2180
+ <Button variant="link" size="sm" onClick={() => setView("custom")}>
2181
+ Add a custom model
2182
+ </Button>
2183
+ </Text>
2184
+ </div>
2185
+ ),
2186
+ };
2187
+ const second = {
2188
+ review: {
2189
+ title: `Review ${model}`,
2190
+ description: "Prices come from models.dev; you can override them.",
2191
+ content: m && (
2192
+ <div className="flex flex-col gap-16">
2193
+ <ModelLabel provider={m.provider} model={m.model} name={m.name} capabilities={m.capabilities} />
2194
+ <KeyValue
2195
+ items={[
2196
+ { label: "Input", value: `$${m.input} / M` },
2197
+ { label: "Output", value: `$${m.output} / M` },
2198
+ { label: "Context", value: `${m.context.toLocaleString("en-US")} tokens` },
2199
+ ]}
2200
+ />
2201
+ </div>
2202
+ ),
2203
+ footer: (
2204
+ <Button variant="primary" onClick={() => add(model)}>
2205
+ Add model
2206
+ </Button>
2207
+ ),
2208
+ },
2209
+ custom: {
2210
+ title: "Add a custom model",
2211
+ description: "The id the provider knows the model by.",
2212
+ content: <Input label="Model id" placeholder="acme-internal-1" value={custom} onChange={(e) => setCustom(e.target.value)} />,
2213
+ footer: (
2214
+ <Button variant="primary" disabled={!custom.trim()} onClick={addCustom}>
2215
+ Add model
2216
+ </Button>
2217
+ ),
2218
+ },
2219
+ };
2220
+ return (
2221
+ <>
2222
+ <Button variant="primary" onClick={() => setOpen(true)}>
2223
+ Add model
2224
+ </Button>
2225
+ <LayerDialog
2226
+ open={open}
2227
+ onOpenChange={(o) => !o && close()}
2228
+ layer={view === "pick" ? 0 : 1}
2229
+ onLayerChange={(l) => l === 0 && setView("pick")}
2230
+ onSubmit={view === "review" ? () => add(model) : view === "custom" ? addCustom : undefined}
2231
+ layers={view === "pick" ? [pick] : [pick, second[view]]}
2232
+ />
2233
+ </>
2234
+ );
2235
+ }
2236
+ ```
2237
+
2238
+ ## Link
2239
+
2240
+ Links look like links: text color with a quiet underline that turns solid on hover.
2241
+
2242
+ `import { Link } from "@logicplanes/kaeso-ui";`
2243
+
2244
+ ### Keyboard
2245
+
2246
+ | Key | Action |
2247
+ |---|---|
2248
+ | Enter | Follows the link |
2249
+
2250
+ ### Accessibility
2251
+
2252
+ - External links open in a new tab, add rel="noopener noreferrer" and say so to screen readers.
2253
+ - Router links come from LinkProvider.
2254
+
2255
+ ### Examples
2256
+
2257
+ #### Inline
2258
+
2259
+ ```tsx
2260
+ <p className="max-w-prose text-body text-fg-secondary">
2261
+ Applications authenticate with a key. <Link href="/components/button">Read how keys work</Link> before you create one.
2262
+ </p>
2263
+ ```
2264
+
2265
+ #### External
2266
+
2267
+ ```tsx
2268
+ <Link href="https://github.com/logicplanes/kaeso">Read the gateway setup guide</Link>
2269
+ ```
2270
+
2271
+ #### Variants
2272
+
2273
+ ```tsx
2274
+ <div className="flex items-center gap-24">
2275
+ <Link href="#">Inline</Link>
2276
+ <Link href="#" variant="quiet">
2277
+ Quiet
2278
+ </Link>
2279
+ <Link href="#" variant="standalone">
2280
+ Team overview ›
2281
+ </Link>
2282
+ </div>
2283
+ ```
2284
+
2285
+ ## List
2286
+
2287
+ 44px rows on the panel, no lines, metadata on the right, group headers, keyboard first.
2288
+
2289
+ `import { List, ListRow, GroupHeader } from "@logicplanes/kaeso-ui";`
2290
+
2291
+ ### Keyboard
2292
+
2293
+ | Key | Action |
2294
+ |---|---|
2295
+ | Tab | Enters the list on the last focused row, and the next Tab leaves it |
2296
+ | Up Down | Moves between rows and group headers (also j k) |
2297
+ | Home End | Moves to the first or the last row |
2298
+ | Space | Selects or unselects the row (also x) |
2299
+ | Enter | Opens the row; on a group header, collapses or expands the group |
2300
+ | Right Left | Moves into the row's actions and back |
2301
+ | Escape | Returns from an action to its row; on a row, clears the selection |
2302
+ | shift Up Down | Extends the selection |
2303
+ | mod A | Selects all |
2304
+ | mod Enter | Left to the surrounding form, such as a dialog's submit |
2305
+
2306
+ ### Accessibility
2307
+
2308
+ - An ARIA grid of one-cell rows with one tab stop: focus roves between rows and group headers, and checkboxes and row actions stay out of the tab order.
2309
+ - Selection is aria-selected on the cell and only present when the list is selectable; disabled rows can be focused but not opened or selected.
2310
+ - Row actions are reached with the right arrow and appear while their row has focus.
2311
+ - While loading, the grid is aria-busy and shows rows shaped like items.
2312
+ - Lists over 100 rows are virtualized.
2313
+
2314
+ ### Examples
2315
+
2316
+ #### Keys by state
2317
+
2318
+ ```tsx
2319
+ <div className="w-full">
2320
+ <List
2321
+ aria-label="Keys"
2322
+ groups={groups}
2323
+ selectable
2324
+ selectionActions={
2325
+ <>
2326
+ <SelectionAction icon={icons.copy}>Copy ids</SelectionAction>
2327
+ <SelectionAction icon={icons.hide}>Disable</SelectionAction>
2328
+ <SelectionAction icon={icons.delete} danger>
2329
+ Delete
2330
+ </SelectionAction>
2331
+ </>
2332
+ }
2333
+ />
2334
+ </div>
2335
+ ```
2336
+
2337
+ #### In a settings column
2338
+
2339
+ ```tsx
2340
+ <Column>
2341
+ <List aria-label="Members" items={memberRows} />
2342
+ </Column>
2343
+ ```
2344
+
2345
+ ## Loader
2346
+
2347
+ Kaeso's spinner: three routes on a ring, the lead in full color, advancing a third of a turn per 800ms beat. In controls it shows only once work has taken 300ms, then for at least 400ms.
2348
+
2349
+ `import { Loader, Spinner, useDelayedLoading } from "@logicplanes/kaeso-ui";`
2350
+
2351
+ ### Accessibility
2352
+
2353
+ - Loader is a status region: its label, or “Loading”, is announced when it shows; while it waits out its delay it keeps its place and is hidden from assistive technology.
2354
+ - A Spinner inside a busy control stays silent: the control says it is busy (aria-busy) at once.
2355
+ - With reduced motion the ring holds still and the light passes from route to route by opacity alone; nothing turns or flashes.
2356
+
2357
+ ### Examples
2358
+
2359
+ #### Inline
2360
+
2361
+ A status with a label, in secondary text.
2362
+
2363
+ ```tsx
2364
+ <Loader label="Saving provider…" />
2365
+ ```
2366
+
2367
+ #### Sizes
2368
+
2369
+ 14 and 16px in controls and toasts, 20px in xl buttons, 24px for a region, 32px for a page. The stroke is 1.5px at every size, like an icon's at 24px.
2370
+
2371
+ ```tsx
2372
+ <div className="flex items-center gap-16 text-fg">
2373
+ <Spinner size="xs" />
2374
+ <Spinner size="sm" />
2375
+ <Spinner size="md" />
2376
+ <Spinner size="lg" />
2377
+ <Spinner size="xl" />
2378
+ </div>
2379
+ ```
2380
+
2381
+ #### Delay and minimum
2382
+
2383
+ Quick work shows nothing; slower work shows the spinner after 300ms and keeps it at least 400ms, so it never flashes or flickers. Buttons do this by themselves.
2384
+
2385
+ ```tsx
2386
+ function Delayed() {
2387
+ const [quick, setQuick] = useState(false);
2388
+ const [slow, setSlow] = useState(false);
2389
+ return (
2390
+ <Inline gap={12}>
2391
+ <Button
2392
+ loading={quick}
2393
+ onClick={() => {
2394
+ setQuick(true);
2395
+ setTimeout(() => setQuick(false), 150);
2396
+ }}
2397
+ >
2398
+ Quick save (150ms)
2399
+ </Button>
2400
+ <Button
2401
+ variant="primary"
2402
+ loading={slow}
2403
+ onClick={() => {
2404
+ setSlow(true);
2405
+ setTimeout(() => setSlow(false), 1800);
2406
+ }}
2407
+ >
2408
+ Slow save (1.8s)
2409
+ </Button>
2410
+ </Inline>
2411
+ );
2412
+ }
2413
+ ```
2414
+
2415
+ #### A page loading
2416
+
2417
+ The quietest tone at 32px, after the delay.
2418
+
2419
+ ```tsx
2420
+ <div className="flex h-160 w-full items-center justify-center rounded-well bg-fill-1">
2421
+ <Loader size="xl" tone="tertiary" delay />
2422
+ </div>
2423
+ ```
2424
+
2425
+ ## Logo
2426
+
2427
+ The Kaeso wordmark, mark and lockup as React SVG in currentColor. Interfaces show the wordmark alone; the mark is for favicons, app icons and avatars, the lockup for brand artwork.
2428
+
2429
+ `import { Logo } from "@logicplanes/kaeso-ui";`
2430
+
2431
+ ### Accessibility
2432
+
2433
+ - The accessible name is “Kaeso”. Inside a link, name the link (“Kaeso home”) and pass decorative.
2434
+ - A linked logo needs a name for the destination, such as “Kaeso home”.
2435
+
2436
+ ### Examples
2437
+
2438
+ #### Wordmark: every interface, top left
2439
+
2440
+ ```tsx
2441
+ <Logo.Wordmark size="nav" />
2442
+ ```
2443
+
2444
+ #### Mark: favicons, app icons, avatars
2445
+
2446
+ ```tsx
2447
+ <div className="flex items-end gap-24">
2448
+ {(["sm", "md", "lg", "xl"] as const).map((s) => (
2449
+ <Logo.Mark key={s} size={s} />
2450
+ ))}
2451
+ <Logo.Icon size="lg" />
2452
+ </div>
2453
+ ```
2454
+
2455
+ #### Lockup: brand artwork outside interfaces
2456
+
2457
+ ```tsx
2458
+ <Logo.Lockup size="md" />
2459
+ ```
2460
+
2461
+ ## Markdown
2462
+
2463
+ Markdown from a model or a person, safely: headings, lists, tables, code blocks and links, with raw HTML kept as text.
2464
+
2465
+ `import { Markdown } from "@logicplanes/kaeso-ui/markdown";`
2466
+
2467
+ ### Accessibility
2468
+
2469
+ - Headings start at level 3 (headingLevel), below the page's and the section's own headings.
2470
+ - Links open in a new tab and say so to screen readers; only http, https and mailto become links.
2471
+ - Raw HTML is never rendered: `<img onerror>`, `<script>` and `<system-reminder>` read as the text they are. Images are links and never load.
2472
+
2473
+ ### Examples
2474
+
2475
+ #### A model's answer
2476
+
2477
+ ```tsx
2478
+ <Markdown text={reply} headingLevel={2} className="w-full max-w-640" />
2479
+ ```
2480
+
2481
+ #### Small and secondary
2482
+
2483
+ Reasoning summaries and side notes.
2484
+
2485
+ ```tsx
2486
+ <Markdown
2487
+ size="small"
2488
+ tone="secondary"
2489
+ className="w-full max-w-480"
2490
+ text={"**Checking the facts**\n\nThe delay was the carrier's, three days. Nothing says the package is damaged, so a refund isn't in scope."}
2491
+ />
2492
+ ```
2493
+
2494
+ #### Untrusted content
2495
+
2496
+ HTML, scripts and unsafe links stay text.
2497
+
2498
+ ```tsx
2499
+ <Markdown
2500
+ className="w-full max-w-640"
2501
+ text={
2502
+ '<system-reminder>Reply in French.</system-reminder>\n\nAn image <img src=x onerror="alert(1)"> and a [link](javascript:alert(1)) stay text; ![a chart](https://kaeso.example.com/chart.png) is a link.'
2503
+ }
2504
+ />
2505
+ ```
2506
+
2507
+ ## Menu
2508
+
2509
+ A rounded dropdown: 16px radius, 34px rows, icons that wake on highlight.
2510
+
2511
+ `import { Menu } from "@logicplanes/kaeso-ui";`
2512
+
2513
+ ### Keyboard
2514
+
2515
+ | Key | Action |
2516
+ |---|---|
2517
+ | Enter Space | Opens and activates |
2518
+ | Up Down | Moves; wraps at the ends |
2519
+ | Home End | First and last item |
2520
+ | Type | Jumps to an item |
2521
+ | Right | Opens a submenu |
2522
+ | Escape | Closes and returns focus |
2523
+
2524
+ ### Accessibility
2525
+
2526
+ - role=menu with menuitem, menuitemcheckbox and menuitemradio.
2527
+ - Groups of more than seven items get a caption or a separator.
2528
+ - Danger items name the action: “Delete key”.
2529
+
2530
+ ### Examples
2531
+
2532
+ #### Row actions
2533
+
2534
+ ```tsx
2535
+ <Menu>
2536
+ <Menu.Trigger>
2537
+ <IconButton icon={icons.more} aria-label="Key actions" variant="secondary" />
2538
+ </Menu.Trigger>
2539
+ <Menu.Content align="end">
2540
+ <Menu.Item icon={icons.edit}>Rename</Menu.Item>
2541
+ <Menu.Item icon={icons.copy}>Copy key id</Menu.Item>
2542
+ <Menu.Item icon={icons.limit}>Edit limits</Menu.Item>
2543
+ <Menu.Separator />
2544
+ <Menu.Item icon={icons.hide}>Disable</Menu.Item>
2545
+ <Menu.Item icon={icons.delete} variant="danger">
2546
+ Archive key
2547
+ </Menu.Item>
2548
+ </Menu.Content>
2549
+ </Menu>
2550
+ ```
2551
+
2552
+ #### Account menu
2553
+
2554
+ ```tsx
2555
+ <Menu>
2556
+ <Menu.Trigger>
2557
+ <Button variant="ghost" leadingIcon={icons.member}>
2558
+ Ada Okafor
2559
+ </Button>
2560
+ </Menu.Trigger>
2561
+ <Menu.Content
2562
+ header={<Menu.Header avatar={<Avatar name="Ada Okafor" size={40} decorative />} name="Ada Okafor" email="ada@northwind.example" />}
2563
+ footer="Kaeso 0.4.0 · self-hosted"
2564
+ >
2565
+ <Menu.Separator />
2566
+ <Menu.Item icon={icons.settings}>Account settings</Menu.Item>
2567
+ <Menu.LinkItem icon={icons.help} href="https://github.com/logicplanes/kaeso" external>
2568
+ Documentation
2569
+ </Menu.LinkItem>
2570
+ <Menu.Separator />
2571
+ <Menu.Item icon={icons.signOut}>Sign out</Menu.Item>
2572
+ </Menu.Content>
2573
+ </Menu>
2574
+ ```
2575
+
2576
+ #### Checkbox and radio items
2577
+
2578
+ ```tsx
2579
+ <Menu>
2580
+ <Menu.Trigger>
2581
+ <Button trailingIcon={icons.chevronDown}>Columns</Button>
2582
+ </Menu.Trigger>
2583
+ <Menu.Content>
2584
+ <Menu.Group label="Show">
2585
+ <Menu.CheckboxItem defaultChecked>Model</Menu.CheckboxItem>
2586
+ <Menu.CheckboxItem defaultChecked>Latency</Menu.CheckboxItem>
2587
+ <Menu.CheckboxItem>Provider</Menu.CheckboxItem>
2588
+ </Menu.Group>
2589
+ <Menu.Separator />
2590
+ <Menu.Group label="Density">
2591
+ <Menu.RadioGroup defaultValue="default">
2592
+ <Menu.RadioItem value="compact">Compact</Menu.RadioItem>
2593
+ <Menu.RadioItem value="default">Default</Menu.RadioItem>
2594
+ <Menu.RadioItem value="comfortable">Comfortable</Menu.RadioItem>
2595
+ </Menu.RadioGroup>
2596
+ </Menu.Group>
2597
+ </Menu.Content>
2598
+ </Menu>
2599
+ ```
2600
+
2601
+ #### Submenu
2602
+
2603
+ ```tsx
2604
+ <Menu>
2605
+ <Menu.Trigger>
2606
+ <Button>Move to project</Button>
2607
+ </Menu.Trigger>
2608
+ <Menu.Content>
2609
+ <Menu.Item icon={icons.project}>Checkout assistant</Menu.Item>
2610
+ <Menu.Sub>
2611
+ <Menu.SubTrigger icon={icons.organization}>Other organizations</Menu.SubTrigger>
2612
+ <Menu.SubContent>
2613
+ <Menu.Item>Contoso</Menu.Item>
2614
+ <Menu.Item>Fabrikam</Menu.Item>
2615
+ </Menu.SubContent>
2616
+ </Menu.Sub>
2617
+ </Menu.Content>
2618
+ </Menu>
2619
+ ```
2620
+
2621
+ ## Menubar
2622
+
2623
+ A row of ghost pills, each opening a menu; arrows move between them.
2624
+
2625
+ `import { Menubar } from "@logicplanes/kaeso-ui";`
2626
+
2627
+ ### Keyboard
2628
+
2629
+ | Key | Action |
2630
+ |---|---|
2631
+ | Left Right | Moves between menus |
2632
+ | Down | Opens the menu |
2633
+
2634
+ ### Examples
2635
+
2636
+ #### Basic
2637
+
2638
+ ```tsx
2639
+ <Menubar aria-label="Request">
2640
+ <Menubar.Menu>
2641
+ <Menubar.Trigger>Request</Menubar.Trigger>
2642
+ <Menubar.Content>
2643
+ <Menubar.Item icon={icons.copy}>Copy as curl</Menubar.Item>
2644
+ <Menubar.Item icon={icons.download}>Download JSON</Menubar.Item>
2645
+ </Menubar.Content>
2646
+ </Menubar.Menu>
2647
+ <Menubar.Menu>
2648
+ <Menubar.Trigger>View</Menubar.Trigger>
2649
+ <Menubar.Content>
2650
+ <Menubar.CheckboxItem defaultChecked>Conversation</Menubar.CheckboxItem>
2651
+ <Menubar.CheckboxItem>Raw JSON</Menubar.CheckboxItem>
2652
+ </Menubar.Content>
2653
+ </Menubar.Menu>
2654
+ </Menubar>
2655
+ ```
2656
+
2657
+ ## Meter
2658
+
2659
+ A limit or quota: label and value, a 6px pill track, threshold ticks and a caption. Progress for work.
2660
+
2661
+ `import { Meter, Progress } from "@logicplanes/kaeso-ui";`
2662
+
2663
+ ### Accessibility
2664
+
2665
+ - role=meter with the value as text (“$32.10 of $50.00”).
2666
+ - Color supports the value and the caption; it never replaces them.
2667
+
2668
+ ### Examples
2669
+
2670
+ #### Budget
2671
+
2672
+ ```tsx
2673
+ <Meter className="w-400" label="Monthly budget" value={0.642} valueLabel="$32.10 of $50.00" thresholds={[0.8]} caption="Resets in 12 days" />
2674
+ ```
2675
+
2676
+ #### Progress
2677
+
2678
+ ```tsx
2679
+ <div className="flex w-400 flex-col gap-16">
2680
+ <Progress aria-label="Export" value={0.38} />
2681
+ <Progress aria-label="Loading logs" />
2682
+ </div>
2683
+ ```
2684
+
2685
+ ## ModelPicker
2686
+
2687
+ A model from the project's catalogue: provider mark, name and id, grouped by provider. The start of a comparison column's header.
2688
+
2689
+ `import { ModelPicker } from "@logicplanes/kaeso-ui/playground";`
2690
+
2691
+ ### Keyboard
2692
+
2693
+ | Key | Action |
2694
+ |---|---|
2695
+ | Enter Space | Opens the list |
2696
+ | Up Down | Moves through models |
2697
+ | A–Z | Jumps to a model by name |
2698
+
2699
+ ### Accessibility
2700
+
2701
+ - A select named “Model” by default; each option reads the model's name.
2702
+
2703
+ ### Examples
2704
+
2705
+ #### In a column header
2706
+
2707
+ ```tsx
2708
+ function Basic() {
2709
+ const [model, setModel] = useState("claude-sonnet-4-5");
2710
+ return (
2711
+ <div className="flex h-52 w-full max-w-360 items-center pl-8 hairline-b">
2712
+ <ModelPicker value={model} onValueChange={setModel} options={playgroundModels} />
2713
+ </div>
2714
+ );
2715
+ }
2716
+ ```
2717
+
2718
+ #### Raised
2719
+
2720
+ ```tsx
2721
+ <ModelPicker appearance="default" size="md" defaultValue="gpt-5" options={playgroundModels} />
2722
+ ```
2723
+
2724
+ ## NumberInput
2725
+
2726
+ Exact numbers: right-aligned tabular digits, a unit, steppers on hover, a decimal string value.
2727
+
2728
+ `import { NumberInput } from "@logicplanes/kaeso-ui";`
2729
+
2730
+ ### Keyboard
2731
+
2732
+ | Key | Action |
2733
+ |---|---|
2734
+ | Up | Increases by the step |
2735
+ | Down | Decreases by the step |
2736
+
2737
+ ### Accessibility
2738
+
2739
+ - The value is a decimal string, so money never passes through a float.
2740
+ - Thousands separators appear on blur and are removed while editing.
2741
+
2742
+ ### Examples
2743
+
2744
+ #### Money
2745
+
2746
+ ```tsx
2747
+ function Money() {
2748
+ const [v, setV] = useState("1250.00");
2749
+ return (
2750
+ <div className="flex w-280 flex-col gap-8">
2751
+ <NumberInput label="Monthly budget" value={v} onValueChange={setV} unit="USD" step="50" min="0" />
2752
+ <span className="mono text-small text-fg-secondary">value = "{v}"</span>
2753
+ </div>
2754
+ );
2755
+ }
2756
+ ```
2757
+
2758
+ #### Rate
2759
+
2760
+ ```tsx
2761
+ <div className="w-240">
2762
+ <NumberInput aria-label="Requests per minute" defaultValue="600" unit="/ min" step="10" />
2763
+ </div>
2764
+ ```
2765
+
2766
+ ## OTPInput
2767
+
2768
+ Six cells for a TOTP code over one accessible input: paste fills, Backspace moves back.
2769
+
2770
+ `import { OTPInput } from "@logicplanes/kaeso-ui";`
2771
+
2772
+ ### Accessibility
2773
+
2774
+ - One real input with autocomplete="one-time-code", so the browser can offer the code.
2775
+ - Cells are decorative; the input carries the label.
2776
+
2777
+ ### Examples
2778
+
2779
+ #### Basic
2780
+
2781
+ ```tsx
2782
+ <Field label="Code from your authenticator app">
2783
+ <OTPInput />
2784
+ </Field>
2785
+ ```
2786
+
2787
+ ## Page patterns
2788
+
2789
+ List, table, overview and empty pages, composed from the library so the console never invents layout.
2790
+
2791
+ `import { ListPage, TablePage, OverviewPage, EmptyPage, AuthPage } from "@logicplanes/kaeso-ui";`
2792
+
2793
+ ### Examples
2794
+
2795
+ #### Overview
2796
+
2797
+ ```tsx
2798
+ <Frame>
2799
+ <OverviewPage
2800
+ header={{ title: "Overview" }}
2801
+ metrics={
2802
+ <MetricRow>
2803
+ <Stat value="12,480" unit="requests" />
2804
+ <Stat value="$184.20" unit="total spend" />
2805
+ </MetricRow>
2806
+ }
2807
+ controls={
2808
+ <Button size="sm" trailingIcon={icons.chevronDown}>
2809
+ Last 30 days
2810
+ </Button>
2811
+ }
2812
+ chart={<TimeseriesChart aria-label="Spend" series={usageSeries().slice(0, 2)} height={200} formatValue={(v) => formatUSD(v.toFixed(2))} />}
2813
+ />
2814
+ </Frame>
2815
+ ```
2816
+
2817
+ #### List page
2818
+
2819
+ ```tsx
2820
+ <Frame>
2821
+ <ListPage
2822
+ header={{
2823
+ title: "Keys",
2824
+ actions: (
2825
+ <Button size="sm" variant="primary" leadingIcon={icons.add}>
2826
+ Create key
2827
+ </Button>
2828
+ ),
2829
+ }}
2830
+ toolbar={
2831
+ <Toolbar aria-label="Filters">
2832
+ <Toolbar.Spacer />
2833
+ <div className="w-240">
2834
+ <SearchInput aria-label="Search keys" />
2835
+ </div>
2836
+ </Toolbar>
2837
+ }
2838
+ >
2839
+ <List aria-label="Keys" items={keys.map((k) => ({ id: k.id, title: k.name, subtitle: k.redacted }))} />
2840
+ </ListPage>
2841
+ </Frame>
2842
+ ```
2843
+
2844
+ #### Empty page
2845
+
2846
+ ```tsx
2847
+ <Frame>
2848
+ <EmptyPage
2849
+ header={{ title: "Models" }}
2850
+ title="No models yet"
2851
+ description="Add a model before creating keys."
2852
+ actions={<Button variant="primary">Add model</Button>}
2853
+ />
2854
+ </Frame>
2855
+ ```
2856
+
2857
+ ## PageHeader
2858
+
2859
+ The 52px header: the page title, pill tabs, at most one ink button and round actions.
2860
+
2861
+ `import { PageHeader } from "@logicplanes/kaeso-ui";`
2862
+
2863
+ ### Examples
2864
+
2865
+ #### Title, tabs and actions
2866
+
2867
+ ```tsx
2868
+ <div className="w-full rounded-well shadow-frame">
2869
+ <PageHeader title="Request logs" icon={icons.requestLog} tabs={tabs} actions={actions} />
2870
+ </div>
2871
+ ```
2872
+
2873
+ #### A title and a primary action
2874
+
2875
+ ```tsx
2876
+ <div className="w-full rounded-well shadow-frame">
2877
+ <PageHeader
2878
+ title="Keys"
2879
+ icon={icons.key}
2880
+ actions={
2881
+ <Button variant="primary" size="sm" leadingIcon={icons.add}>
2882
+ Create key
2883
+ </Button>
2884
+ }
2885
+ />
2886
+ </div>
2887
+ ```
2888
+
2889
+ ## Pagination
2890
+
2891
+ Cursor-style paging for the console's APIs: “Load more”, or previous and next.
2892
+
2893
+ `import { Pagination } from "@logicplanes/kaeso-ui";`
2894
+
2895
+ ### Examples
2896
+
2897
+ #### Load more
2898
+
2899
+ ```tsx
2900
+ function LoadMore() {
2901
+ const [n, setN] = useState(50);
2902
+ return <Pagination hasMore={n < 200} onLoadMore={() => setN((x) => Math.min(200, x + 50))} />;
2903
+ }
2904
+ ```
2905
+
2906
+ #### Previous and next
2907
+
2908
+ ```tsx
2909
+ <Pagination variant="cursor" hasMore hasPrevious={false} />
2910
+ ```
2911
+
2912
+ #### Page numbers
2913
+
2914
+ ```tsx
2915
+ function Pages() {
2916
+ const [p, setP] = useState(4);
2917
+ return <Pagination variant="pages" page={p} pageCount={12} onPageChange={setP} />;
2918
+ }
2919
+ ```
2920
+
2921
+ ## ParameterPanel
2922
+
2923
+ A request's parameters: system instructions, temperature, top P, max output tokens, and reasoning for models that reason. Unset is the model's default.
2924
+
2925
+ `import { ParameterPanel, ParameterPopover } from "@logicplanes/kaeso-ui/playground";`
2926
+
2927
+ ### Keyboard
2928
+
2929
+ | Key | Action |
2930
+ |---|---|
2931
+ | Left Right | Moves a slider by a step |
2932
+ | Escape | Closes the popover |
2933
+
2934
+ ### Accessibility
2935
+
2936
+ - Every control has a visible label that names it; the popover holds a form and traps the focus until it closes.
2937
+
2938
+ ### Examples
2939
+
2940
+ #### In a popover
2941
+
2942
+ ```tsx
2943
+ function Basic() {
2944
+ const [value, setValue] = useState<PlaygroundParameters>({ instructions });
2945
+ return (
2946
+ <ParameterPopover
2947
+ value={value}
2948
+ onValueChange={setValue}
2949
+ reasoning
2950
+ align="start"
2951
+ trigger={
2952
+ <Button size="sm" variant="secondary" leadingIcon={icons.displayOptions}>
2953
+ Parameters
2954
+ </Button>
2955
+ }
2956
+ />
2957
+ );
2958
+ }
2959
+ ```
2960
+
2961
+ #### As a panel
2962
+
2963
+ A model that reasons: effort and summary appear.
2964
+
2965
+ ```tsx
2966
+ function Panel() {
2967
+ const [value, setValue] = useState<PlaygroundParameters>({ instructions, temperature: 0.4 });
2968
+ return (
2969
+ <ParameterPanel
2970
+ layout="panel"
2971
+ className="max-w-320"
2972
+ value={value}
2973
+ onValueChange={setValue}
2974
+ reasoning={["minimal", "low", "medium", "high"]}
2975
+ maxOutputTokensLimit={128000}
2976
+ />
2977
+ );
2978
+ }
2979
+ ```
2980
+
2981
+ ## Popover
2982
+
2983
+ A floating panel next to its trigger; HoverCard previews on hover, never as the only way in.
2984
+
2985
+ `import { Popover, HoverCard } from "@logicplanes/kaeso-ui";`
2986
+
2987
+ ### Keyboard
2988
+
2989
+ | Key | Action |
2990
+ |---|---|
2991
+ | Escape | Closes and returns focus |
2992
+
2993
+ ### Accessibility
2994
+
2995
+ - Popovers with a form trap focus (trapFocus); others do not.
2996
+
2997
+ ### Examples
2998
+
2999
+ #### With a form
3000
+
3001
+ ```tsx
3002
+ <Popover trapFocus>
3003
+ <Popover.Trigger>
3004
+ <Button>Rename</Button>
3005
+ </Popover.Trigger>
3006
+ <Popover.Content title="Rename key" className="w-320">
3007
+ <div className="flex flex-col gap-12">
3008
+ <Input aria-label="Name" defaultValue="checkout-web" />
3009
+ <div className="flex justify-end gap-8">
3010
+ <Popover.Close>
3011
+ <Button size="sm">Cancel</Button>
3012
+ </Popover.Close>
3013
+ <Button size="sm" variant="primary">
3014
+ Save
3015
+ </Button>
3016
+ </div>
3017
+ </div>
3018
+ </Popover.Content>
3019
+ </Popover>
3020
+ ```
3021
+
3022
+ #### HoverCard
3023
+
3024
+ ```tsx
3025
+ <HoverCard>
3026
+ <HoverCard.Trigger>
3027
+ <a href="#" className="mono text-small text-fg underline decoration-line-strong underline-offset-3">
3028
+ claude-sonnet-4-5
3029
+ </a>
3030
+ </HoverCard.Trigger>
3031
+ <HoverCard.Content>
3032
+ <div className="flex flex-col gap-12">
3033
+ <ModelLabel provider="anthropic" model="claude-sonnet-4-5" capabilities={["tools", "reasoning", "vision", "pdf"]} />
3034
+ <KeyValue
3035
+ items={[
3036
+ { label: "Input", value: "$3.00 / M" },
3037
+ { label: "Output", value: "$15.00 / M" },
3038
+ { label: "Context", value: "200,000 tokens" },
3039
+ ]}
3040
+ />
3041
+ </div>
3042
+ </HoverCard.Content>
3043
+ </HoverCard>
3044
+ ```
3045
+
3046
+ ## PromptSuggestions
3047
+
3048
+ Example prompts before a thread starts: up to three quiet lines that take a fill on hover and go once a prompt is sent.
3049
+
3050
+ `import { PromptSuggestions } from "@logicplanes/kaeso-ui/playground";`
3051
+
3052
+ ### Accessibility
3053
+
3054
+ - A list named “Example prompts”; each suggestion is a button that reads its prompt.
3055
+
3056
+ ### Examples
3057
+
3058
+ #### Above the composer
3059
+
3060
+ ```tsx
3061
+ function Basic() {
3062
+ const [draft, setDraft] = useState("");
3063
+ return (
3064
+ <div className="flex w-full max-w-640 flex-col gap-12">
3065
+ {draft === "" && <PromptSuggestions suggestions={suggestions} onSelect={setDraft} />}
3066
+ <Composer value={draft} onValueChange={setDraft} aria-label="Message all models" placeholder="Message 3 models" />
3067
+ </div>
3068
+ );
3069
+ }
3070
+ ```
3071
+
3072
+ ## QRCode
3073
+
3074
+ A scannable code drawn as one SVG path: dark modules on white in both themes, with a quiet zone.
3075
+
3076
+ `import { QRCode } from "@logicplanes/kaeso-ui";`
3077
+
3078
+ ### Accessibility
3079
+
3080
+ - The code is an image with a label that says what it is for; always offer the same value as text next to it.
3081
+
3082
+ ### Examples
3083
+
3084
+ #### Authenticator setup
3085
+
3086
+ ```tsx
3087
+ <QRCode value={uri} label="QR code to add Kaeso to an authenticator app" />
3088
+ ```
3089
+
3090
+ ## Radio
3091
+
3092
+ One of a few choices: an ink donut around a panel center; RadioCards for choices with descriptions.
3093
+
3094
+ `import { Radio, RadioGroup, RadioCards } from "@logicplanes/kaeso-ui";`
3095
+
3096
+ ### Keyboard
3097
+
3098
+ | Key | Action |
3099
+ |---|---|
3100
+ | Up Down | Moves between choices |
3101
+ | Space | Selects |
3102
+
3103
+ ### Accessibility
3104
+
3105
+ - Arrow keys move and select within the group; Tab leaves it.
3106
+
3107
+ ### Examples
3108
+
3109
+ #### Basic
3110
+
3111
+ ```tsx
3112
+ <RadioGroup defaultValue="30" aria-label="Retention">
3113
+ <Radio value="7" label="7 days" />
3114
+ <Radio value="30" label="30 days" />
3115
+ <Radio value="90" label="90 days" />
3116
+ </RadioGroup>
3117
+ ```
3118
+
3119
+ #### RadioCards
3120
+
3121
+ ```tsx
3122
+ <div className="w-480">
3123
+ <RadioCards defaultValue="responses" aria-label="OpenAI dialect">
3124
+ <Radio value="responses" label="Responses" description="The default. Reasoning and tools work best here." />
3125
+ <Radio value="chat" label="Chat Completions" description="For models or proxies that only speak the older API." />
3126
+ </RadioCards>
3127
+ </div>
3128
+ ```
3129
+
3130
+ ## ResourceList
3131
+
3132
+ A searchable list with group headers, row actions and a selection bar.
3133
+
3134
+ `import { ResourceList } from "@logicplanes/kaeso-ui";`
3135
+
3136
+ ### Examples
3137
+
3138
+ #### Keys
3139
+
3140
+ ```tsx
3141
+ <div className="w-full">
3142
+ <ResourceList
3143
+ aria-label="Keys"
3144
+ groups={groups}
3145
+ bulkActions={[{ label: "Disable", icon: icons.hide, onClick: () => undefined }]}
3146
+ emptyAction={<Button variant="primary">Create key</Button>}
3147
+ />
3148
+ </div>
3149
+ ```
3150
+
3151
+ ## ResponseMetrics
3152
+
3153
+ A response's numbers on one line: time to first token, total time, tokens in and out, reasoning, cost. Empty before a response; quiet placeholders hold their place while one streams.
3154
+
3155
+ `import { ResponseMetrics, formatLatency } from "@logicplanes/kaeso-ui/playground";`
3156
+
3157
+ ### Accessibility
3158
+
3159
+ - A description list: each label is its term. Tokens read as “1,204 in, 856 out”.
3160
+
3161
+ ### Examples
3162
+
3163
+ #### A finished response
3164
+
3165
+ ```tsx
3166
+ <ResponseMetrics timeToFirstToken={420} duration={3140} inputTokens={1204} outputTokens={856} reasoningTokens={312} cost="0.009725" />
3167
+ ```
3168
+
3169
+ #### While it streams
3170
+
3171
+ ```tsx
3172
+ <ResponseMetrics live reasoning timeToFirstToken={1180} />
3173
+ ```
3174
+
3175
+ ## ResponseView
3176
+
3177
+ A model's response as it arrives: Markdown with a quiet caret, reasoning in a Thinking disclosure, and calm stopped and failed states.
3178
+
3179
+ `import { ResponseView, UserMessage, Thread } from "@logicplanes/kaeso-ui/playground";`
3180
+
3181
+ ### Keyboard
3182
+
3183
+ | Key | Action |
3184
+ |---|---|
3185
+ | Enter Space | Opens and closes Thinking |
3186
+
3187
+ ### Accessibility
3188
+
3189
+ - Each response is an article named by aria-label (“GPT-5 response” in a comparison) and is aria-busy while it streams, so its words aren't read out one by one.
3190
+ - Thinking is a disclosure button; closed, its row shows the latest line of the summary, hidden from screen readers, which read the summary when opened.
3191
+ - A failure says only “No response” in place: the error itself is a toast.
3192
+
3193
+ ### Examples
3194
+
3195
+ #### Streaming
3196
+
3197
+ Thinking first, then the answer.
3198
+
3199
+ ```tsx
3200
+ function Stream() {
3201
+ const [state, setState] = useState<{ status: ResponseStatus; text: string; reasoning: string; took?: number; started?: number; total?: number }>({
3202
+ status: "done",
3203
+ text: gpt.text,
3204
+ reasoning: gpt.reasoning!,
3205
+ took: 3800,
3206
+ total: 6120,
3207
+ });
3208
+ const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
3209
+ useEffect(() => () => clearTimeout(timer.current), []);
3210
+ const play = () => {
3211
+ clearTimeout(timer.current);
3212
+ const started = Date.now();
3213
+ let r = 0;
3214
+ let t = 0;
3215
+ let took: number | undefined;
3216
+ setState({ status: "pending", text: "", reasoning: "", started });
3217
+ const tick = () => {
3218
+ if (r < gpt.reasoning!.length) r += 8;
3219
+ else {
3220
+ took ??= Date.now() - started;
3221
+ t += 6;
3222
+ }
3223
+ const done = t >= gpt.text.length;
3224
+ setState({
3225
+ status: done ? "done" : "streaming",
3226
+ reasoning: gpt.reasoning!.slice(0, r),
3227
+ text: gpt.text.slice(0, t),
3228
+ started,
3229
+ ...(took !== undefined ? { took } : {}),
3230
+ ...(done ? { total: Date.now() - started } : {}),
3231
+ });
3232
+ if (!done) timer.current = setTimeout(tick, 24);
3233
+ };
3234
+ timer.current = setTimeout(tick, 700);
3235
+ };
3236
+ return (
3237
+ <div className="flex w-full max-w-560 flex-col gap-16">
3238
+ <div>
3239
+ <Button size="sm" onClick={play} disabled={state.status === "pending" || state.status === "streaming"}>
3240
+ Stream it again
3241
+ </Button>
3242
+ </div>
3243
+ <ResponseView
3244
+ aria-label="GPT-5 response"
3245
+ status={state.status}
3246
+ text={state.text}
3247
+ reasoning={state.reasoning}
3248
+ {...(state.took !== undefined ? { reasoningDuration: state.took } : {})}
3249
+ footer={
3250
+ <ResponseMetrics
3251
+ reasoning
3252
+ live={state.status === "pending" || state.status === "streaming"}
3253
+ startedAt={state.started}
3254
+ duration={state.total}
3255
+ timeToFirstToken={state.status === "pending" ? undefined : 1180}
3256
+ />
3257
+ }
3258
+ />
3259
+ </div>
3260
+ );
3261
+ }
3262
+ ```
3263
+
3264
+ #### A thread
3265
+
3266
+ ```tsx
3267
+ <Thread className="w-full max-w-560">
3268
+ <div className="flex flex-col gap-12">
3269
+ <UserMessage>{prompts[1]}</UserMessage>
3270
+ <ResponseView
3271
+ aria-label="Claude Sonnet 4.5 response"
3272
+ text={sonnet.text}
3273
+ reasoning={sonnet.reasoning}
3274
+ reasoningDuration={2100}
3275
+ footer={<ResponseMetrics timeToFirstToken={2920} duration={4380} inputTokens={412} outputTokens={168} reasoningTokens={64} cost="0.003756" />}
3276
+ />
3277
+ </div>
3278
+ <div className="flex flex-col gap-12">
3279
+ <UserMessage>Shorter subject line, please.</UserMessage>
3280
+ <ResponseView aria-label="Claude Sonnet 4.5 response" status="streaming" text={'```json\n{\n "subject": "Order #4471 is'} />
3281
+ </div>
3282
+ </Thread>
3283
+ ```
3284
+
3285
+ ## RightsMatrix
3286
+
3287
+ Rights by group: checkboxes that keep requirements, or marks with the roles that grant them.
3288
+
3289
+ `import { RightsMatrix } from "@logicplanes/kaeso-ui";`
3290
+
3291
+ ### Keyboard
3292
+
3293
+ | Key | Action |
3294
+ |---|---|
3295
+ | Space | Grants or withdraws the focused right, or every right of its group |
3296
+ | ↑ ↓ | Moves between checkboxes |
3297
+ | Home End | First or last checkbox |
3298
+
3299
+ ### Accessibility
3300
+
3301
+ - Each checkbox is named by the right's name and described by its description.
3302
+ - A group's checkbox is mixed when some of its rights are granted.
3303
+ - Granting a right grants what it needs; the change is announced politely.
3304
+ - ⚠ means the right exposes sensitive data or is hard to undo; its tooltip says so.
3305
+ - A right the person can't grant is disabled, and its row's tooltip says why.
3306
+
3307
+ ### Examples
3308
+
3309
+ #### A custom role
3310
+
3311
+ ```tsx
3312
+ function Role() {
3313
+ const [groups, setGroups] = useState(PROJECT_GROUPS);
3314
+ return (
3315
+ <RightsMatrix className="w-640" searchable aria-label="Rights of Support lead" groups={groups} onRightsChange={(c) => setGroups((gs) => apply(gs, c))} />
3316
+ );
3317
+ }
3318
+ ```
3319
+
3320
+ #### Against a role
3321
+
3322
+ ```tsx
3323
+ function Member() {
3324
+ const [granted, setGranted] = useState(() => new Set([...DEVELOPER, "models.manage"].filter((c) => c !== "logs.content.view")));
3325
+ const groups = PROJECT_GROUPS.map((g) => ({
3326
+ ...g,
3327
+ rights: g.rights.map((r) => {
3328
+ const inRole = DEVELOPER.has(r.code);
3329
+ const on = granted.has(r.code);
3330
+ return {
3331
+ ...r,
3332
+ granted: on,
3333
+ origin: inRole ? (on ? ("preset" as const) : ("removed" as const)) : on ? ("added" as const) : ("none" as const),
3334
+ ...(HELD.has(r.code) ? {} : { disabled: true, disabledReason: "You can grant only rights you hold" }),
3335
+ };
3336
+ }),
3337
+ }));
3338
+ return (
3339
+ <RightsMatrix
3340
+ className="w-640"
3341
+ aria-label="Rights of Maya Chen"
3342
+ originLabels={{ preset: "Developer" }}
3343
+ groups={groups}
3344
+ onRightsChange={(changes) =>
3345
+ setGranted((s) => {
3346
+ const next = new Set(s);
3347
+ for (const c of changes) {
3348
+ if (c.granted) next.add(c.code);
3349
+ else next.delete(c.code);
3350
+ }
3351
+ return next;
3352
+ })
3353
+ }
3354
+ />
3355
+ );
3356
+ }
3357
+ ```
3358
+
3359
+ #### Read-only, with sources
3360
+
3361
+ ```tsx
3362
+ <RightsMatrix className="w-640" readOnly searchable aria-label="Maya Chen's rights in Support bot" groups={EFFECTIVE} />
3363
+ ```
3364
+
3365
+ ## RolePicker
3366
+
3367
+ Choosing a role: built-in, then custom, each with one sentence; ungivable roles say why.
3368
+
3369
+ `import { RolePicker } from "@logicplanes/kaeso-ui";`
3370
+
3371
+ ### Keyboard
3372
+
3373
+ | Key | Action |
3374
+ |---|---|
3375
+ | ↑ ↓ | Moves between roles in the list |
3376
+ | Space | Chooses the focused role |
3377
+
3378
+ ### Accessibility
3379
+
3380
+ - A list is a radio group; each radio is named by the role and described by its sentence.
3381
+ - A role the person can't give stays in the list, disabled, with the reason under it and in its tooltip.
3382
+
3383
+ ### Examples
3384
+
3385
+ #### List
3386
+
3387
+ ```tsx
3388
+ function List() {
3389
+ const [role, setRole] = useState("developer");
3390
+ return <RolePicker className="w-480" aria-label="Role in the organization" roles={ORG_ROLES} value={role} onValueChange={setRole} />;
3391
+ }
3392
+ ```
3393
+
3394
+ #### Select
3395
+
3396
+ ```tsx
3397
+ function Compact() {
3398
+ const [role, setRole] = useState("developer");
3399
+ return <RolePicker appearance="select" aria-label="Role in the organization" roles={ORG_ROLES} value={role} onValueChange={setRole} />;
3400
+ }
3401
+ ```
3402
+
3403
+ ## Sankey
3404
+
3405
+ Requests from keys to models to providers; hover a node to follow its path.
3406
+
3407
+ `import { Sankey } from "@logicplanes/kaeso-ui";`
3408
+
3409
+ ### Examples
3410
+
3411
+ #### Routes
3412
+
3413
+ ```tsx
3414
+ <div className="w-full">
3415
+ <Sankey aria-label="Requests from keys to models to providers" nodes={nodes} links={links} />
3416
+ </div>
3417
+ ```
3418
+
3419
+ ## ScrollArea
3420
+
3421
+ A scroll container with thin scrollbars that appear on hover.
3422
+
3423
+ `import { ScrollArea } from "@logicplanes/kaeso-ui";`
3424
+
3425
+ ### Accessibility
3426
+
3427
+ - Give a focusable scroll region an aria-label so keyboard users know what they scroll.
3428
+
3429
+ ### Examples
3430
+
3431
+ #### Basic
3432
+
3433
+ ```tsx
3434
+ <ScrollArea className="h-160 w-320 rounded-well bg-fill-1" aria-label="Models">
3435
+ <ul className="flex flex-col p-12">
3436
+ {[...models, ...models, ...models].map((m, i) => (
3437
+ <li key={i} className="flex h-32 items-center mono text-small text-fg">
3438
+ {m.model}
3439
+ </li>
3440
+ ))}
3441
+ </ul>
3442
+ </ScrollArea>
3443
+ ```
3444
+
3445
+ ## SearchInput
3446
+
3447
+ Search with a leading icon, a clear circle when filled, and a shortcut hint where one was asked for.
3448
+
3449
+ `import { SearchInput } from "@logicplanes/kaeso-ui";`
3450
+
3451
+ ### Keyboard
3452
+
3453
+ | Key | Action |
3454
+ |---|---|
3455
+ | Escape | Clears the search |
3456
+
3457
+ ### Accessibility
3458
+
3459
+ - The clear button is named “Clear search”.
3460
+ - type=search lets assistive technology announce it as a search field.
3461
+
3462
+ ### Examples
3463
+
3464
+ #### In a toolbar
3465
+
3466
+ ```tsx
3467
+ <div className="w-240">
3468
+ <SearchInput aria-label="Search logs" placeholder="Search logs" />
3469
+ </div>
3470
+ ```
3471
+
3472
+ #### In the settings sidebar
3473
+
3474
+ ```tsx
3475
+ <div className="w-240">
3476
+ <SearchInput size="md" aria-label="Search settings" placeholder="Search…" />
3477
+ </div>
3478
+ ```
3479
+
3480
+ ## SecretReveal
3481
+
3482
+ The one-time secret: calm, centered, certain. Copy it, store it, see how to use it.
3483
+
3484
+ `import { SecretReveal, EnvironmentSnippet } from "@logicplanes/kaeso-ui";`
3485
+
3486
+ ### Accessibility
3487
+
3488
+ - The secret is a read-only text box; a click selects all of it.
3489
+ - “Copy key” becomes “Copied” without changing size.
3490
+ - The warning is text, not only color.
3491
+
3492
+ ### Examples
3493
+
3494
+ #### After creating a key
3495
+
3496
+ ```tsx
3497
+ <div className="w-560">
3498
+ <SecretReveal secret={SECRET} name="checkout-web" baseUrl="https://gateway.northwind.example" model="gpt-5-mini" />
3499
+ </div>
3500
+ ```
3501
+
3502
+ #### EnvironmentSnippet
3503
+
3504
+ ```tsx
3505
+ <div className="w-640">
3506
+ <EnvironmentSnippet baseUrl="https://gateway.northwind.example" />
3507
+ </div>
3508
+ ```
3509
+
3510
+ #### A Platform API key
3511
+
3512
+ ```tsx
3513
+ <div className="w-560">
3514
+ <SecretReveal
3515
+ secret={SECRET}
3516
+ name="terraform"
3517
+ baseUrl="https://gateway.northwind.example"
3518
+ snippets={[
3519
+ {
3520
+ label: "curl",
3521
+ lang: "bash",
3522
+ code: `curl https://gateway.northwind.example/platform/v1/orgs/org_01/projects \\\n -H "Authorization: Bearer ${SECRET}"`,
3523
+ },
3524
+ ]}
3525
+ />
3526
+ </div>
3527
+ ```
3528
+
3529
+ ## Section
3530
+
3531
+ Section, SplitView and AuthLayout: page structure with space instead of boxes.
3532
+
3533
+ `import { Section, SplitView, AuthLayout } from "@logicplanes/kaeso-ui";`
3534
+
3535
+ ### Examples
3536
+
3537
+ #### SplitView
3538
+
3539
+ ```tsx
3540
+ <div className="w-full">
3541
+ <SplitView
3542
+ aside={
3543
+ <>
3544
+ <Select
3545
+ label="Measure"
3546
+ options={[
3547
+ { value: "spend", label: "Spend" },
3548
+ { value: "requests", label: "Requests" },
3549
+ ]}
3550
+ defaultValue="spend"
3551
+ fullWidth
3552
+ />
3553
+ <Select
3554
+ label="Group by"
3555
+ options={[
3556
+ { value: "model", label: "Model" },
3557
+ { value: "key", label: "Key" },
3558
+ ]}
3559
+ defaultValue="model"
3560
+ fullWidth
3561
+ />
3562
+ </>
3563
+ }
3564
+ >
3565
+ <Section title="Usage" description="Last 30 days">
3566
+ <MetricRow>
3567
+ <Stat value="12,480" unit="requests" />
3568
+ <Stat value="$184.20" unit="total spend" />
3569
+ </MetricRow>
3570
+ </Section>
3571
+ </SplitView>
3572
+ </div>
3573
+ ```
3574
+
3575
+ #### AuthLayout
3576
+
3577
+ ```tsx
3578
+ <div className="h-640 w-full overflow-hidden rounded-frame shadow-frame">
3579
+ <AuthLayout title="Sign in to Kaeso" description="Use the account your administrator created." className="min-h-full">
3580
+ <Input label="Email" size="xl" placeholder="ada@northwind.example" />
3581
+ <Input label="Password" size="xl" type="password" />
3582
+ <Button variant="primary" size="xl" fullWidth>
3583
+ Sign in
3584
+ </Button>
3585
+ </AuthLayout>
3586
+ </div>
3587
+ ```
3588
+
3589
+ ## Segmented
3590
+
3591
+ Two to five exclusive options on a fill track; the active one raised, sliding in 180ms.
3592
+
3593
+ `import { Segmented } from "@logicplanes/kaeso-ui";`
3594
+
3595
+ ### Keyboard
3596
+
3597
+ | Key | Action |
3598
+ |---|---|
3599
+ | Left Right | Moves and selects |
3600
+
3601
+ ### Accessibility
3602
+
3603
+ - A radio group: one stop in the tab order, arrows choose.
3604
+ - Icon-only options need an aria-label, shown as a tooltip.
3605
+
3606
+ ### Examples
3607
+
3608
+ #### Period
3609
+
3610
+ ```tsx
3611
+ <Segmented
3612
+ aria-label="Period"
3613
+ defaultValue="7d"
3614
+ options={[
3615
+ { value: "24h", label: "24h" },
3616
+ { value: "7d", label: "7d" },
3617
+ { value: "30d", label: "30d" },
3618
+ { value: "90d", label: "90d" },
3619
+ ]}
3620
+ />
3621
+ ```
3622
+
3623
+ #### View with icons
3624
+
3625
+ ```tsx
3626
+ <Segmented
3627
+ aria-label="View"
3628
+ size="sm"
3629
+ defaultValue="list"
3630
+ options={[
3631
+ { value: "list", label: "List", icon: icons.list },
3632
+ { value: "board", label: "Board", icon: icons.board },
3633
+ ]}
3634
+ />
3635
+ ```
3636
+
3637
+ ## Select
3638
+
3639
+ A pill that opens a list on the menu surface: a check on the selected item, groups, typeahead.
3640
+
3641
+ `import { Select } from "@logicplanes/kaeso-ui";`
3642
+
3643
+ ### Keyboard
3644
+
3645
+ | Key | Action |
3646
+ |---|---|
3647
+ | Enter Space | Opens |
3648
+ | Up Down | Moves |
3649
+ | Type | Jumps to an option |
3650
+ | Escape | Closes and returns focus |
3651
+
3652
+ ### Accessibility
3653
+
3654
+ - Takes label, description and error like Input.
3655
+ - appearance="ghost" is for inline use in settings rows, where the row title is the label: pass aria-label.
3656
+
3657
+ ### Examples
3658
+
3659
+ #### Basic
3660
+
3661
+ ```tsx
3662
+ <div className="w-280">
3663
+ <Select label="Log retention" options={retention} defaultValue="30" fullWidth />
3664
+ </div>
3665
+ ```
3666
+
3667
+ #### Ghost, in a settings row
3668
+
3669
+ ```tsx
3670
+ <Select
3671
+ appearance="ghost"
3672
+ size="sm"
3673
+ aria-label="First day of the week"
3674
+ options={[
3675
+ { value: "mon", label: "Monday" },
3676
+ { value: "sun", label: "Sunday" },
3677
+ ]}
3678
+ defaultValue="mon"
3679
+ />
3680
+ ```
3681
+
3682
+ #### Descriptions and groups
3683
+
3684
+ ```tsx
3685
+ <Select aria-label="Preset" options={presets} defaultValue="developer" icon={icons.members} />
3686
+ ```
3687
+
3688
+ #### Multiple
3689
+
3690
+ ```tsx
3691
+ <Select
3692
+ multiple
3693
+ aria-label="Providers"
3694
+ placeholder="All providers"
3695
+ options={[
3696
+ { value: "openai", label: "OpenAI" },
3697
+ { value: "anthropic", label: "Anthropic" },
3698
+ ]}
3699
+ defaultValue={["openai"]}
3700
+ />
3701
+ ```
3702
+
3703
+ ## SensitiveInput
3704
+
3705
+ Secrets masked by default, an eye to reveal, mono when revealed, never autocompleted.
3706
+
3707
+ `import { SensitiveInput } from "@logicplanes/kaeso-ui";`
3708
+
3709
+ ### Accessibility
3710
+
3711
+ - The reveal circle is named Show or Hide.
3712
+ - Passwords show “Caps Lock is on.” in the field's description.
3713
+
3714
+ ### Examples
3715
+
3716
+ #### Provider key
3717
+
3718
+ ```tsx
3719
+ <div className="w-400">
3720
+ <SensitiveInput label="Provider key" description="Only the last four characters stay visible after saving." placeholder="sk-ant-…" />
3721
+ </div>
3722
+ ```
3723
+
3724
+ #### Password
3725
+
3726
+ ```tsx
3727
+ <div className="w-320">
3728
+ <SensitiveInput kind="password" label="Password" />
3729
+ </div>
3730
+ ```
3731
+
3732
+ ## Separator
3733
+
3734
+ A 1px line, for when space and tone cannot separate two regions.
3735
+
3736
+ `import { Separator } from "@logicplanes/kaeso-ui";`
3737
+
3738
+ ### Accessibility
3739
+
3740
+ - Renders role=separator with its orientation.
3741
+
3742
+ ### Examples
3743
+
3744
+ #### Basic
3745
+
3746
+ ```tsx
3747
+ <div className="flex w-320 flex-col gap-12 text-body text-fg">
3748
+ <span>Above</span>
3749
+ <Separator />
3750
+ <span>Below</span>
3751
+ </div>
3752
+ ```
3753
+
3754
+ #### With a label
3755
+
3756
+ ```tsx
3757
+ <div className="w-320">
3758
+ <Separator label="or" />
3759
+ </div>
3760
+ ```
3761
+
3762
+ ## SettingsGroup
3763
+
3764
+ Settings rows in a rounded group, title and description left, the control right.
3765
+
3766
+ `import { SettingsSection, SettingsGroup, SettingsRow } from "@logicplanes/kaeso-ui";`
3767
+
3768
+ ### Accessibility
3769
+
3770
+ - Name controls in rows with aria-label or aria-labelledby pointing at the row title.
3771
+ - Navigation rows are links; the whole row is the target.
3772
+
3773
+ ### Examples
3774
+
3775
+ #### Preferences
3776
+
3777
+ ```tsx
3778
+ <div className="w-720">
3779
+ <SettingsSection title="General">
3780
+ <SettingsGroup>
3781
+ <SettingsRow
3782
+ title="Default project"
3783
+ description="Opens when you sign in."
3784
+ control={
3785
+ <Select
3786
+ appearance="ghost"
3787
+ size="sm"
3788
+ aria-label="Default project"
3789
+ options={[
3790
+ { value: "a", label: "Checkout assistant" },
3791
+ { value: "b", label: "Support triage" },
3792
+ ]}
3793
+ defaultValue="a"
3794
+ />
3795
+ }
3796
+ />
3797
+ <SettingsRow
3798
+ title="First day of the week"
3799
+ description="Used in date pickers and usage charts."
3800
+ control={
3801
+ <Select
3802
+ appearance="ghost"
3803
+ size="sm"
3804
+ aria-label="First day of the week"
3805
+ options={[
3806
+ { value: "mon", label: "Monday" },
3807
+ { value: "sun", label: "Sunday" },
3808
+ ]}
3809
+ defaultValue="mon"
3810
+ />
3811
+ }
3812
+ />
3813
+ <SettingsRow
3814
+ title="Show times in UTC"
3815
+ description="Request logs and the audit log use UTC instead of your time zone."
3816
+ control={<Switch aria-label="Show times in UTC" />}
3817
+ />
3818
+ </SettingsGroup>
3819
+ </SettingsSection>
3820
+ <SettingsSection title="Security">
3821
+ <SettingsGroup>
3822
+ <SettingsRow title="Two-step verification" description="Codes from an authenticator app." control={<Button size="sm">Set up</Button>} />
3823
+ <SettingsRow
3824
+ title="Single sign-on"
3825
+ description="Managed by your organization."
3826
+ control={
3827
+ <Link href="#" external>
3828
+ Open settings
3829
+ </Link>
3830
+ }
3831
+ />
3832
+ </SettingsGroup>
3833
+ </SettingsSection>
3834
+ </div>
3835
+ ```
3836
+
3837
+ #### Navigation rows
3838
+
3839
+ ```tsx
3840
+ <div className="w-720">
3841
+ <SettingsGroup>
3842
+ <SettingsRow icon={icons.members} title="Members" description="Who can use this organization." value="5 members" href="#" />
3843
+ <SettingsRow icon={icons.providerKey} title="Provider keys" description="Credentials the gateway uses upstream." value="2" href="#" />
3844
+ <SettingsRow icon={icons.security} title="Security" description="SSO, two-step verification and key lifetime." value="Off" href="#" />
3845
+ </SettingsGroup>
3846
+ </div>
3847
+ ```
3848
+
3849
+ #### Narrow column
3850
+
3851
+ ```tsx
3852
+ <div className="w-360">
3853
+ <SettingsGroup>
3854
+ <SettingsRow
3855
+ title="Name"
3856
+ description="Shown to the members of your organizations."
3857
+ control={
3858
+ <div className="flex items-center gap-8">
3859
+ <div className="w-200">
3860
+ <Input aria-label="Name" size="sm" defaultValue="Ada Okafor" />
3861
+ </div>
3862
+ <Button size="sm">Save</Button>
3863
+ </div>
3864
+ }
3865
+ />
3866
+ <SettingsRow
3867
+ title="Account id"
3868
+ description="Used by instance administrators and in the audit log."
3869
+ control={<InlineCopyText value="usr_01m4sprwt3eksbpepefnnzdg4a" label="Copy account id" />}
3870
+ />
3871
+ <SettingsRow title="Email" description="owner@example.com" control={<Button size="sm">Change email</Button>} />
3872
+ <SettingsRow title="Show times in UTC" description="Request logs and the audit log use UTC." control={<Switch aria-label="Show times in UTC" />} />
3873
+ </SettingsGroup>
3874
+ </div>
3875
+ ```
3876
+
3877
+ ## Sheet
3878
+
3879
+ A panel from the edge that keeps the list behind it readable: the request inspector, details.
3880
+
3881
+ `import { Sheet } from "@logicplanes/kaeso-ui";`
3882
+
3883
+ ### Keyboard
3884
+
3885
+ | Key | Action |
3886
+ |---|---|
3887
+ | Escape | Closes |
3888
+
3889
+ ### Accessibility
3890
+
3891
+ - A dialog; the title names it, the subtitle carries the id.
3892
+ - Apps make it URL-addressable so links can open it.
3893
+
3894
+ ### Examples
3895
+
3896
+ #### Request inspector
3897
+
3898
+ ```tsx
3899
+ <Sheet
3900
+ trigger={<Button>Open request</Button>}
3901
+ title="POST /v1/messages"
3902
+ subtitle="req_01JAR4X2KQ"
3903
+ size="md"
3904
+ modal={false}
3905
+ actions={<IconButton icon={icons.copy} aria-label="Copy request id" />}
3906
+ >
3907
+ {body}
3908
+ </Sheet>
3909
+ ```
3910
+
3911
+ ## Sidebar
3912
+
3913
+ The console navigation: the place it is in, 32px items with pill fills, groups that remember, a quiet resize edge, collapse and rail.
3914
+
3915
+ `import { Sidebar } from "@logicplanes/kaeso-ui";`
3916
+
3917
+ ### Keyboard
3918
+
3919
+ | Key | Action |
3920
+ |---|---|
3921
+ | [ | Collapses and expands |
3922
+ | Left Right | Resize, on the edge |
3923
+ | Enter | Collapse, on the edge |
3924
+
3925
+ ### Accessibility
3926
+
3927
+ - The current page has aria-current=page.
3928
+ - In rail mode labels move into tooltips and become accessible names.
3929
+ - A collapsed sidebar is inert, so its links leave the tab order.
3930
+
3931
+ ### Examples
3932
+
3933
+ #### In the console
3934
+
3935
+ Click an item to move the current page. Collapse with the button in the header, or click the right edge; the button in the page header brings it back, and resting on it peeks.
3936
+
3937
+ ```tsx
3938
+ <Console />
3939
+ ```
3940
+
3941
+ #### Rail
3942
+
3943
+ Icons only, with labels in tooltips. For narrow tools that keep navigation in view.
3944
+
3945
+ ```tsx
3946
+ <Console rail />
3947
+ ```
3948
+
3949
+ ## Skeleton
3950
+
3951
+ Shapes of the content that is loading, with a quiet pulse, never a shimmer.
3952
+
3953
+ `import { Skeleton, SkeletonLine } from "@logicplanes/kaeso-ui";`
3954
+
3955
+ ### Accessibility
3956
+
3957
+ - Skeletons are hidden from assistive technology; mark the region aria-busy while it loads.
3958
+
3959
+ ### Examples
3960
+
3961
+ #### Rows
3962
+
3963
+ ```tsx
3964
+ <div className="flex w-480 flex-col gap-4">
3965
+ {[64, 48, 72].map((w, i) => (
3966
+ <div key={i} className="flex h-44 items-center gap-12 px-16">
3967
+ <Skeleton shape="circle" className="size-16" />
3968
+ <Skeleton shape="line" style={{ width: `${w}%` }} />
3969
+ </div>
3970
+ ))}
3971
+ </div>
3972
+ ```
3973
+
3974
+ #### Metric row
3975
+
3976
+ ```tsx
3977
+ <div className="flex flex-wrap gap-x-48 gap-y-24">
3978
+ {[0, 1, 2].map((i) => (
3979
+ <div key={i} className="flex flex-col gap-8">
3980
+ <Skeleton className="h-32 w-120" />
3981
+ <SkeletonLine className="w-80" />
3982
+ </div>
3983
+ ))}
3984
+ </div>
3985
+ ```
3986
+
3987
+ ## Slider
3988
+
3989
+ A 4px track, an ink range and an ink thumb; a value bubble while dragging; ticks and ranges.
3990
+
3991
+ `import { Slider } from "@logicplanes/kaeso-ui";`
3992
+
3993
+ ### Keyboard
3994
+
3995
+ | Key | Action |
3996
+ |---|---|
3997
+ | Left | Decreases |
3998
+ | Right | Increases |
3999
+ | Home | Minimum |
4000
+ | End | Maximum |
4001
+
4002
+ ### Accessibility
4003
+
4004
+ - Each thumb is a slider with its own name (getAriaLabel for ranges).
4005
+
4006
+ ### Examples
4007
+
4008
+ #### Basic
4009
+
4010
+ ```tsx
4011
+ <div className="w-320">
4012
+ <Slider aria-label="Alert threshold" defaultValue={80} formatValue={(v) => `${v}%`} />
4013
+ </div>
4014
+ ```
4015
+
4016
+ #### Range with ticks
4017
+
4018
+ ```tsx
4019
+ <div className="w-320">
4020
+ <Slider getAriaLabel={(i) => (i === 0 ? "Minimum" : "Maximum")} defaultValue={[20, 60]} step={10} ticks />
4021
+ </div>
4022
+ ```
4023
+
4024
+ ## Sparkline
4025
+
4026
+ One series inline: 80×24, the last point as a dot.
4027
+
4028
+ `import { Sparkline } from "@logicplanes/kaeso-ui";`
4029
+
4030
+ ### Examples
4031
+
4032
+ #### Inline
4033
+
4034
+ ```tsx
4035
+ <div className="flex items-center gap-12 text-body text-fg">
4036
+ <span className="mono text-small">gpt-5</span>
4037
+ <Sparkline aria-label="Requests over 7 days, rising" values={[12, 18, 15, 22, 28, 26, 34]} />
4038
+ <span className="tabular">34</span>
4039
+ </div>
4040
+ ```
4041
+
4042
+ #### Fluid
4043
+
4044
+ fluid fills its container's width, as in a StatTile; the line keeps its weight at any width.
4045
+
4046
+ ```tsx
4047
+ <div className="flex w-full max-w-480 flex-col gap-16">
4048
+ <Sparkline fluid aria-label="Requests over 14 days, rising" values={[12, 18, 15, 22, 28, 26, 34, 31, 36, 40, 38, 44, 47, 52]} />
4049
+ <div className="w-200">
4050
+ <Sparkline fluid aria-label="Errors over 14 days, falling" color="var(--k-viz-4)" values={[9, 8, 8, 7, 6, 7, 5, 5, 4, 4, 3, 3, 2, 2]} />
4051
+ </div>
4052
+ </div>
4053
+ ```
4054
+
4055
+ ## Stat
4056
+
4057
+ A big number followed by its word, no box. MetricRow lines up three to five; StatGrid holds tiles.
4058
+
4059
+ `import { Stat, MetricRow, StatGrid, StatTile } from "@logicplanes/kaeso-ui";`
4060
+
4061
+ ### Examples
4062
+
4063
+ #### Metric row
4064
+
4065
+ ```tsx
4066
+ <MetricRow>
4067
+ <Stat value="12,480" unit="requests" />
4068
+ <Stat value="$184.20" unit="total spend" delta={{ label: "+12%", tone: "neutral" }} />
4069
+ <Stat value="1.2 s" unit="median latency" />
4070
+ </MetricRow>
4071
+ ```
4072
+
4073
+ #### Stat tiles: four, three without cost, totals
4074
+
4075
+ ```tsx
4076
+ <div className="flex flex-col gap-24">
4077
+ <StatGrid aria-label="Usage in the period">
4078
+ <StatTile label="Requests" value="12,480" change={{ value: 0.12 }} detail="vs. the previous 7 days" trend={DAYS} />
4079
+ <StatTile label="Tokens" value="4.2M" change={{ value: -0.04 }} detail="vs. the previous 7 days" trend={DAYS.map((d) => d * 3)} />
4080
+ <StatTile label="Cost" value="$184.20" detail="vs. the previous 7 days" trend={[]} />
4081
+ <StatTile label="Errors" value="38" change={{ value: 0.3, better: "down" }} detail="0.3% of requests" trend={DAYS.map((d) => d % 5)} />
4082
+ </StatGrid>
4083
+ <StatGrid aria-label="Usage without the cost right">
4084
+ <StatTile label="Requests" value="12,480" change={{ value: 0.12 }} detail="vs. the previous 7 days" trend={DAYS} />
4085
+ <StatTile label="Tokens" value="4.2M" change={{ value: -0.04 }} detail="vs. the previous 7 days" trend={DAYS.map((d) => d * 3)} />
4086
+ <StatTile label="Errors" value="38" change={{ value: 0.3, better: "down" }} detail="0.3% of requests" trend={DAYS.map((d) => d % 5)} />
4087
+ </StatGrid>
4088
+ <StatGrid aria-label="Usage in the last 30 days">
4089
+ <StatTile label="Requests" value="334" detail="11 a day on average" />
4090
+ <StatTile label="Tokens" value="27.2K" detail="81 per request" />
4091
+ <StatTile label="Cost" value="$0.09" detail="$0.00027 per request" />
4092
+ <StatTile label="Logs" value="360 KB" detail="334 entries" />
4093
+ </StatGrid>
4094
+ </div>
4095
+ ```
4096
+
4097
+ ## StatusDot
4098
+
4099
+ An 8px semantic dot with its word. Status is never color alone.
4100
+
4101
+ `import { StatusDot } from "@logicplanes/kaeso-ui";`
4102
+
4103
+ ### Examples
4104
+
4105
+ #### Tones
4106
+
4107
+ ```tsx
4108
+ <div className="flex flex-col gap-8">
4109
+ <StatusDot tone="success">Active</StatusDot>
4110
+ <StatusDot tone="warning">Expiring</StatusDot>
4111
+ <StatusDot tone="danger">Down</StatusDot>
4112
+ <StatusDot tone="info">Syncing</StatusDot>
4113
+ <StatusDot tone="neutral">Archived</StatusDot>
4114
+ <StatusDot tone="success">Live</StatusDot>
4115
+ </div>
4116
+ ```
4117
+
4118
+ ## Switch
4119
+
4120
+ A pill track and a round thumb that travels in 180ms.
4121
+
4122
+ `import { Switch } from "@logicplanes/kaeso-ui";`
4123
+
4124
+ ### Keyboard
4125
+
4126
+ | Key | Action |
4127
+ |---|---|
4128
+ | Space | Toggles |
4129
+
4130
+ ### Accessibility
4131
+
4132
+ - Use a switch for settings that apply at once; use a checkbox inside forms that are submitted.
4133
+
4134
+ ### Examples
4135
+
4136
+ #### Basic
4137
+
4138
+ ```tsx
4139
+ <Switch label="Send budget alerts" description="Email owners at 80% and 100% of the monthly budget." defaultChecked className="w-400" />
4140
+ ```
4141
+
4142
+ #### Sizes
4143
+
4144
+ ```tsx
4145
+ <div className="flex items-center gap-16">
4146
+ <Switch size="sm" aria-label="Small" defaultChecked />
4147
+ <Switch size="md" aria-label="Medium" defaultChecked />
4148
+ <Switch size="lg" aria-label="Large" defaultChecked />
4149
+ </div>
4150
+ ```
4151
+
4152
+ #### Disabled, with the reason
4153
+
4154
+ ```tsx
4155
+ <Switch
4156
+ label="Require single sign-on"
4157
+ description="Members reach the organization only through the identity provider."
4158
+ disabled
4159
+ disabledReason="Connect an identity provider first"
4160
+ className="w-400"
4161
+ />
4162
+ ```
4163
+
4164
+ ## Table
4165
+
4166
+ A plain hairline table for small static data.
4167
+
4168
+ `import { Table } from "@logicplanes/kaeso-ui";`
4169
+
4170
+ ### Examples
4171
+
4172
+ #### Prices
4173
+
4174
+ ```tsx
4175
+ <Table className="w-640">
4176
+ <Table.Head>
4177
+ <Table.Row>
4178
+ <Table.HeaderCell>Model</Table.HeaderCell>
4179
+ <Table.HeaderCell align="end">Input / M</Table.HeaderCell>
4180
+ <Table.HeaderCell align="end">Output / M</Table.HeaderCell>
4181
+ <Table.HeaderCell align="end">Cache read / M</Table.HeaderCell>
4182
+ </Table.Row>
4183
+ </Table.Head>
4184
+ <Table.Body>
4185
+ {models.map((m) => (
4186
+ <Table.Row key={m.id}>
4187
+ <Table.Cell className="mono text-small">{m.model}</Table.Cell>
4188
+ <Table.Cell align="end">${m.input}</Table.Cell>
4189
+ <Table.Cell align="end">${m.output}</Table.Cell>
4190
+ <Table.Cell align="end">${m.cacheRead}</Table.Cell>
4191
+ </Table.Row>
4192
+ ))}
4193
+ </Table.Body>
4194
+ </Table>
4195
+ ```
4196
+
4197
+ #### Tones
4198
+
4199
+ A cell's tone colors words that already carry a status. Use it sparingly.
4200
+
4201
+ ```tsx
4202
+ <Table className="w-560">
4203
+ <Table.Head>
4204
+ <Table.Row>
4205
+ <Table.HeaderCell>Provider</Table.HeaderCell>
4206
+ <Table.HeaderCell>State</Table.HeaderCell>
4207
+ <Table.HeaderCell align="end">Median latency</Table.HeaderCell>
4208
+ </Table.Row>
4209
+ </Table.Head>
4210
+ <Table.Body>
4211
+ {providers.map((p) => (
4212
+ <Table.Row key={p.name}>
4213
+ <Table.Cell>{p.name}</Table.Cell>
4214
+ <Table.Cell tone={p.tone}>{p.state}</Table.Cell>
4215
+ <Table.Cell align="end">{p.latency}</Table.Cell>
4216
+ </Table.Row>
4217
+ ))}
4218
+ </Table.Body>
4219
+ </Table>
4220
+ ```
4221
+
4222
+ #### Wrapping
4223
+
4224
+ Long text and links wrap like prose; the row grows and keeps its padding, and one-line rows stay 44px.
4225
+
4226
+ ```tsx
4227
+ <Table className="w-560">
4228
+ <Table.Head>
4229
+ <Table.Row>
4230
+ <Table.HeaderCell>Key</Table.HeaderCell>
4231
+ <Table.HeaderCell>Limit</Table.HeaderCell>
4232
+ <Table.HeaderCell>Note</Table.HeaderCell>
4233
+ </Table.Row>
4234
+ </Table.Head>
4235
+ <Table.Body>
4236
+ {limits.map((l) => (
4237
+ <Table.Row key={l.key}>
4238
+ <Table.Cell className="mono text-small whitespace-nowrap">{l.key}</Table.Cell>
4239
+ <Table.Cell>{l.limit}</Table.Cell>
4240
+ <Table.Cell>
4241
+ {l.note} <Link href={l.href}>{l.link}</Link>
4242
+ </Table.Cell>
4243
+ </Table.Row>
4244
+ ))}
4245
+ </Table.Body>
4246
+ </Table>
4247
+ ```
4248
+
4249
+ ## TableOfContents
4250
+
4251
+ “On this page”: the heading in view in text color with a 2px ink bar.
4252
+
4253
+ `import { TableOfContents } from "@logicplanes/kaeso-ui";`
4254
+
4255
+ ### Examples
4256
+
4257
+ #### Basic
4258
+
4259
+ ```tsx
4260
+ <TableOfContents items={items} active="examples" className="w-200" />
4261
+ ```
4262
+
4263
+ ## Tabs
4264
+
4265
+ Underline tabs for detail pages and pill tabs for page headers.
4266
+
4267
+ `import { Tabs, PillTabs } from "@logicplanes/kaeso-ui";`
4268
+
4269
+ ### Keyboard
4270
+
4271
+ | Key | Action |
4272
+ |---|---|
4273
+ | Left Right | Moves between tabs |
4274
+ | Home End | First and last tab |
4275
+
4276
+ ### Accessibility
4277
+
4278
+ - role=tablist with tabs and panels wired by Base UI.
4279
+
4280
+ ### Examples
4281
+
4282
+ #### Underline
4283
+
4284
+ ```tsx
4285
+ <Tabs defaultValue="overview" className="w-480">
4286
+ <Tabs.List aria-label="Request">
4287
+ <Tabs.Tab value="overview">Overview</Tabs.Tab>
4288
+ <Tabs.Tab value="conversation">Conversation</Tabs.Tab>
4289
+ <Tabs.Tab value="request">Request</Tabs.Tab>
4290
+ <Tabs.Tab value="response">Response</Tabs.Tab>
4291
+ </Tabs.List>
4292
+ <Tabs.Panel value="overview" className="pt-16 text-body text-fg-secondary">
4293
+ The gateway answered in 1.2 s.
4294
+ </Tabs.Panel>
4295
+ </Tabs>
4296
+ ```
4297
+
4298
+ #### Pill tabs
4299
+
4300
+ ```tsx
4301
+ <PillTabs defaultValue="all">
4302
+ <PillTabs.List aria-label="Keys">
4303
+ <PillTabs.Tab value="all" count={6}>
4304
+ All
4305
+ </PillTabs.Tab>
4306
+ <PillTabs.Tab value="active" count={3}>
4307
+ Active
4308
+ </PillTabs.Tab>
4309
+ <PillTabs.Tab value="disabled">Disabled</PillTabs.Tab>
4310
+ <PillTabs.Tab value="archived" icon={icons.stack}>
4311
+ Archived
4312
+ </PillTabs.Tab>
4313
+ </PillTabs.List>
4314
+ </PillTabs>
4315
+ ```
4316
+
4317
+ ## Tag
4318
+
4319
+ Metadata pills: 28px, raised and ringed, with an optional icon and remove circle.
4320
+
4321
+ `import { Tag } from "@logicplanes/kaeso-ui";`
4322
+
4323
+ ### Accessibility
4324
+
4325
+ - Clickable tags are buttons; toggle pills carry aria-pressed.
4326
+ - Dots support the word; they never replace it.
4327
+
4328
+ ### Examples
4329
+
4330
+ #### Metadata
4331
+
4332
+ ```tsx
4333
+ <div className="flex flex-wrap items-center gap-6">
4334
+ <Tag>gpt-5</Tag>
4335
+ <Tag>claude-sonnet-4-5</Tag>
4336
+ <Tag icon={icons.calendar}>Oct 12</Tag>
4337
+ <Tag>Active</Tag>
4338
+ <Tag onRemove={() => undefined}>checkout</Tag>
4339
+ </div>
4340
+ ```
4341
+
4342
+ #### Toggle pills
4343
+
4344
+ ```tsx
4345
+ function Toggle() {
4346
+ const [on, setOn] = useState(["model", "cost"]);
4347
+ return (
4348
+ <div className="flex flex-wrap gap-6">
4349
+ {["model", "key", "latency", "cost"].map((p) => (
4350
+ <Tag
4351
+ key={p}
4352
+ size="sm"
4353
+ variant={on.includes(p) ? "soft" : "outline"}
4354
+ pressed={on.includes(p)}
4355
+ onClick={() => setOn((x) => (x.includes(p) ? x.filter((y) => y !== p) : [...x, p]))}
4356
+ >
4357
+ {p}
4358
+ </Tag>
4359
+ ))}
4360
+ </div>
4361
+ );
4362
+ }
4363
+ ```
4364
+
4365
+ ## TagInput
4366
+
4367
+ Removable tags in a field: Enter, comma and paste split them; Backspace removes the last.
4368
+
4369
+ `import { TagInput } from "@logicplanes/kaeso-ui";`
4370
+
4371
+ ### Keyboard
4372
+
4373
+ | Key | Action |
4374
+ |---|---|
4375
+ | Enter | Adds the typed tag |
4376
+ | , | Adds the typed tag |
4377
+ | Backspace | Removes the last tag when the field is empty |
4378
+
4379
+ ### Accessibility
4380
+
4381
+ - Each tag's remove circle is named “Remove …”.
4382
+ - Invalid tags stay, marked with the danger ring; each says what is wrong in its tooltip, and the first problem is the field's description for a screen reader. Nothing is drawn under the field.
4383
+
4384
+ ### Examples
4385
+
4386
+ #### Invite by email
4387
+
4388
+ ```tsx
4389
+ <div className="w-400">
4390
+ <TagInput label="Invite members" placeholder="name@example.com" defaultValue={["lin@northwind.example", "tomas@northwind.example"]} validate={email} />
4391
+ </div>
4392
+ ```
4393
+
4394
+ ## Text
4395
+
4396
+ Text in the type scale: one scale, used the same way everywhere.
4397
+
4398
+ `import { Text } from "@logicplanes/kaeso-ui";`
4399
+
4400
+ ### Accessibility
4401
+
4402
+ - Text renders a span by default; pass as="p" for paragraphs.
4403
+ - Truncated text gets a tooltip with the full string and becomes focusable so keyboard users can read it.
4404
+ - Tertiary never carries meaning alone (3.23:1 on white).
4405
+
4406
+ ### Examples
4407
+
4408
+ #### Scale
4409
+
4410
+ ```tsx
4411
+ <div className="flex flex-col gap-8">
4412
+ {(Object.keys(KAESO_TEXT_VARIANTS.variant) as TextVariant[]).map((v) => (
4413
+ <Text key={v} variant={v}>
4414
+ {v === "metric" ? "$1,284.20" : `${v}: Route each request to the model you choose`}
4415
+ </Text>
4416
+ ))}
4417
+ </div>
4418
+ ```
4419
+
4420
+ #### Tones
4421
+
4422
+ ```tsx
4423
+ <div className="flex flex-col gap-6">
4424
+ {(
4425
+ [
4426
+ ["default", "Provider key saved"],
4427
+ ["secondary", "Last used 2 min ago"],
4428
+ ["tertiary", "Optional"],
4429
+ ["danger", "Unreachable"],
4430
+ ["success", "Connected"],
4431
+ ["warning", "Expires in 5 days"],
4432
+ ["info", "Syncing models"],
4433
+ ] as [TextTone, string][]
4434
+ ).map(([t, words]) => (
4435
+ <Text key={t} tone={t}>
4436
+ {t}: {words}
4437
+ </Text>
4438
+ ))}
4439
+ </div>
4440
+ ```
4441
+
4442
+ #### Mono and tabular
4443
+
4444
+ ```tsx
4445
+ <div className="flex flex-col gap-6">
4446
+ <Text mono>claude-sonnet-4-5</Text>
4447
+ <Text tabular>1,204,880 tokens</Text>
4448
+ </div>
4449
+ ```
4450
+
4451
+ #### Truncation
4452
+
4453
+ ```tsx
4454
+ <div className="w-240">
4455
+ <Text truncate>Checkout assistant · production traffic from the web storefront</Text>
4456
+ </div>
4457
+ ```
4458
+
4459
+ ## Textarea
4460
+
4461
+ Multi-line text at radius 14: grows to eight lines, then scrolls.
4462
+
4463
+ `import { Textarea } from "@logicplanes/kaeso-ui";`
4464
+
4465
+ ### Accessibility
4466
+
4467
+ - Takes label, description and error like Input.
4468
+
4469
+ ### Examples
4470
+
4471
+ #### Basic
4472
+
4473
+ ```tsx
4474
+ <div className="w-400">
4475
+ <Textarea label="System prompt" placeholder="You help customers finish checkout." />
4476
+ </div>
4477
+ ```
4478
+
4479
+ #### JSON
4480
+
4481
+ ```tsx
4482
+ <div className="w-400">
4483
+ <Textarea label="Metadata" mono defaultValue={'{\n "team": "checkout",\n "env": "production"\n}'} />
4484
+ </div>
4485
+ ```
4486
+
4487
+ ## Timeline
4488
+
4489
+ Events down a 1px line: the audit log.
4490
+
4491
+ `import { Timeline } from "@logicplanes/kaeso-ui";`
4492
+
4493
+ ### Examples
4494
+
4495
+ #### Audit log
4496
+
4497
+ ```tsx
4498
+ <Timeline events={auditEvents} className="w-560" />
4499
+ ```
4500
+
4501
+ ## TimeseriesChart
4502
+
4503
+ Spend over time: y labels on the right, a dashed grid, smart time ticks, a crosshair and tooltip.
4504
+
4505
+ `import { TimeseriesChart } from "@logicplanes/kaeso-ui";`
4506
+
4507
+ ### Keyboard
4508
+
4509
+ | Key | Action |
4510
+ |---|---|
4511
+ | Tab | Moves to the plot and shows the newest point |
4512
+ | ← → | Moves the crosshair and tooltip a point at a time |
4513
+ | Home End | Jumps to the first or last point |
4514
+ | Escape | Hides the crosshair |
4515
+
4516
+ ### Accessibility
4517
+
4518
+ - A hidden data table carries every value; “View as table” shows it.
4519
+ - The plot is one tab stop: the arrow keys move the crosshair and tooltip, and each point's values are announced politely.
4520
+ - Series keep their color in every chart for the session.
4521
+ - Legend pills toggle series and are buttons with aria-pressed.
4522
+
4523
+ ### Examples
4524
+
4525
+ #### Lines
4526
+
4527
+ ```tsx
4528
+ <div className="w-full">
4529
+ <TimeseriesChart aria-label="Spend per model, last 30 days" series={series} formatValue={money} />
4530
+ </div>
4531
+ ```
4532
+
4533
+ #### Area with a budget line
4534
+
4535
+ ```tsx
4536
+ <div className="w-full">
4537
+ <TimeseriesChart
4538
+ aria-label="Spend, last 30 days"
4539
+ kind="area"
4540
+ series={series.slice(0, 1)}
4541
+ formatValue={money}
4542
+ threshold={{ value: 9, label: "Daily budget $9.00" }}
4543
+ hideLegend
4544
+ />
4545
+ </div>
4546
+ ```
4547
+
4548
+ #### Stacked
4549
+
4550
+ ```tsx
4551
+ <div className="w-full">
4552
+ <TimeseriesChart aria-label="Spend by model, stacked" kind="stacked" series={series} formatValue={money} />
4553
+ </div>
4554
+ ```
4555
+
4556
+ ## Toast
4557
+
4558
+ An ink capsule at the bottom center: brief, calm, at most three.
4559
+
4560
+ `import { toast } from "@logicplanes/kaeso-ui";`
4561
+
4562
+ ### Keyboard
4563
+
4564
+ | Key | Action |
4565
+ |---|---|
4566
+ | Escape | Dismisses the focused toast |
4567
+
4568
+ ### Accessibility
4569
+
4570
+ - role=status; errors are alerts.
4571
+ - Five seconds, paused on hover and focus; swipe to dismiss.
4572
+ - Undo lives in the toast, so reversible actions need no confirmation.
4573
+
4574
+ ### Examples
4575
+
4576
+ #### Basic
4577
+
4578
+ ```tsx
4579
+ <>
4580
+ <Inline gap={8}>
4581
+ <Button onClick={() => toast("Key copied")}>Neutral</Button>
4582
+ <Button onClick={() => toast.success("Provider saved")}>Success</Button>
4583
+ <Button onClick={() => toast.error("Couldn't reach the provider.", { description: "Check the provider key, then try again." })}>Error</Button>
4584
+ <Button onClick={() => toast("Key disabled", { action: { label: "Undo", onClick: () => toast("Key enabled") } })}>With undo</Button>
4585
+ <Button
4586
+ onClick={() =>
4587
+ void toast.promise(new Promise((r) => setTimeout(r, 1600)), { loading: "Syncing models…", success: "Models synced", error: "Couldn't sync models" })
4588
+ }
4589
+ >
4590
+ Promise
4591
+ </Button>
4592
+ </Inline>
4593
+ </>
4594
+ ```
4595
+
4596
+ ## Toolbar
4597
+
4598
+ Filters, search and display options on one 48px row, with arrow-key focus across them.
4599
+
4600
+ `import { Toolbar } from "@logicplanes/kaeso-ui";`
4601
+
4602
+ ### Keyboard
4603
+
4604
+ | Key | Action |
4605
+ |---|---|
4606
+ | Left Right | Moves between controls |
4607
+
4608
+ ### Examples
4609
+
4610
+ #### Request logs
4611
+
4612
+ ```tsx
4613
+ function Logs() {
4614
+ const display = useLogDisplayOptions();
4615
+ return (
4616
+ <div className="w-full rounded-well shadow-frame">
4617
+ <Toolbar aria-label="Filters">
4618
+ <FilterChip property="Status" operator="is" value="2xx" onRemove={() => undefined} />
4619
+ <FilterChip property="Model" operator="is" value="gpt-5" onRemove={() => undefined} />
4620
+ <Button variant="ghost" size="sm" leadingIcon={icons.add}>
4621
+ Add filter
4622
+ </Button>
4623
+ <Toolbar.Spacer />
4624
+ <div className="w-240">
4625
+ <SearchInput aria-label="Search logs" placeholder="Search logs" />
4626
+ </div>
4627
+ <DisplayOptions {...display} />
4628
+ </Toolbar>
4629
+ </div>
4630
+ );
4631
+ }
4632
+ ```
4633
+
4634
+ ## Tooltip
4635
+
4636
+ A short label on hover and focus: a pill, with a shortcut when there is one.
4637
+
4638
+ `import { Tooltip } from "@logicplanes/kaeso-ui";`
4639
+
4640
+ ### Keyboard
4641
+
4642
+ | Key | Action |
4643
+ |---|---|
4644
+ | Tab | Shows the tooltip of the focused control |
4645
+ | Escape | Hides it |
4646
+
4647
+ ### Accessibility
4648
+
4649
+ - Opens after 500ms, instantly when moving between tooltipped siblings.
4650
+ - Never the only place for essential information.
4651
+ - Disabled controls show a tooltip only with a disabledReason.
4652
+
4653
+ ### Examples
4654
+
4655
+ #### Basic
4656
+
4657
+ ```tsx
4658
+ <Tooltip content="Filter">
4659
+ <IconButton icon={icons.filter} aria-label="Filter" variant="secondary" tooltip={false} />
4660
+ </Tooltip>
4661
+ ```
4662
+
4663
+ #### With a shortcut
4664
+
4665
+ The label and its keys together: “Go to request logs G then L”.
4666
+
4667
+ ```tsx
4668
+ <Tooltip content="Go to request logs" shortcut={[["g"], ["l"]]} defaultOpen>
4669
+ <Button variant="ghost">Request logs</Button>
4670
+ </Tooltip>
4671
+ ```
4672
+
4673
+ #### Several lines
4674
+
4675
+ ```tsx
4676
+ <Tooltip content="Requests over a hard limit are refused with 429. Soft limits only send an alert." shape="multiline">
4677
+ <Button>Hard limit</Button>
4678
+ </Tooltip>
4679
+ ```
4680
+
4681
+ ## UsageBreakdown
4682
+
4683
+ Token kinds in their fixed colors, and limits with their scope, window, kind and reset.
4684
+
4685
+ `import { UsageBreakdown, LimitMeter } from "@logicplanes/kaeso-ui";`
4686
+
4687
+ ### Accessibility
4688
+
4689
+ - Token kinds always map to the same series: input 1, cache read 6, cache write 2, output 3, reasoning 5.
4690
+ - Limits say hard or soft in words.
4691
+
4692
+ ### Examples
4693
+
4694
+ #### UsageBreakdown
4695
+
4696
+ ```tsx
4697
+ <UsageBreakdown tokens={tokenTotals} className="w-560" />
4698
+ ```
4699
+
4700
+ #### LimitMeter
4701
+
4702
+ ```tsx
4703
+ <div className="flex w-480 flex-col gap-24">
4704
+ <LimitMeter
4705
+ kind="spend"
4706
+ scope="Checkout assistant"
4707
+ window="Per month"
4708
+ used={321}
4709
+ limit={500}
4710
+ valueLabel="$321.00 of $500.00"
4711
+ alerts={[0.5, 0.8]}
4712
+ resets="Resets in 19 days"
4713
+ />
4714
+ <LimitMeter kind="requests" scope="Key checkout-web" window="Per minute" used={540} limit={600} valueLabel="540 of 600" resets="Resets every minute" />
4715
+ <LimitMeter
4716
+ kind="tokens"
4717
+ scope="Member Tomás Ruiz"
4718
+ window="Per day"
4719
+ used={1_050_000}
4720
+ limit={1_000_000}
4721
+ valueLabel="1.05M of 1M"
4722
+ hard={false}
4723
+ resets="Resets at 00:00 UTC"
4724
+ />
4725
+ </div>
4726
+ ```
4727
+
4728
+ ## VisuallyHidden
4729
+
4730
+ Utilities: text for assistive technology only, a portal to the body, and the internal Surface.
4731
+
4732
+ `import { VisuallyHidden, Portal } from "@logicplanes/kaeso-ui";`
4733
+
4734
+ ### Accessibility
4735
+
4736
+ - Surface is internal: it draws only the framed surfaces of the foundations, never a generic card.
4737
+
4738
+ ### Examples
4739
+
4740
+ #### VisuallyHidden
4741
+
4742
+ ```tsx
4743
+ <button type="button" className="inline-flex h-36 items-center rounded-pill px-16 text-body font-medium material-secondary focus-ring">
4744
+ Export
4745
+ <VisuallyHidden> request logs as CSV</VisuallyHidden>
4746
+ </button>
4747
+ ```
4748
+
4749
+ #### Surface kinds
4750
+
4751
+ ```tsx
4752
+ <div className="grid grid-cols-2 gap-16 sm:grid-cols-4">
4753
+ {(["panel", "menu", "group", "well"] as const).map((k) => (
4754
+ <Surface key={k} kind={k} className="flex h-80 w-140 items-center justify-center text-small text-fg-secondary">
4755
+ {k}
4756
+ </Surface>
4757
+ ))}
4758
+ </div>
4759
+ ```
4760
+