@kerfjs/ui 5.0.0-beta.97 → 5.0.0-beta.99

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 (416) hide show
  1. package/README.md +42 -1
  2. package/ai/application-ui-diagnostic-ids-v1.json +1 -0
  3. package/ai/component-catalog.json +487 -66
  4. package/ai/component-catalog.schema.json +1 -1
  5. package/ai/component-composition-rules.json +22 -2
  6. package/ai/component-composition.json +360 -33
  7. package/ai/component-composition.schema.json +1 -1
  8. package/ai/components/README.md +18 -17
  9. package/ai/components/anchored-surface.md +13 -0
  10. package/ai/components/application-tabs.md +1 -1
  11. package/ai/components/chip.md +9 -5
  12. package/ai/components/choice-chips.md +15 -8
  13. package/ai/components/collapsible-panel.md +3 -2
  14. package/ai/components/composer.md +83 -0
  15. package/ai/components/content-item.md +2 -2
  16. package/ai/components/corner-status.md +1 -1
  17. package/ai/components/document-baseline.md +3 -3
  18. package/ai/components/foundation.md +2 -2
  19. package/ai/components/layout.md +1 -1
  20. package/ai/components/list-action-row.md +5 -1
  21. package/ai/components/list-item.md +5 -1
  22. package/ai/components/list.md +19 -3
  23. package/ai/components/nav-stack.md +11 -2
  24. package/ai/components/pane.md +13 -4
  25. package/ai/components/popup-menu.md +4 -4
  26. package/ai/components/recipe-app-shell.md +3 -3
  27. package/ai/components/recipe-collapsible-sidebar.md +3 -3
  28. package/ai/components/recipe-compact-toolbar.md +1 -1
  29. package/ai/components/recipe-composer-form.md +3 -3
  30. package/ai/components/recipe-list-detail-dialog.md +2 -2
  31. package/ai/components/recipe-list-workspace-states.md +3 -3
  32. package/ai/components/recipe-loading-inspector.md +1 -1
  33. package/ai/components/recipe-navigation-sidebar.md +3 -3
  34. package/ai/components/recipe-navigation-stack.md +3 -3
  35. package/ai/components/recipe-workspace-header.md +3 -3
  36. package/ai/components/resize.md +2 -2
  37. package/ai/components/split-view.md +4 -2
  38. package/ai/components/sunken-panel.md +2 -2
  39. package/ai/components/surface-scaffold.md +15 -3
  40. package/ai/components/tab-bar.md +1 -0
  41. package/ai/components/tab-navigator.md +4 -0
  42. package/ai/components/toolbar-control-group.md +2 -0
  43. package/ai/components/toolbar-text.md +1 -0
  44. package/ai/components/toolbar.md +1 -0
  45. package/ai/components/workbench.md +1 -0
  46. package/ai/public-api-signatures-v1.md +504 -80
  47. package/ai/skill.md +41 -14
  48. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  49. package/analyzer/component-ownership.mjs +1 -1
  50. package/analyzer/index.mjs +41 -14
  51. package/dist/anchored-surface.d.ts +30 -2
  52. package/dist/anchored-surface.js +1 -1
  53. package/dist/app-tab.js +1 -1
  54. package/dist/browser/composer.js +4 -0
  55. package/dist/catalog-resources.d.ts +3 -2
  56. package/dist/catalog-resources.js +4 -4
  57. package/dist/catalog-resources.js.map +1 -1
  58. package/dist/catalog.d.ts +39 -8
  59. package/dist/catalog.js +55 -32
  60. package/dist/catalog.js.map +1 -1
  61. package/dist/chip.d.ts +41 -9
  62. package/dist/chip.js +1 -1
  63. package/dist/choice-chips.d.ts +40 -16
  64. package/dist/choice-chips.js +1 -1
  65. package/dist/chunk-34U4ANDJ.js +119 -0
  66. package/dist/chunk-34U4ANDJ.js.map +1 -0
  67. package/dist/{chunk-36TL6BCR.js → chunk-3GLYIIFR.js} +34 -6
  68. package/dist/chunk-3GLYIIFR.js.map +1 -0
  69. package/dist/{chunk-HWY4BSLS.js → chunk-3VXDSIBJ.js} +2 -2
  70. package/dist/{chunk-HWY4BSLS.js.map → chunk-3VXDSIBJ.js.map} +1 -1
  71. package/dist/chunk-3WYREXW4.js +120 -0
  72. package/dist/chunk-3WYREXW4.js.map +1 -0
  73. package/dist/chunk-AV7CPAPT.js +82 -0
  74. package/dist/chunk-AV7CPAPT.js.map +1 -0
  75. package/dist/{chunk-XK4E225T.js → chunk-B53PS2LE.js} +2 -2
  76. package/dist/{chunk-XK4E225T.js.map → chunk-B53PS2LE.js.map} +1 -1
  77. package/dist/{chunk-R42D2BAO.js → chunk-CA7LNX3J.js} +2 -2
  78. package/dist/{chunk-R42D2BAO.js.map → chunk-CA7LNX3J.js.map} +1 -1
  79. package/dist/{chunk-AYHC62OZ.js → chunk-CMETHPKR.js} +26 -29
  80. package/dist/chunk-CMETHPKR.js.map +1 -0
  81. package/dist/{chunk-Y43B6DS3.js → chunk-CVD5IZGB.js} +2 -2
  82. package/dist/{chunk-Y43B6DS3.js.map → chunk-CVD5IZGB.js.map} +1 -1
  83. package/dist/{chunk-6STQXRT4.js → chunk-CXZXHZTR.js} +121 -12
  84. package/dist/chunk-CXZXHZTR.js.map +1 -0
  85. package/dist/{chunk-GZE2XVKC.js → chunk-DZJ4GBSP.js} +3 -3
  86. package/dist/{chunk-GZE2XVKC.js.map → chunk-DZJ4GBSP.js.map} +1 -1
  87. package/dist/chunk-ELCMSQNJ.js +49 -0
  88. package/dist/chunk-ELCMSQNJ.js.map +1 -0
  89. package/dist/{chunk-P74VXMB6.js → chunk-EOO6PYGF.js} +7 -14
  90. package/dist/chunk-EOO6PYGF.js.map +1 -0
  91. package/dist/{chunk-PRG4CL3Q.js → chunk-FR76ZJQC.js} +21 -19
  92. package/dist/chunk-FR76ZJQC.js.map +1 -0
  93. package/dist/{chunk-QQ3TTWFH.js → chunk-G3J7JDP6.js} +1 -1
  94. package/dist/{chunk-QQ3TTWFH.js.map → chunk-G3J7JDP6.js.map} +1 -1
  95. package/dist/{chunk-IGTWXPTN.js → chunk-HGV7J4T6.js} +3 -3
  96. package/dist/{chunk-IGTWXPTN.js.map → chunk-HGV7J4T6.js.map} +1 -1
  97. package/dist/{chunk-YWQGSUF2.js → chunk-HLDRH23D.js} +3 -3
  98. package/dist/{chunk-YWQGSUF2.js.map → chunk-HLDRH23D.js.map} +1 -1
  99. package/dist/{chunk-PBT3CPPY.js → chunk-HQZYKJ56.js} +24 -4
  100. package/dist/chunk-HQZYKJ56.js.map +1 -0
  101. package/dist/{chunk-V6W56V3N.js → chunk-HZPHCSCS.js} +16 -3
  102. package/dist/chunk-HZPHCSCS.js.map +1 -0
  103. package/dist/{chunk-GJYQ27C2.js → chunk-INI4WDWC.js} +8 -1
  104. package/dist/chunk-INI4WDWC.js.map +1 -0
  105. package/dist/{chunk-U657IHFB.js → chunk-IQSRHXZR.js} +10 -4
  106. package/dist/chunk-IQSRHXZR.js.map +1 -0
  107. package/dist/{chunk-KQDU7Z7P.js → chunk-K4AT3V3J.js} +2 -1
  108. package/dist/chunk-K4AT3V3J.js.map +1 -0
  109. package/dist/{chunk-QIAPOWHH.js → chunk-KCS6NJ3O.js} +2 -2
  110. package/dist/{chunk-QIAPOWHH.js.map → chunk-KCS6NJ3O.js.map} +1 -1
  111. package/dist/{chunk-UKKRPP56.js → chunk-LOSRXZAH.js} +2 -2
  112. package/dist/{chunk-UKKRPP56.js.map → chunk-LOSRXZAH.js.map} +1 -1
  113. package/dist/{chunk-ROZLJULF.js → chunk-MF6ZUUJZ.js} +8 -3
  114. package/dist/chunk-MF6ZUUJZ.js.map +1 -0
  115. package/dist/{chunk-6IVNMC6R.js → chunk-MHMWQ7RB.js} +2 -2
  116. package/dist/{chunk-6IVNMC6R.js.map → chunk-MHMWQ7RB.js.map} +1 -1
  117. package/dist/{chunk-JYD7G4RG.js → chunk-PIVY32AE.js} +15 -5
  118. package/dist/chunk-PIVY32AE.js.map +1 -0
  119. package/dist/{chunk-BU23Z5HZ.js → chunk-PYBBYSZ3.js} +13 -18
  120. package/dist/chunk-PYBBYSZ3.js.map +1 -0
  121. package/dist/chunk-QDSZTQXL.js +28 -0
  122. package/dist/chunk-QDSZTQXL.js.map +1 -0
  123. package/dist/{chunk-YXOMI7ES.js → chunk-QIG4IGUP.js} +2 -2
  124. package/dist/{chunk-YXOMI7ES.js.map → chunk-QIG4IGUP.js.map} +1 -1
  125. package/dist/{chunk-6BRRUPJY.js → chunk-QROYYGWU.js} +3 -3
  126. package/dist/chunk-QROYYGWU.js.map +1 -0
  127. package/dist/{chunk-FOQK4F3L.js → chunk-RHD7RKGI.js} +28 -7
  128. package/dist/chunk-RHD7RKGI.js.map +1 -0
  129. package/dist/{chunk-BBU2SPRE.js → chunk-T4WJ5VGP.js} +4 -4
  130. package/dist/{chunk-BBU2SPRE.js.map → chunk-T4WJ5VGP.js.map} +1 -1
  131. package/dist/{chunk-6NOLL5KG.js → chunk-TGLUDQP2.js} +3 -3
  132. package/dist/{chunk-6NOLL5KG.js.map → chunk-TGLUDQP2.js.map} +1 -1
  133. package/dist/{chunk-HO6FU3G7.js → chunk-TLMMFQWM.js} +4 -4
  134. package/dist/{chunk-HO6FU3G7.js.map → chunk-TLMMFQWM.js.map} +1 -1
  135. package/dist/chunk-UMKV7YVQ.js +20 -0
  136. package/dist/chunk-UMKV7YVQ.js.map +1 -0
  137. package/dist/{chunk-M2NRCFRO.js → chunk-VFYO3UGZ.js} +2 -2
  138. package/dist/{chunk-M2NRCFRO.js.map → chunk-VFYO3UGZ.js.map} +1 -1
  139. package/dist/{chunk-YOKEL75M.js → chunk-WVS6CUCT.js} +2 -2
  140. package/dist/{chunk-YOKEL75M.js.map → chunk-WVS6CUCT.js.map} +1 -1
  141. package/dist/{chunk-MN3POJFN.js → chunk-YMUIQRWK.js} +2 -2
  142. package/dist/{chunk-MN3POJFN.js.map → chunk-YMUIQRWK.js.map} +1 -1
  143. package/dist/{chunk-Y3LC7JQO.js → chunk-YOA5W5ZH.js} +2 -2
  144. package/dist/{chunk-Y3LC7JQO.js.map → chunk-YOA5W5ZH.js.map} +1 -1
  145. package/dist/{chunk-44A6LP54.js → chunk-YQ2AC4E2.js} +2 -2
  146. package/dist/{chunk-44A6LP54.js.map → chunk-YQ2AC4E2.js.map} +1 -1
  147. package/dist/chunk-ZNROKDSZ.js +54 -0
  148. package/dist/chunk-ZNROKDSZ.js.map +1 -0
  149. package/dist/collapsible-panel.d.ts +3 -2
  150. package/dist/collapsible-panel.js +4 -3
  151. package/dist/collapsible-panel.js.map +1 -1
  152. package/dist/composer.d.ts +62 -0
  153. package/dist/composer.js +5 -0
  154. package/dist/composer.js.map +1 -0
  155. package/dist/corner-status.d.ts +1 -1
  156. package/dist/corner-status.js +1 -1
  157. package/dist/css-values.d.ts +9 -1
  158. package/dist/css-values.js +1 -1
  159. package/dist/disclosure-arrow.js +1 -1
  160. package/dist/empty-state.js +1 -1
  161. package/dist/grid.js +1 -1
  162. package/dist/icon-button.js +1 -1
  163. package/dist/icon-toggle-button.js +1 -1
  164. package/dist/index.d.ts +10 -5
  165. package/dist/index.js +40 -37
  166. package/dist/list-action-row.d.ts +10 -2
  167. package/dist/list-action-row.js +1 -1
  168. package/dist/list-header.js +1 -1
  169. package/dist/list-icon-tile-B8dr97OW.d.ts +18 -0
  170. package/dist/list-item.d.ts +10 -3
  171. package/dist/list-item.js +1 -1
  172. package/dist/list.d.ts +36 -4
  173. package/dist/list.js +1 -1
  174. package/dist/loading-spinner.js +1 -1
  175. package/dist/lucide-icon.js +1 -1
  176. package/dist/{nav-stack-BvSKY448.d.ts → nav-stack-VXTumzKa.d.ts} +53 -4
  177. package/dist/nav-stack.d.ts +3 -2
  178. package/dist/nav-stack.js +2 -1
  179. package/dist/pane.d.ts +40 -3
  180. package/dist/pane.js +1 -1
  181. package/dist/{panel-toolbar-CL9tYOkh.d.ts → panel-toolbar-vg6Ir5Dy.d.ts} +6 -1
  182. package/dist/popup-menu-register.d.ts +2 -1
  183. package/dist/popup-menu-register.js +97 -22
  184. package/dist/popup-menu-register.js.map +1 -1
  185. package/dist/popup-menu.js +1 -1
  186. package/dist/row.js +1 -1
  187. package/dist/scroll-clearance-BikHljTc.d.ts +14 -0
  188. package/dist/select-register.js +11 -0
  189. package/dist/select-register.js.map +1 -1
  190. package/dist/select.js +1 -1
  191. package/dist/skeleton.js +1 -1
  192. package/dist/spacer.js +1 -1
  193. package/dist/split-button.js +1 -1
  194. package/dist/split-view.d.ts +7 -5
  195. package/dist/split-view.js +6 -3
  196. package/dist/split-view.js.map +1 -1
  197. package/dist/state-banner.d.ts +6 -1
  198. package/dist/state-banner.js +1 -1
  199. package/dist/styles/anchored-surface.css +27 -1
  200. package/dist/styles/catalog/components/catalog-example-stack.css +5 -0
  201. package/dist/styles/catalog/components/catalog-example.css +33 -0
  202. package/dist/styles/catalog/components/catalog-stage.css +7 -0
  203. package/dist/styles/chip.css +70 -0
  204. package/dist/styles/components/actions/popup-menu/popup-menu.css +21 -4
  205. package/dist/styles/components/actions/toolbar/toolbar.css +19 -0
  206. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +75 -1
  207. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +75 -0
  208. package/dist/styles/components/collections/list/list.css +77 -7
  209. package/dist/styles/components/collections/list-action-row/list-action-row.css +177 -22
  210. package/dist/styles/components/collections/list-item/list-item.css +158 -11
  211. package/dist/styles/components/feedback/anchored-surface/anchored-surface.css +27 -1
  212. package/dist/styles/components/feedback/chip/chip.css +70 -0
  213. package/dist/styles/components/feedback/state-banner/state-banner.css +24 -13
  214. package/dist/styles/components/forms/composer/composer.css +100 -0
  215. package/dist/styles/components/forms/select/select.css +1 -0
  216. package/dist/styles/components/forms/token-search-field/token-search-field.css +13 -2
  217. package/dist/styles/components/layout/pane/pane.css +81 -2
  218. package/dist/styles/components/navigation/nav-stack/nav-stack.css +84 -0
  219. package/dist/styles/components/navigation/tab-bar/tab-bar.css +15 -4
  220. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +79 -4
  221. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +195 -0
  222. package/dist/styles/composer.css +100 -0
  223. package/dist/styles/foundation.css +66 -0
  224. package/dist/styles/list-action-row.css +177 -22
  225. package/dist/styles/list-item.css +158 -11
  226. package/dist/styles/list.css +77 -7
  227. package/dist/styles/nav-stack.css +84 -0
  228. package/dist/styles/pane.css +81 -2
  229. package/dist/styles/popup-menu.css +21 -4
  230. package/dist/styles/select.css +1 -0
  231. package/dist/styles/state-banner.css +24 -13
  232. package/dist/styles/styles.css +1 -0
  233. package/dist/styles/surface-scaffold.css +195 -0
  234. package/dist/styles/tab-bar.css +15 -4
  235. package/dist/styles/tab-navigator.css +79 -4
  236. package/dist/styles/token-search-field.css +13 -2
  237. package/dist/styles/toolbar-control-group.css +75 -1
  238. package/dist/styles/toolbar-text.css +75 -0
  239. package/dist/styles/toolbar.css +19 -0
  240. package/dist/styles/webawesome.css +18 -0
  241. package/dist/surface-scaffold-register.d.ts +6 -1
  242. package/dist/surface-scaffold-register.js +89 -3
  243. package/dist/surface-scaffold-register.js.map +1 -1
  244. package/dist/surface-scaffold.d.ts +66 -7
  245. package/dist/surface-scaffold.js +1 -1
  246. package/dist/tab-navigator.d.ts +23 -2
  247. package/dist/tab-navigator.js +2 -0
  248. package/dist/tab-navigator.js.map +1 -1
  249. package/dist/token-search-field.d.ts +1 -1
  250. package/dist/token-search-field.js +1 -1
  251. package/dist/{token-search-model-DTcTXWMS.d.ts → token-search-model-aYR0YAei.d.ts} +32 -2
  252. package/dist/token-search-model.d.ts +1 -1
  253. package/dist/toolbar-control-group.js +1 -1
  254. package/dist/toolbar-text.js +1 -1
  255. package/dist/toolbar.d.ts +1 -0
  256. package/dist/value-table.js +1 -1
  257. package/dist/wire-catalog.d.ts +3 -2
  258. package/dist/wire-catalog.js +2 -2
  259. package/dist/wire-composers.d.ts +16 -0
  260. package/dist/wire-composers.js +3 -0
  261. package/dist/wire-composers.js.map +1 -0
  262. package/dist/wire-list-focus-scroll.d.ts +16 -0
  263. package/dist/wire-list-focus-scroll.js +3 -0
  264. package/dist/wire-list-focus-scroll.js.map +1 -0
  265. package/dist/wire-nav-stack.js +135 -0
  266. package/dist/wire-nav-stack.js.map +1 -1
  267. package/dist/wire-scroll-dividers.d.ts +9 -0
  268. package/dist/wire-scroll-dividers.js +1 -1
  269. package/dist/wire-tab-bars.js +1 -1
  270. package/dist/wire-tab-navigator.d.ts +3 -1
  271. package/dist/wire-tab-navigator.js +133 -1
  272. package/dist/wire-tab-navigator.js.map +1 -1
  273. package/dist/wire-token-search-fields.d.ts +1 -1
  274. package/dist/wire-token-search-fields.js +1 -1
  275. package/dist/workbench.d.ts +8 -3
  276. package/dist/workbench.js +2 -1
  277. package/docs/accessibility.md +132 -3
  278. package/docs/anchored-surface.md +46 -4
  279. package/docs/app-layouts.md +23 -2
  280. package/docs/apple-hig.md +1 -0
  281. package/docs/catalog.md +61 -9
  282. package/docs/collapsible-panel.md +9 -6
  283. package/docs/component-contract.md +46 -13
  284. package/docs/component-selection.md +147 -20
  285. package/docs/composer.md +143 -0
  286. package/docs/design/templates/choice-chips/multiple-dark.svg +1 -0
  287. package/docs/design/templates/choice-chips/multiple.svg +1 -0
  288. package/docs/design/templates/choice-chips-dark.svg +1 -1
  289. package/docs/design/templates/choice-chips.svg +1 -1
  290. package/docs/design/templates/composer/chat-dark.svg +1 -0
  291. package/docs/design/templates/composer/chat.svg +1 -0
  292. package/docs/design/templates/composer/prompt-dark.svg +1 -0
  293. package/docs/design/templates/composer/prompt.svg +1 -0
  294. package/docs/design/templates/composer-dark.svg +1 -0
  295. package/docs/design/templates/composer.svg +1 -0
  296. package/docs/design/templates/empty-state/filtered-dark.svg +1 -1
  297. package/docs/design/templates/empty-state/filtered.svg +1 -1
  298. package/docs/design/templates/empty-state/with-action-dark.svg +1 -1
  299. package/docs/design/templates/empty-state/with-action.svg +1 -1
  300. package/docs/design/templates/empty-state-dark.svg +1 -1
  301. package/docs/design/templates/empty-state.svg +1 -1
  302. package/docs/design/templates/list/compact-dark.svg +1 -1
  303. package/docs/design/templates/list/compact.svg +1 -1
  304. package/docs/design/templates/list-action-row/default-dark.svg +1 -1
  305. package/docs/design/templates/list-action-row/default.svg +1 -1
  306. package/docs/design/templates/list-action-row/icon-tile-dark.svg +1 -0
  307. package/docs/design/templates/list-action-row/icon-tile.svg +1 -0
  308. package/docs/design/templates/list-action-row/selected-dark.svg +1 -1
  309. package/docs/design/templates/list-action-row/selected.svg +1 -1
  310. package/docs/design/templates/list-action-row/trailing-menu-dark.svg +1 -1
  311. package/docs/design/templates/list-action-row/trailing-menu.svg +1 -1
  312. package/docs/design/templates/list-action-row-dark.svg +1 -1
  313. package/docs/design/templates/list-action-row.svg +1 -1
  314. package/docs/design/templates/list-dark.svg +1 -1
  315. package/docs/design/templates/list-header/size-l-dark.svg +1 -1
  316. package/docs/design/templates/list-header/size-l.svg +1 -1
  317. package/docs/design/templates/list-header/size-m-dark.svg +1 -1
  318. package/docs/design/templates/list-header/size-m.svg +1 -1
  319. package/docs/design/templates/list-header/size-xl-dark.svg +1 -1
  320. package/docs/design/templates/list-header/size-xl.svg +1 -1
  321. package/docs/design/templates/list-header-dark.svg +1 -1
  322. package/docs/design/templates/list-header.svg +1 -1
  323. package/docs/design/templates/list-item/icon-tile-dark.svg +1 -0
  324. package/docs/design/templates/list-item/icon-tile.svg +1 -0
  325. package/docs/design/templates/list-item/selected-dark.svg +1 -1
  326. package/docs/design/templates/list-item/selected.svg +1 -1
  327. package/docs/design/templates/list-item-dark.svg +1 -1
  328. package/docs/design/templates/list-item.svg +1 -1
  329. package/docs/design/templates/list.svg +1 -1
  330. package/docs/design/templates/segmented-control-dark.svg +1 -1
  331. package/docs/design/templates/segmented-control.svg +1 -1
  332. package/docs/design/templates/skeleton-dark.svg +1 -1
  333. package/docs/design/templates/skeleton.svg +1 -1
  334. package/docs/design/templates/state-banner/danger-dark.svg +1 -1
  335. package/docs/design/templates/state-banner/danger.svg +1 -1
  336. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  337. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  338. package/docs/design/templates/state-banner/info-dark.svg +1 -1
  339. package/docs/design/templates/state-banner/info.svg +1 -1
  340. package/docs/design/templates/state-banner/strip-dark.svg +1 -1
  341. package/docs/design/templates/state-banner/strip.svg +1 -1
  342. package/docs/design/templates/state-banner/success-dark.svg +1 -1
  343. package/docs/design/templates/state-banner/success.svg +1 -1
  344. package/docs/design/templates/state-banner/warning-dark.svg +1 -1
  345. package/docs/design/templates/state-banner/warning.svg +1 -1
  346. package/docs/design/templates/state-banner-dark.svg +1 -1
  347. package/docs/design/templates/state-banner.svg +1 -1
  348. package/docs/design/templates/tab-bar-dark.svg +1 -1
  349. package/docs/design/templates/tab-bar.svg +1 -1
  350. package/docs/design/templates/token-search-field-dark.svg +1 -1
  351. package/docs/design/templates/token-search-field.svg +1 -1
  352. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  353. package/docs/design/templates/toolbar-control-group.svg +1 -1
  354. package/docs/design/templates/toolbar-dark.svg +1 -1
  355. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  356. package/docs/design/templates/toolbar-text/default.svg +1 -1
  357. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  358. package/docs/design/templates/toolbar-text/large.svg +1 -1
  359. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  360. package/docs/design/templates/toolbar-text.svg +1 -1
  361. package/docs/design/templates/toolbar.svg +1 -1
  362. package/docs/design/templates/value-table/details-dark.svg +1 -1
  363. package/docs/design/templates/value-table/details.svg +1 -1
  364. package/docs/design/templates/value-table-dark.svg +1 -1
  365. package/docs/design/templates/value-table.svg +1 -1
  366. package/docs/design-philosophy.md +9 -1
  367. package/docs/examples/command-palette-adapter.tsx +3 -3
  368. package/docs/layout.md +147 -14
  369. package/docs/nav-stack.md +132 -8
  370. package/docs/recipes.md +9 -9
  371. package/docs/split-view.md +3 -3
  372. package/docs/sunken-panel.md +2 -2
  373. package/docs/surface-scaffold.md +138 -7
  374. package/docs/tab-navigator.md +38 -1
  375. package/docs/type-contracts.md +16 -16
  376. package/docs/ui-analyzer.md +3 -1
  377. package/docs/ui-doctor.md +71 -0
  378. package/docs/ux-demo.md +6 -6
  379. package/docs/webawesome-theme.md +7 -0
  380. package/docs/workbench.md +10 -6
  381. package/docs/writing.md +152 -0
  382. package/doctor/copy-capitalization-checks.mjs +591 -0
  383. package/doctor/index.mjs +17 -8
  384. package/llms.txt +105 -11
  385. package/package.json +15 -1
  386. package/ux-demo/recipes/app-shell.tsx +11 -11
  387. package/ux-demo/recipes/collapsible-sidebar.tsx +12 -11
  388. package/ux-demo/recipes/compact-toolbar.tsx +10 -10
  389. package/ux-demo/recipes/composer-form.tsx +8 -8
  390. package/ux-demo/recipes/list-detail-dialog.tsx +13 -13
  391. package/ux-demo/recipes/list-workspace-states.tsx +13 -13
  392. package/ux-demo/recipes/loading-inspector.tsx +10 -10
  393. package/ux-demo/recipes/navigation-sidebar.tsx +7 -7
  394. package/ux-demo/recipes/navigation-stack.tsx +5 -5
  395. package/ux-demo/recipes/workspace-header.tsx +5 -5
  396. package/dist/chunk-36TL6BCR.js.map +0 -1
  397. package/dist/chunk-4LOK4634.js +0 -21
  398. package/dist/chunk-4LOK4634.js.map +0 -1
  399. package/dist/chunk-6BRRUPJY.js.map +0 -1
  400. package/dist/chunk-6STQXRT4.js.map +0 -1
  401. package/dist/chunk-76PWEN5Y.js +0 -64
  402. package/dist/chunk-76PWEN5Y.js.map +0 -1
  403. package/dist/chunk-AYHC62OZ.js.map +0 -1
  404. package/dist/chunk-BU23Z5HZ.js.map +0 -1
  405. package/dist/chunk-DQDTZE4W.js +0 -70
  406. package/dist/chunk-DQDTZE4W.js.map +0 -1
  407. package/dist/chunk-FOQK4F3L.js.map +0 -1
  408. package/dist/chunk-GJYQ27C2.js.map +0 -1
  409. package/dist/chunk-JYD7G4RG.js.map +0 -1
  410. package/dist/chunk-KQDU7Z7P.js.map +0 -1
  411. package/dist/chunk-P74VXMB6.js.map +0 -1
  412. package/dist/chunk-PBT3CPPY.js.map +0 -1
  413. package/dist/chunk-PRG4CL3Q.js.map +0 -1
  414. package/dist/chunk-ROZLJULF.js.map +0 -1
  415. package/dist/chunk-U657IHFB.js.map +0 -1
  416. package/dist/chunk-V6W56V3N.js.map +0 -1
@@ -61,6 +61,13 @@ mockup or another application:
61
61
  The package's defaults are the starting design, not an empty canvas. A custom
62
62
  component or rule should explain which missing behavior or structure it owns.
63
63
 
64
+ Write the copy by the package rule as you choose components: a component's
65
+ naming props (button and menu-item labels, segment and `Select` choices,
66
+ `ToolbarText` titles, `ListHeader` labels, field labels) take title style, and
67
+ its describing props (`description`, `detail`, hints, placeholders, tooltips,
68
+ option text, status) take sentence style. See
69
+ [Writing interface copy](./writing.md#capitalization).
70
+
64
71
  **Don't fight the components.** The package is built to look right without an
65
72
  application customization layer. Before adding `padding`, `margin`, `width`,
66
73
  `height`, `border`, `background`, a wrapper card, or a decoration, check whether
@@ -187,8 +194,14 @@ selection entries.
187
194
  For arbitrary interactive help or popup content beside a control, use
188
195
  [`openAnchoredSurface`](anchored-surface.md) with an accessible label; use
189
196
  `openAnchoredSurfaceAt` for pointer coordinates. It owns the Kerf UI surface,
190
- placement, focus, dismissal, and modal-dialog hosting while the app owns its
191
- content and action policy. Use `PopupMenu` for a typed command list.
197
+ placement (an 8px viewport gutter, and a height fitted to the room beside the
198
+ anchor with scrolling content), focus, dismissal, and modal-dialog hosting while
199
+ the app owns its content and action policy. Never add media queries or
200
+ `max-height` overrides to make a surface fit a phone. For a popup anchored to
201
+ dark media chrome (a `ToolbarControlGroup tone="dark"` over a preview), pass
202
+ `tone: 'dark'` rather than building an app-owned dark panel: the surface takes
203
+ the group's inverse tone and its content inherits the matching colors. Use
204
+ `PopupMenu` for a typed command list.
192
205
 
193
206
  Kerf UI does not export a command-palette component. Do not invent a package
194
207
  command-palette import.
@@ -229,6 +242,7 @@ beside a `ContentItem` value, and connect its id to the preview group's
229
242
  | Self-contained content child — `ContentItem` | An ordinary surface-like child of a `Pane` or `.kui-content` stack needs the shared 8px margin / 1px border / 8px padding geometry; pass `frame="framed"` only when it marks a real distinction, `appearance="surface"` or a semantic appearance for coordinated surface colors, and `shape="pill"` for the 22px radius. | Do not wrap a component that already owns content-item geometry (`ListItem`, `ListHeader`, `StateBanner`, `Toolbar`, `ValueTable`) or pad around it — that double-insets. Do not frame an item only to make it look contained. Use `SunkenPanel` for a lowered inset stack and `ListInsetText` for bare text that only needs to align. | Pass the item content as children; choose `appearance` (`transparent` default, `surface`, or a semantic status), `frame`, and `shape` instead of writing CSS. Put safe `data-*` metadata in `rootAttributes`; `ariaLabel` names a distinct region and `focusTarget` makes it a programmatic focus target. | The content, surface or status appearance, and whether it marks a distinction worth a visible frame. | `@kerfjs/ui/content-item` | [Content items](./layout.md#content-items) |
230
243
  | Application column — `Pane` | A sidebar, main area, inspector, or dialog needs shared vertical header/content/footer organization, one scroll owner, and optional edge separators. | Do not pad the pane shell, wrap child-owned geometry in competing insets, invent unrelated centered measures, or leave an icon-only rail for a hidden pane. | Import `Pane`, use `children` for its homogeneous primary content and the named `SafeHtml` prop slots `header`/`footer` for optional fixed chrome, then opt into only the needed logical-edge separators; add `ContentItem` children as needed. A visible pane owns collapse in its toolbar; move a hidden inline-start pane's restore control to the main toolbar leading edge and an inline-end pane's restore control to its trailing edge. | Layout hierarchy, reading width, responsive relocation, pane visibility state, slot semantics, labels, and separator placement. | `@kerfjs/ui/pane` | [Pane anatomy](./layout.md#anatomy) |
231
244
  | Scroll dividers — `wireScrollDividers` | Pinned chrome (a `Pane` header or footer, a `NavStack` top chrome or bottom toolbar, a `TabNavigator` bar, the sides of a `TabBar` strip, or app-owned chrome around a scroller) needs a divider only while content is scrolled away beneath it. | Do not keep a static toolbar divider to fake it or write a border onto another component; each component draws its own line from the wiring's state. | Call `wireScrollDividers` once at the app root and retain the disposer; it covers every `Pane`, `NavStack`, `TabNavigator`, and `TabBar` below it. Name app-owned arrangements in `targets` by id; a `Toolbar` or `List` named as chrome draws its facing side. Configure a pane with `chromeDividers` (`scroll` default, `always`, `none`). | Which panes follow the scroll, app-owned targets, and the disposer. | `@kerfjs/ui/wire-scroll-dividers` | [Scroll dividers](./layout.md#scroll-dividers) |
245
+ | Keyboard focus scroll in scrollable Lists — `wireListFocusScroll` | Rows of a `scrollable` List are reached by keyboard (Tab, or the app's own arrow-key focus moves) and must come fully into view, focus ring included, in Firefox and Safari too: their `focus()` leaves a partly hidden row clipped. | Do not call `scrollIntoView` from app focus handlers or style the List; do not wire it for pointer-only lists. | Call `wireListFocusScroll(root)` once at the app root and retain the disposer. On keyboard (`:focus-visible`) focus inside a `scrollable` List it scrolls the focused element into view only when its ring crosses the clip; pointer focus is left alone. | The disposer. | `@kerfjs/ui/wire-list-focus-scroll` | [Framed result pickers](./layout.md#framed-result-pickers) |
232
246
  | One-pane navigation — `NavStack` | A compact flow pushes into details while keeping earlier views mounted, or a single-pane screen needs the same stable layout contract. | Do not use it when list and detail should remain visible together; use `SplitView`. Do not use it alone for co-equal top-level destinations; use one stack per `TabNavigator` tab. | Keep the ordered view array controlled, put view-specific bottom chrome on each view (or use the component fallback), import its CSS explicitly, and call `wireNavStack` for back delegation, content-slide/chrome-cross-fade transitions, and focus move/restore. Mark a preferred initial target with `data-nav-focus` when DOM order is insufficient. | View keys/content/titles, push and pop actions, preferred initial focus targets, and per-view or persistent bottom toolbar content. | `@kerfjs/ui/nav-stack` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
233
247
  | Responsive list-detail — `SplitView` | A primary list and related detail belong together on roomy devices but become a one-pane drill-down on compact devices. | Do not use it for a single linear flow or a multi-panel editor shell. | Provide controlled selection/detail state and device class; opt into resize wiring only when the separator is user adjustable. | Selection, detail content, compact back action, device policy, and optional persisted width. | `@kerfjs/ui/split-view` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
234
248
  | Compact top-level destinations — `TabNavigator` | A handset or portrait-tablet app has two to five co-equal destinations whose scenes and nested navigation must remain mounted across switches. | Do not use it for document tabs, reorderable work items, or a desktop navigation rail. | Keep the active id controlled, import its CSS explicitly, and call `wireTabNavigator`; commonly place one `NavStack` in each tab scene. | Destination definitions, active id, routing, persistence, and roomy-device replacement. | `@kerfjs/ui/tab-navigator` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
@@ -242,7 +256,7 @@ beside a `ContentItem` value, and connect its id to the preview group's
242
256
  | Panel, dialog, or page heading — `Toolbar` + `ToolbarText` | A panel, dialog, or page needs an extra-large title, optional icon, grouped trailing actions, and optional supporting copy. | Do not add a private heading wrapper or bespoke layout. The toolbar supplies structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. In a narrow peripheral rail or drawer, where xlarge would truncate at the panel's minimum size, use the default `ToolbarText` size as pane identity. | Connect title/supporting-copy ids to the host, group controls, and delegate actions. **For a page or view title set `headingLevel` (usually `1`)** on `ToolbarText`; omit it for a dialog title referenced by `aria-labelledby`. | Open state, focus lifecycle, dismissal, controls, ids, copy, and heading level. | `@kerfjs/ui/toolbar` + `@kerfjs/ui/toolbar-text` | [Toolbar headings](./accessibility.md#toolbar-headings) |
243
257
  | Dialog or popup geometry — `DialogSurface`, `PopoverDialogSurface`, `PopupSurface` | Recurring Web Awesome dialogs need standard sizes/presentations/body/footer insets, typed modal viewport bounds, a definite height, or a body one child fills; a dialog must stay non-modal and light-dismiss; or dropdown menus need list-compatible inset ownership. | Do not use these wrappers as generic layout or to replace native dialog/dropdown behavior. Avoid consumer `::part()` overrides and do not stack a dialog body inset around list-owned child geometry. | Wrap one `wa-dialog` or `wa-dropdown`; choose the typed geometry while retaining native labeling, open state, focus, dismissal, and menu semantics. Compose a dialog body as a `List` by default, use `bodyInset="none"` when its children own list geometry, and wrap bare prose in `ListInsetText`. Set `height` with `bodyLayout="fill"` when one `SplitView` or `Pane` should fill the body. For a non-modal, light-dismiss dialog render `PopoverDialogSurface` (native `popover="auto"`, opened by a `popoverTarget` control or `showPopover()`), never a hand-built popover with app chrome CSS; `presentation="embedded"` shows it in flow. | State, focus lifecycle, dismissal, labels, content, and outer placement. | `@kerfjs/ui/surface-scaffold` | [Surface scaffolds](./surface-scaffold.md) |
244
258
  | Key/value facts — `ValueTable`, `ValueTableRow` | Read-only labels and values form a semantic definition list, optionally with a leading icon. | Do not use it for editable form fields or a row/column data grid; use native form or table semantics. | Compose typed `ValueTableRow` entries; pass `icon` when a 24px leading icon adds useful context. Set `labelWidth` for a minimum label column, `presentation="stacked"` to put each value under its label, or `stackBelow` (`compact` 512px, `narrow` 576px) to stack only while the table is that narrow; never override `--kui-value-table-row-columns` for these. | Values, formatting, icon meaning, and empty/loading policy. | `@kerfjs/ui/value-table` | [Component ownership](./component-contract.md#ownership-boundaries) |
245
- | Framed rows of arbitrary content — `FramedList`, `FramedListRow` | Settings rows, account or source cards, or a shortcut list group several rows of copy and controls in one rounded frame with dividers between them. | Do not use it for read-only label/value facts; use `ValueTable`. Do not use it for selectable navigation rows; use `List` with `ListItem`. Do not pad or border rows from application CSS. | Compose any row content (a `Row` of copy, a keycap, `IconButton`s) in each `FramedListRow`; mark the row being edited `highlighted`. Name the list with `label` when no heading does. | Row content, controls, which row is highlighted, and the list's name. | `@kerfjs/ui/framed-list` | [Feedback accessibility](./accessibility.md#shared-rules) |
259
+ | Framed rows of arbitrary content — `FramedList`, `FramedListRow` | Settings rows, account or source cards, or a shortcut list group several rows of copy and controls in one rounded frame with dividers between them. | Do not use it for read-only label/value facts; use `ValueTable`. Do not use it for selectable navigation rows or a bounded result picker; use `List` with `ListItem` (a picker adds `frame`, `scrollable`, and `maxHeight`). Do not pad or border rows from application CSS. | Compose any row content (a `Row` of copy, a keycap, `IconButton`s) in each `FramedListRow`; mark the row being edited `highlighted`. Name the list with `label` when no heading does. | Row content, controls, which row is highlighted, and the list's name. | `@kerfjs/ui/framed-list` | [Feedback accessibility](./accessibility.md#shared-rules) |
246
260
  | Selectable item summaries — `SummaryRow` | A list or board shows many items as dense, selectable summaries: an identifier and title, a timestamp, and a metadata line (tickets, tasks). | Do not use it for navigation rows; use `ListItem`. Do not use it for free-form selectable tiles; use `ContentItem`. Do not hand-roll row chrome. | Pass `title`, optional `leading`, `trailingMeta`, and `metadata`; choose `presentation` (`row` or `card`), `rail`, `selected`, `dimmed`, `listEdge`, and `titleLines`. Put rows in a labeled `role="listbox"` and own selection and arrow-key focus in the app. | The listbox, selection, keyboard movement, content, rail tone, and dimming. | `@kerfjs/ui/summary-row` | [Feedback accessibility](./accessibility.md#shared-rules) |
247
261
  | Indeterminate activity — `LoadingSpinner` | A Kerf surface needs compact, labeled or decorative indeterminate progress. | Do not use it for known progress; use Web Awesome `wa-progress-bar` or `wa-progress-ring`. Direct Web Awesome UI may use `wa-spinner`; do not mix spinner systems within one surface. | Pass a label when the spinner conveys status; use size for a named icon step or positive pixel size. | Loading lifecycle and adjacent status copy. | `@kerfjs/ui/loading-spinner` | [Accessibility](./accessibility.md#shared-rules) |
248
262
  | Loading placeholder — `Skeleton` + a component's `placeholder` prop | A value or a whole component is still loading and should hold its space as a subtle, unanimated block, keeping the layout stable — an inspector or detail view rendering its real chrome with per-record values absent. | Do not use it for known progress (use `LoadingSpinner`), do not animate it, and do not hand-rebuild a component's empty state — set `placeholder` on the component instead. Prefer it over `wa-skeleton`, which the pure-Kerf primitives avoid to stay Web-Awesome-free. | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `SegmentedControl`, `StateBanner`, `AppTab`, `ToolbarText`, `ListActionRow`) to render skeletons in its value slots with interactivity disabled; use the standalone `Skeleton` for a custom slot. | Loading lifecycle, which slots are unknown, and announcing the loading region. | `@kerfjs/ui/skeleton` | [Accessibility](./accessibility.md#shared-rules) |
@@ -254,10 +268,11 @@ beside a `ContentItem` value, and connect its id to the preview group's
254
268
  | Value selection — `Select` | A controlled form, toolbar, or compact navigation value comes from a moderate or long choice list, possibly grouped, icon-bearing, icon-only when closed, or truncating. Set `multiple` with an array `value` when the person may choose any number of them; add `selectedPresentation="icon-only"` and a fixed `triggerIcon` for a toolbar filter menu. | Do not use it for commands; use `PopupMenu`. Do not use it for a small visible choice set; use `SegmentedControl`. Prefer it over direct `wa-select`, `wa-option`, or value-like `wa-dropdown`/`wa-dropdown-item` composition. | Import `@kerfjs/ui/select/register` once for registration and current-request animation ownership; listen for standard input/change events. Do not add consumer popup timing or positioning repairs. Use typed presentation props rather than consumer `::part()` overrides; let the parent own placement. | Controlled value, validation, choices, domain mapping, and outer placement. | `@kerfjs/ui/select` | [Web Awesome integration](../README.md#web-awesome-theme) |
255
269
  | Pick or create a value by typing — `Combobox`, `wireComboboxes` | People type to find an existing value among many or create a new one (tags, labels, recipients). | Do not use it when only existing values are allowed; use `Select`. Do not use it for search text with removable filter tokens; use `TokenSearchField`. Do not style a native input with a datalist. | Pass `id`, `label`, and `suggestions` (strings or `{ value, label }`), plus `placeholder`, `hint`, and `createLabel` (or `false` for suggestions only). Call `wireComboboxes(root, { onCommit })` once; it filters, moves the active option with the arrows, and reports `{ id, value, created, input }` on Enter, a comma, or a click. | Suggestions, committed values, normalization, and clearing the field. | `@kerfjs/ui/combobox`, `@kerfjs/ui/wire-comboboxes` | [Feedback accessibility](./accessibility.md#shared-rules) |
256
270
  | Small exclusive choice — `SegmentedControl` | A few visible choices switch a compact view or setting, with toolbar, rounded, or pill presentation. | Do not use it for tabpanel semantics; use `TabBar`. Do not use it for many choices; use `ChoiceChips` when they stay visible and wrap, or `Select` for a long list. Prefer it over `wa-button-group` when the controls select one value. | Delegate its action, read `data-segment-value`, update `value`, and rerender. | Controlled value, labels, action, and persistence. | `@kerfjs/ui/segmented-control` | [SegmentedControl contract](./accessibility.md#segmentedcontrol) |
257
- | Wrapping exclusive choice — `ChoiceChips` | Several visible options, too many for one segmented track, must wrap onto more rows — a labeled color palette with swatches, a tag color, a category. | Do not use it for a few compact choices on one track; use `SegmentedControl`. Do not use it for a long list or tight space; use `Select`. Do not use it for removable tags (`Chip`) or multiple selection. Prefer it over `wa-radio-group` with `appearance="button"` radios, which wrap as a ragged joined group. | Pass a form `name`, a `label` or `labelledBy`, and the controlled `value`; delegate the `action` on `change`, read the radio `value`, and rerender. Lead choices with a `swatch` (`CssColor` or `'transparent'`) or an `icon`. | Controlled value, choices, swatch colors, change handling, and form submission. | `@kerfjs/ui/choice-chips` | [ChoiceChips contract](./accessibility.md#choicechips) |
271
+ | Wrapping visible choice — `ChoiceChips` | Several visible options, too many for one segmented track, must wrap onto more rows — a labeled color palette with swatches, a tag color, a category. Set `multiple` with an array `value` when any number may be on, such as label filters or annotation intents. | Do not use it for a few compact choices on one track; use `SegmentedControl`. Do not use it for a long list or tight space; use `Select`. Do not use it for removable tags (`Chip`). Prefer it over `wa-radio-group` with `appearance="button"` radios, which wrap as a ragged joined group. | Pass a form `name`, a `label` or `labelledBy`, and the controlled `value`; delegate the `action` on `change`, read the radio `value` (or, with `multiple`, the checked boxes under `name`), and rerender. Place it in a dark appearance scope for inverse chrome rather than restyling it. Lead choices with a `swatch` (`CssColor` or `'transparent'`) or an `icon`. | Controlled value, choices, swatch colors, change handling, and form submission. | `@kerfjs/ui/choice-chips` | [ChoiceChips contract](./accessibility.md#choicechips) |
272
+ | Chat or prompt composer — `Composer`, `wireComposers` | A chat message, assistant prompt, or reply is written in a raised frame and sent with a trailing action, often in a `DialogSurface` floating footer over a transcript. | Do not use it for an ordinary form field submitted with the rest of a form; use `wa-textarea` with a `FieldLabel`. Do not wrap it in another framed `ContentItem` or card; it owns its frame, padding, and shadow. Prefer it over a `wa-textarea` and a button in app-styled chrome. | Pass a `name`, a title-style `label`, an `id` when it has a `hint`, and the trailing `actions` (a brand Send `wa-button`). Bound the growing field with `minRows` and `maxRows`. Read and clear the textarea from the send action. For Enter-to-send, set `submitOnEnter` and call `wireComposers(root)` once; Enter clicks the first trailing action, Shift+Enter is a newline, and IME commits never send. | Value, sending, shortcuts beyond the opt-in Enter-to-send, and the disabled or busy state of its actions. | `@kerfjs/ui/composer`, `@kerfjs/ui/wire-composers` | [Composer](./composer.md) |
258
273
  | Structured search editor — `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`, `wireTokenSearchFields` | Free text and ordered, editable, removable filter tokens share one searchbox; enable `collapsible` when an empty, unfocused field should reduce to one iconic action, standalone or in a toolbar group. | Do not use it for ordinary text entry; use a native input or Web Awesome `wa-input`. Do not use it when filters belong in separate form controls. | Read DOM-owned text on input, empty `textContent` on clear, and use `placeTokenSearchCaret` after explicit controlled focus changes. Call `wireTokenSearchFields` from `@kerfjs/ui/wire-token-search-fields` once so Enter submits without adding a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. In `collapsible` mode it also manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse); bind the field's `expanded` to the signal on the returned handle (`handle.expanded(id)`) or adopt your own via `collapsible.signals`, and opt out per behavior only when the app must own it. | Parsing, suggestions, tokens, query execution, results, announcements, and — only if overriding the default — the collapsible `expanded` signal. | `@kerfjs/ui/token-search-field` | [TokenSearchField contract](./accessibility.md#tokensearchfield) |
259
274
  | Persistent inline status — `StateBanner` | A neutral, info, pop, success, warning, or danger message belongs next to the affected work; pop highlights featured or novel content without implying status. | Do not use pop as a success, warning, or danger substitute. Do not use StateBanner for a no-content screen; use `EmptyState`. Do not use it for transient confirmation; use a toast. Web Awesome `wa-callout` is the ecosystem alternative for Web Awesome-owned content. | Delegate an optional action; choose alert urgency only for attention-requiring failure. Use the optional tone-tinted `badge` for a terse count or status beside the title, not as the only expression of meaning. | State mapping, message lifetime, retry/action behavior, badge content, and copy. | `@kerfjs/ui/state-banner` | [Feedback accessibility](./accessibility.md#shared-rules) |
260
- | App-level progress in a corner — `CornerStatus`, `wireCornerStatus` | Ongoing app-wide work, such as "Loading…" or "Updating tickets… 3 of 12", needs a non-dismissible status that has no better home beside the affected work. | Do not use it for transient, dismissible confirmation; use a toast. Do not use it for status that belongs beside the affected work; place a `StateBanner` there. Do not position a `StateBanner` with fixed-position application CSS. | Compose one or more `StateBanner`s with `urgency="status"`; they stack. Call `wireCornerStatus` once so each overlay host opens in the top layer as renders insert it. It never takes focus and has no close button, light dismiss, or Escape. | Status lifetime (render while the work runs, remove when it ends), banner copy and progress, and corner placement. | `@kerfjs/ui/corner-status`, `@kerfjs/ui/wire-corner-status` | [Feedback accessibility](./accessibility.md#shared-rules) |
275
+ | App-level progress in a corner — `CornerStatus`, `wireCornerStatus` | Ongoing app-wide work, such as "Loading…" or "Updating Tickets… 3 of 12", needs a non-dismissible status that has no better home beside the affected work. | Do not use it for transient, dismissible confirmation; use a toast. Do not use it for status that belongs beside the affected work; place a `StateBanner` there. Do not position a `StateBanner` with fixed-position application CSS. | Compose one or more `StateBanner`s with `urgency="status"`; they stack. Call `wireCornerStatus` once so each overlay host opens in the top layer as renders insert it. It never takes focus and has no close button, light dismiss, or Escape. | Status lifetime (render while the work runs, remove when it ends), banner copy and progress, and corner placement. | `@kerfjs/ui/corner-status`, `@kerfjs/ui/wire-corner-status` | [Feedback accessibility](./accessibility.md#shared-rules) |
261
276
  | Ambient edge activity — `EdgeActivityIndicator` | App-wide background activity (server work, sync) should be visible along the top edge without a modal, a banner, or a layout shift. | Do not use it for progress the user waits on or must read; use `CornerStatus`. Do not use it for known progress; use a progress bar. Do not make it the only record of important state. | Drive `busy`; pass an optional `message` for a polite status pill and a `tone`. It sits below the top safe area (or flush with `inset="none"`), takes no pointer, and only fades under reduced motion. | The busy state, message, and tone. | `@kerfjs/ui/edge-activity-indicator` | [Feedback accessibility](./accessibility.md#shared-rules) |
262
277
  | Empty or busy content area — `EmptyState` | A content region has no items, cannot proceed, or is loading and needs explanation plus an optional action. | Do not use it for an inline status update; use `StateBanner`. Do not use it for transient success; use `wa-toast`/`wa-toast-item` or the application's toast system. | Delegate its optional action; it composes `LoadingSpinner` when busy. | Empty/busy policy, recovery action, illustration, and copy. | `@kerfjs/ui/empty-state` | [Feedback ownership](./component-contract.md#extracted-versus-application-specific) |
263
278
 
@@ -304,6 +319,18 @@ title minimum height, and border-width tokens for section density.
304
319
  Set `--kui-list-group-divider-color` on a containing `List` to give
305
320
  `ListHeader`, `ListItem`, and `ListActionRow` the same before/after divider
306
321
  color; each row otherwise uses the quiet neutral border tone.
322
+ Set `--kui-list-row-padding-inline` on a containing `List` to give `ListItem`
323
+ and `ListActionRow` rows one leading inline inset: both start their leading
324
+ icons on the same edge at any value, and the token defaults to
325
+ `--kui-layout-item-padding`. `--kui-list-item-padding-inline` still overrides
326
+ it for `ListItem` rows alone, so use the shared token whenever the two row kinds
327
+ share a list.
328
+ Both row kinds also draw their untiled leading icon at one shared size,
329
+ `--kui-list-row-icon-size` (default 18px), so a list that mixes them starts
330
+ their labels on one edge too. Set it on the containing `List` to resize both
331
+ row kinds together; `--kui-list-item-icon-size` still overrides it for
332
+ `ListItem` rows alone. An `iconTile` keeps its 32px tile and 18px glyph at any
333
+ value.
307
334
  For a filled command list, set `--kui-list-item-background` and
308
335
  `--kui-list-item-border` on the list or row. Scope `--kui-list-item-color`,
309
336
  `--kui-list-item-hover-background`, `--kui-list-item-selected-color`, and
@@ -324,7 +351,10 @@ trailing zone that should take a full second row when expanded. `responsiveAt`
324
351
  chooses the compact or narrow container breakpoint. The leading identity stays
325
352
  in the first row. Set `centerAlign="balanced"` when the center zone must sit on
326
353
  the toolbar's midpoint despite unequal or empty side content; it gives the
327
- leading and trailing zones equal tracks. `--kui-toolbar-leading-min-width`,
354
+ leading and trailing zones equal tracks. A lone `ToolbarText` in a balanced
355
+ center is a navigation-bar title: the side zones keep their content width and
356
+ the title truncates first, centered while the sides leave room (NavStack uses
357
+ this once it shows a back control). `--kui-toolbar-leading-min-width`,
328
358
  `--kui-toolbar-trailing-gap`, `--kui-toolbar-trailing-justify`, and the
329
359
  `--kui-toolbar-*-padding-inline` zone tokens adjust local geometry without
330
360
  selecting toolbar anatomy from application CSS.
@@ -356,7 +386,7 @@ group; set `--kui-token-search-editor-min-width` only when a wider editor is
356
386
  required.
357
387
 
358
388
  - `Toolbar` serves persistent app chrome and headings; the zone contents and accessible naming distinguish the purpose.
359
- - `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `ChoiceChips` keeps a larger exclusive set visible as wrapping pills; `Select` handles a longer value list.
389
+ - `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `ChoiceChips` keeps a larger exclusive set (or, with `multiple`, any-number set) visible as wrapping pills; `Select` handles a longer value list.
360
390
 
361
391
  Use `AppTab presentation="icon-only"` with its `name` kept as the accessible
362
392
  tab name. For segmented tabs that should show names in a wide reader and icons
@@ -384,7 +414,9 @@ beside that pinned tab. `wireTabBars` measures the pinned inset and provides
384
414
  enough end scroll room for the last peer to align there; a partial peer can
385
415
  still appear at the trailing edge as an overflow cue. This mode also aligns a
386
416
  newly selected tab at the pinned edge in LTR and RTL. Set the prop on the bar,
387
- without styling its strip or tab internals.
417
+ without styling its strip or tab internals. With `snapTabs`, keep the trailing
418
+ zone non-shrinking (the default `--kui-tab-bar-trailing-flex`): the reserved
419
+ end room counts toward the strip's share when both zones shrink.
388
420
  Set the public
389
421
  `--kui-app-tab-pinned-background` token if the surrounding surface differs from
390
422
  the default; the pinned tab must cover peers as they scroll beneath it. Its
@@ -398,8 +430,13 @@ scroll correction.
398
430
  - `StateBanner` persists beside affected work; `EmptyState` replaces absent content; `wa-callout` is contextual ecosystem content; `wa-toast` and `wa-toast-item` are transient and must not carry the only copy of important state.
399
431
 
400
432
  For a banner with multi-line supporting copy, use `copyLayout="stacked"` to put
401
- the detail below the title and badge. Use `actionPlacement="below"` when its
402
- action needs a separate trailing row. The default values keep both inline;
433
+ the detail below the title and badge. The default `actionPlacement="trailing"`
434
+ keeps the action beside the copy while the copy has room and moves it to its
435
+ own end-aligned trailing row when the banner is narrow (a phone, a sidebar),
436
+ so a group of buttons never squeezes or covers the title; no breakpoint or
437
+ `actionPlacement` switch is needed for narrow layouts. Use
438
+ `actionPlacement="below"` when the action should always take a separate
439
+ trailing row. The default copy layout keeps the detail inline;
403
440
  `--kui-state-banner-copy-gap` and `--kui-state-banner-copy-row-gap` adjust the
404
441
  stacked copy's spacing at an instance boundary.
405
442
 
@@ -494,8 +531,13 @@ To apply a saved search or restore a query programmatically, call `model.replace
494
531
  When one root contains both kinds of field, a registered model enables chip keyboard behavior only for its own field. Other fields keep native editing unless the root opts into `keyboard` with an application removal callback.
495
532
  For an icon action at the end of the field, pass `trailingAction` with `icon`,
496
533
  accessible `label`, and delegated `action` (plus optional `id`). The component
497
- renders a button that fills the trailing hit target. Reserve `trailing` for
498
- passive content such as a shortcut hint.
534
+ renders a compact button beside the clear button, with the same size, hover,
535
+ focus ring, and cursor, in every presentation. For a help toggle that opens a
536
+ surface, set `expanded` (and `haspopup`, plus `controls` while it is open); for
537
+ an on/off toggle, set `pressed`. The open or pressed state keeps the hover fill.
538
+ Put domain `data-*` metadata in `attributes`. Never style your own button into
539
+ the `trailing` slot; reserve `trailing` for passive content such as a shortcut
540
+ hint.
499
541
  Set `fill` on a collapsible `TokenSearchField` in a stretched Toolbar center to
500
542
  use the available width when expanded; the field and its search group retain
501
543
  their compact widths when collapsed.
@@ -505,6 +547,8 @@ unset.
505
547
 
506
548
  `ToolbarControlGroup.tileTone` selects `neutral`, `brand`, `success`, `warning`, or `danger` quiet fill, matching quiet border, and on-quiet foreground for a non-interactive icon tile. Compose a contained `single` group whose only child is a direct decorative LucideIcon (`aria-hidden="true"`). Omission preserves the normal group palette. The prop does not recolor interactive, mixed, multi-control, or borderless groups; keep real actions in controls with their existing focus/hover/pressed treatments.
507
549
 
550
+ `ListItem`, `ListItemLink`, and `ListActionRow` take `iconTile` to present their leading `icon` in a filled 32px rounded tile holding an 18px glyph, for settings or command rows that identify each entry by color. Pass a tone (`neutral`, `brand`, `success`, `warning`, or `danger`) for the same quiet fill, matching quiet border, and on-quiet foreground as `ToolbarControlGroup.tileTone`, or `{ color, foreground? }` for an application color: `color` is a `CssColor` (`uiColor()`, `colorVar()`, or `fillColor('#e5484d')`) and `foreground` a `CssForegroundColor`; without one, the glyph is black or white, whichever contrasts with the fill. The tile sits 6px clear of a standard 44px row (a compact row is at least 42px tall), moves the label to the tile's edge, and keeps its colors when the row is hovered or selected. Never wrap the icon in your own tile or resize the row's icon slot.
551
+
508
552
  ## Toolbar composition
509
553
 
510
554
  Use `ToolbarText tone="dark"` on a loud or photo-backed toolbar surface to
@@ -573,7 +617,7 @@ the control's dimensions, shows `LoadingSpinner` in its icon slot, sets
573
617
  | ------------------------------ | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
574
618
  | Identity or title text | `<ToolbarText text="…" size="large" />` (or `xlarge` for a page/panel title that fits) | Wrap in a `single` borderless group only when it must align with adjacent control pills |
575
619
  | One or more icon/text buttons | `<ToolbarControlGroup>{buttons}</ToolbarControlGroup>` | Use `buttonAppearance="push"` for toggle buttons with `aria-pressed`; `single` for a lone control |
576
- | One primary application action | `trailing={<wa-button variant="brand">New ticket…</wa-button>}` | The standalone button keeps its Web Awesome chrome; use `variant="danger"` when the action is destructive |
620
+ | One primary application action | `trailing={<wa-button variant="brand">New Ticket…</wa-button>}` | The standalone button keeps its Web Awesome chrome; use `variant="danger"` when the action is destructive |
577
621
  | An exclusive view switch | `<ToolbarControlGroup><SegmentedControl … /></ToolbarControlGroup>` | Not `TabBar`, which switches tabpanels |
578
622
  | A value list | `<ToolbarControlGroup><Select … /></ToolbarControlGroup>` | Register `@kerfjs/ui/select/register` once; use `focusRingOwner="group"` so focus follows the group's geometry |
579
623
  | A command menu | `<ToolbarControlGroup single nestedDropdown><PopupMenu … /></ToolbarControlGroup>` | Register `@kerfjs/ui/popup-menu/register` once; set `menuInset` on the group |
@@ -634,7 +678,7 @@ menu.
634
678
  label="Sort"
635
679
  icon={<LucideIcon icon={ArrowDownAZ} name="arrow-down-a-z" />}
636
680
  items={[
637
- { label: "Recently updated", action: "sort-recent" },
681
+ { label: "Recently Updated", action: "sort-recent" },
638
682
  { label: "Priority", action: "sort-priority" },
639
683
  ]}
640
684
  />
@@ -653,7 +697,7 @@ items shape covers single-line and batch actions; compute each batch child's
653
697
  <PopupMenu
654
698
  label="Line actions"
655
699
  items={[
656
- { label: 'Copy reference', action: 'copy-reference', attributes: { 'data-item-id': lineId } },
700
+ { label: 'Copy Reference', action: 'copy-reference', attributes: { 'data-item-id': lineId } },
657
701
  { label: 'Decide', submenu: [
658
702
  { kind: 'heading', label: 'Review' },
659
703
  { label: 'Approve', action: 'decide', checked: current === 'approve', attributes: { 'data-item-id': lineId, 'data-decision': 'approve' } },
@@ -670,8 +714,11 @@ items shape covers single-line and batch actions; compute each batch child's
670
714
  For a flat command menu with persistent sort choices, group commands under
671
715
  `heading` entries and set `checked` explicitly on each choice. Checked rows
672
716
  show a checkmark; the background marks the current keyboard row, as in `Select`.
673
- Only items with `checked: true` or `checked: false` reserve checkmark space.
674
- Omit `checked` for a plain command, including one beside checkbox items.
717
+ Items with `checked: true` or `checked: false` reserve checkmark space, and so
718
+ does every plain command in a menu level (the top menu or one submenu) that
719
+ holds such an item, so icons and labels line up across the level; a level
720
+ without a choice keeps the compact start. Omit `checked` for a plain command,
721
+ including one beside checkbox items.
675
722
  Commands still dispatch their own `data-action` once on pointer and keyboard
676
723
  selection.
677
724
 
@@ -715,6 +762,9 @@ remain siblings. `trailingActionVisibility="interaction"` keeps the action
715
762
  available on hover and keyboard focus and automatically leaves it visible on
716
763
  non-hover devices. These props own presentation only—the application still
717
764
  owns labels, status meaning, action policy, selection, and domain metadata.
765
+ `density="compact"` makes a single-line row 36px tall (42px with `iconTile`),
766
+ the same height as a compact `ListItem`, so the two row kinds stay level in a
767
+ dense mixed list; a standard row is 44px.
718
768
 
719
769
  When the trailing control is an overflow menu ("…" with Edit, Delete, …),
720
770
  pass `trailingMenu: { label, icon, items, placement?, disabled? }` instead of
@@ -832,6 +882,40 @@ requires a string label so the title cannot lose text hidden inside markup.
832
882
  </Chip>
833
883
  ```
834
884
 
885
+ ## File and link chips
886
+
887
+ Give `Chip` an `href` when a short chip should open or download something,
888
+ such as the files a chat message references. The chip becomes a native `<a>`
889
+ with the same geometry, icon slot, truncation, and focus ring, plus hover
890
+ feedback. `download` is `true` or the suggested file name; `external` opens a
891
+ new tab with `rel="noopener noreferrer"` and announces it, like
892
+ `ListItemLink`. A link chip cannot also carry a remove button. Use
893
+ `ListItemLink` for a navigation row and `ToolbarActionLink` in a toolbar.
894
+
895
+ Inside a `brand-loud` or `neutral` `ContentItem` bubble, set
896
+ `tone="inherit"`: the chip follows the bubble's foreground and draws its shape
897
+ with a currentColor border (soft for `quiet`, full strength for `outline` and
898
+ `solid`) instead of a fill, so its label keeps the bubble's text contrast.
899
+ Its hover strengthens the border and underlines the label, and its focus ring
900
+ follows the foreground. Semantic tones keep their fills and add a currentColor
901
+ wash on hover. File names are data: keep their spelling.
902
+
903
+ ```tsx
904
+ <Row gap="xs" wrap>
905
+ {files.map((file) => (
906
+ <Chip
907
+ tone="inherit"
908
+ href={file.url}
909
+ download={file.name}
910
+ icon={<LucideIcon icon={FileText} name="file-text" />}
911
+ truncate
912
+ >
913
+ {file.name}
914
+ </Chip>
915
+ ))}
916
+ </Row>
917
+ ```
918
+
835
919
  ## Wrapping exclusive choices
836
920
 
837
921
  Use `ChoiceChips` when one value is chosen from a visible set that does not
@@ -844,7 +928,8 @@ The pills wrap with the standard 8px control gap; never wrap a
844
928
  borders turn ragged across rows.
845
929
 
846
930
  Lead a choice with `swatch` for a color (a `CssColor` from `uiColor()`,
847
- `colorVar()`, or `foregroundColor()`), `swatch: 'transparent'` for the
931
+ `colorVar()`, or the `fillColor` helper — `fillColor('#3b82f6')` — for an
932
+ application-chosen CSS color; never `foregroundColor()` for a fill), `swatch: 'transparent'` for the
848
933
  no-color option (an outlined dot struck through diagonally), or a decorative
849
934
  `icon`. Keep every label visible: the swatch and the selected tint are
850
935
  supplementary. Name the group with `label`, or with `labelledBy` pointing at a
@@ -859,12 +944,54 @@ visible `FieldLabel`.
859
944
  action="select-command-color"
860
945
  choices={[
861
946
  { value: 'transparent', label: 'Transparent', swatch: 'transparent' },
862
- { value: 'blue', label: 'Blue', swatch: foregroundColor('#3b82f6') },
863
- { value: 'green', label: 'Green', swatch: foregroundColor('#22c55e') },
947
+ { value: 'blue', label: 'Blue', swatch: fillColor('#3b82f6') },
948
+ { value: 'green', label: 'Green', swatch: fillColor('#22c55e') },
864
949
  ]}
865
950
  />
866
951
  ```
867
952
 
953
+ ### Wrapping multiple choices
954
+
955
+ Set `multiple` when the person may turn any number of the visible options on
956
+ or off — label filters, or the intents an annotation carries. The same pills
957
+ are then native checkboxes in a `role="group"`: each pill is a Tab stop, Space
958
+ toggles it, and every checked value submits under `name`. `value` becomes a
959
+ `readonly Value[]` (the type rejects a single value, and a single group
960
+ rejects an array). Delegate the `action` on `change` and read the checked
961
+ boxes by `name`, which lists them in choice order. A multiple group takes no `required`. Prefer a
962
+ multiple `Select` when the option list is long or space is tight.
963
+
964
+ On dark inverse chrome, such as a media viewer, put the chips inside a dark
965
+ appearance scope (`.wa-dark`, or any `color-scheme: dark` ancestor) rather
966
+ than restyling them: every chip color is a theme token that follows the scope.
967
+
968
+ ```tsx
969
+ <ChoiceChips
970
+ name="intents"
971
+ label="Annotation Intents"
972
+ multiple
973
+ value={annotation.intents}
974
+ action="toggle-annotation-intent"
975
+ choices={[
976
+ { value: 'comment', label: 'Comment' },
977
+ { value: 'bug', label: 'Bug' },
978
+ { value: 'question', label: 'Question' },
979
+ ]}
980
+ />
981
+ ```
982
+
983
+ ```ts
984
+ delegateActions(root, 'change', {
985
+ 'toggle-annotation-intent': () => {
986
+ annotation.intents = [
987
+ ...root.querySelectorAll<HTMLInputElement>(
988
+ 'input[name="intents"]:checked',
989
+ ),
990
+ ].map((box) => box.value);
991
+ },
992
+ });
993
+ ```
994
+
868
995
  ## Web Awesome overlap policy
869
996
 
870
997
  Web Awesome catalog coverage means supported and themed, not preferred. Import
@@ -0,0 +1,143 @@
1
+ # Composer
2
+
3
+ `Composer` (`@kerfjs/ui/composer`, CSS `@kerfjs/ui/composer.css`) is the chat
4
+ or prompt composer: the place a person writes a message, assistant prompt, or
5
+ reply and sends it with a trailing action. It replaces app-owned composer
6
+ chrome, such as a `wa-textarea` and a button wrapped in application CSS that
7
+ draws a frame, a focus border, and a shadow.
8
+
9
+ ## Contract
10
+
11
+ ```tsx
12
+ <Composer
13
+ id="assistant-message"
14
+ name="message"
15
+ label="Message"
16
+ placeholder="Ask about this build"
17
+ hint="Answers use the latest nightly build."
18
+ minRows={1}
19
+ maxRows={8}
20
+ actions={
21
+ <wa-button variant="brand" data-action="send-message">
22
+ Send
23
+ </wa-button>
24
+ }
25
+ />
26
+ ```
27
+
28
+ - **Frame.** The root is a raised surface (`--kui-color-surface-raised`) with
29
+ the standard 1px neutral border, the large radius (`--kui-radius-l`), and
30
+ `--kui-shadow-l`. While focus is anywhere within it, including on a trailing
31
+ action, the border turns `--kui-color-brand-border-loud`. The frame owns
32
+ 8px padding, so a composer placed 8px from a container edge puts its text on
33
+ the same 17px column as a `List` of content items beside it.
34
+ - **Field.** A native, borderless `<textarea>` named by `label`. It sizes to
35
+ its content with `field-sizing: content`, between `minRows` (default 1) and
36
+ `maxRows` (default 8) lines, and scrolls past the maximum. Both bounds are
37
+ positive integers, and `maxRows` is at least `minRows`; otherwise `Composer`
38
+ throws a `RangeError`. In an engine without `field-sizing`, the field keeps
39
+ a fixed height of three lines, clamped to the bounds, and scrolls.
40
+ - **Action row.** Below the field: an optional quiet `hint` at the text start
41
+ and the trailing `actions`, normally one brand Send button. With an `id`,
42
+ the hint is the field's accessible description (`<id>-hint`).
43
+ - **Value.** `value` seeds the field (a restored draft, or `''`). The app
44
+ reads the textarea in its send action, clears it, and re-renders.
45
+ - **Disabled.** `disabled` disables the field; the app disables or marks busy
46
+ its own actions.
47
+ - **Placement.** `slot` assigns it to a web component slot, such as a
48
+ `wa-dialog`'s `footer`. `rootAttributes` takes safe `data-*` identity.
49
+ - **Enter to send.** `submitOnEnter` opts in to the keyboard contract below;
50
+ it marks the root `data-submit-on-enter="true"` (protected from
51
+ `rootAttributes`) and gives the field `aria-keyshortcuts="Enter"`.
52
+
53
+ The app owns sending, any other keyboard shortcut, what a sent message looks
54
+ like, and where the composer sits.
55
+
56
+ ## Enter to send
57
+
58
+ Set `submitOnEnter` and call `wireComposers` once over an ancestor of every
59
+ such composer; it returns a disposer:
60
+
61
+ ```ts
62
+ import { wireComposers } from '@kerfjs/ui/wire-composers';
63
+
64
+ const stopComposers = wireComposers(app);
65
+ ```
66
+
67
+ In an opted-in composer's field:
68
+
69
+ | Key | Result |
70
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
71
+ | Enter, Cmd+Enter, Ctrl+Enter | Clicks the composer's **first trailing action**; the newline is suppressed. |
72
+ | Shift+Enter, Alt+Enter | Inserts a newline, as in any textarea. |
73
+ | Enter committing an IME composition | Left to the input method: `isComposing`, keyCode 229, or between `compositionstart` and `compositionend`. |
74
+ | Enter on an empty or whitespace-only message | Nothing: no send and no newline. |
75
+ | Auto-repeating Enter (key held down) | Nothing. |
76
+
77
+ The first trailing action is the first `wa-button`, `button`, or
78
+ `[data-action]` element inside `actions`. Clicking it means the keyboard send
79
+ runs the same delegated `data-action` handler as a pointer send, so the app
80
+ writes one send handler that reads the field, clears it, and re-renders. The
81
+ click does not move focus, so the person keeps typing. Nothing is sent while
82
+ the field is `disabled`, or while that action has a `disabled` attribute or
83
+ property, a `loading` attribute (a busy `wa-button`), or
84
+ `aria-disabled="true"`; the Enter is still suppressed, so a message waiting on
85
+ a busy Send does not gain a newline.
86
+
87
+ Leave `submitOnEnter` off for a long-form prompt where Enter should be a
88
+ newline, and send with the action alone. A visible `hint` may also state the
89
+ shortcut ("Enter to send, Shift+Enter for a new line") when the audience may
90
+ not expect it; `aria-keyshortcuts` already announces it to assistive
91
+ technology.
92
+
93
+ ## In a chat dialog
94
+
95
+ A chat dialog floats the composer over its transcript. Use a `DialogSurface`
96
+ with `bodyLayout="fill-last"` and `footerPresentation="floating"` (and
97
+ `footerInset="none"`, since the composer draws its own frame), and slot the
98
+ composer into the footer:
99
+
100
+ ```tsx
101
+ <DialogSurface height={remify(560)} bodyInset="none" bodyLayout="fill-last" footerPresentation="floating" footerOffset={remify(8)} footerInset="none">
102
+ <wa-dialog id="assistant" label="Assistant">
103
+ <StateBanner title="Answers use the latest nightly build." tone="neutral" />
104
+ <List gap="xs" controlInsets="tb" scrollable scrollClearance="block-end">{messages}</List>
105
+ <Composer submitOnEnter slot="footer" id="assistant-message" name="message" label="Message" placeholder="Ask about this build" actions={send} />
106
+ </wa-dialog>
107
+ </DialogSurface>;
108
+ ```
109
+
110
+ The floating footer is transparent, so the composer's own frame and shadow
111
+ lift it over the transcript. The transcript's `scrollClearance="block-end"`
112
+ lets messages scroll visibly beneath the composer while the last one still
113
+ comes to rest above it, following the composer's measured height as it grows.
114
+ Without the opt-in, the dialog body reserves that height below the transcript
115
+ instead.
116
+
117
+ ## Do not
118
+
119
+ - Wrap it in a framed `ContentItem`, card, or bordered container; it owns its
120
+ frame, padding, and shadow.
121
+ - Style its field, frame, or action row from application CSS. A variation it
122
+ does not offer is a component gap.
123
+ - Use it for an ordinary form field submitted with the rest of a form; use
124
+ `wa-textarea` with a `FieldLabel`.
125
+
126
+ ## Verification
127
+
128
+ `tests/unit/composer.test.tsx` covers serialization, the hint description,
129
+ row bounds, and protected attributes; `tests/unit/wire-composers.test.tsx`
130
+ covers the Enter-to-send matrix (modifiers, IME, empty, repeat, disabled
131
+ field and actions, first action only, disposal). `tests/browser/composer.spec.ts` covers
132
+ the focus-within border, growth up to `maxRows` and then scrolling, the action
133
+ row geometry, and the send flow at wide and phone widths, and, in Chromium,
134
+ Firefox, and WebKit, Enter, Shift+Enter, Cmd/Ctrl+Enter, empty messages, and
135
+ IME composition (dispatched composition events and keyCode 229). In
136
+ Chromium it also drives a real IME composition through the DevTools protocol
137
+ (`Input.imeSetComposition`, then `Input.insertText` to commit). That test
138
+ asserts the engine's own event order: an Enter keydown flagged `isComposing`
139
+ while composing, the committing `Process` keydown with keyCode 229, then
140
+ `compositionend`. None of those sends, the committed text lands in the field,
141
+ and the next Enter sends it;
142
+ `tests/browser/surface-scaffold.spec.ts` covers the composer in the floating
143
+ footer of the chat dialog, including Enter to send.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 88" width="600" height="88"><defs><clipPath id="ishc0"><rect x="21" y="21" width="68.8" height="30" rx="15"/></clipPath><clipPath id="ishc1"><rect x="33" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp2"><rect x="51" y="29" width="26.8" height="14"/></clipPath><clipPath id="ct3"><rect x="51" y="29" width="26.8" height="14"/></clipPath><clipPath id="ishc4"><rect x="111.8" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp5"><rect x="129.8" y="29" width="52.3" height="14"/></clipPath><clipPath id="ct6"><rect x="129.8" y="29" width="52.3" height="14"/></clipPath><clipPath id="ishc7"><rect x="204.1" y="21" width="76.4" height="30" rx="15"/></clipPath><clipPath id="ishc8"><rect x="216.1" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp9"><rect x="234.1" y="29" width="34.4" height="14"/></clipPath><clipPath id="ct10"><rect x="234.1" y="29" width="34.4" height="14"/></clipPath><clipPath id="ishc11"><rect x="302.5" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp12"><rect x="320.5" y="29" width="47.2" height="14"/></clipPath><clipPath id="ct13"><rect x="320.5" y="29" width="47.2" height="14"/></clipPath><clipPath id="ishc14"><rect x="401.7" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp15"><rect x="419.7" y="29" width="41.1" height="14"/></clipPath><clipPath id="ct16"><rect x="419.7" y="29" width="41.1" height="14"/></clipPath></defs><rect x="20" y="20" width="70.8" height="32" rx="16" fill="rgb(40,57,66)"/><g clip-path="url(#ishc0)"><path d="M17 17h76.8v38H17zm19 5h38.8a14 14 0 0 1 14 14 14 14 0 0 1-14 14H36a14 14 0 0 1-14-14 14 14 0 0 1 14-14" fill="rgb(100,210,255)" fill-rule="evenodd"/></g><rect x="20.5" y="20.5" width="70" height="31" rx="15.5" fill="none" stroke="rgb(100,210,255)" stroke-width="1"/><rect x="33" y="30" width="12" height="12" rx="6" fill="rgb(239,68,68)"/><g clip-path="url(#ishc1)"><path d="M29 26h20v20H29zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="51" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(138,203,255)">Bug</text></g></g><rect x="98.8" y="20" width="96.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="99.5" y="20.5" width="95" height="31" rx="15.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><rect x="111.8" y="30" width="12" height="12" rx="6" fill="rgb(59,130,246)"/><g clip-path="url(#ishc4)"><path d="M107.8 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp5)"><g clip-path="url(#ct6)"><text x="129.81" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(245,245,247)">Feature</text></g></g><rect x="203.1" y="20" width="78.4" height="32" rx="16" fill="rgb(40,57,66)"/><g clip-path="url(#ishc7)"><path d="M200.1 17h84.4v38h-84.4zm19 5h46.4a14 14 0 0 1 14 14 14 14 0 0 1-14 14h-46.4a14 14 0 0 1-14-14 14 14 0 0 1 14-14" fill="rgb(100,210,255)" fill-rule="evenodd"/></g><rect x="203.5" y="20.5" width="77" height="31" rx="15.5" fill="none" stroke="rgb(100,210,255)" stroke-width="1"/><rect x="216.1" y="30" width="12" height="12" rx="6" fill="rgb(34,197,94)"/><g clip-path="url(#ishc8)"><path d="M212.1 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp9)"><g clip-path="url(#ct10)"><text x="234.06" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(138,203,255)">Docs</text></g></g><rect x="289.5" y="20" width="91.2" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="289.5" y="20.5" width="91" height="31" rx="15.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><rect x="302.5" y="30" width="12" height="12" rx="6" fill="rgb(168,85,247)"/><g clip-path="url(#ishc11)"><path d="M298.5 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp12)"><g clip-path="url(#ct13)"><text x="320.47" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(245,245,247)">Design</text></g></g><rect x="388.7" y="20" width="85.1" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="389.5" y="20.5" width="84" height="31" rx="15.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><rect x="401.7" y="30" width="12" height="12" rx="6" fill="rgb(107,114,128)"/><g clip-path="url(#ishc14)"><path d="M397.7 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(245,245,247,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp15)"><g clip-path="url(#ct16)"><text x="419.7" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(245,245,247)">Chore</text></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 88" width="600" height="88"><defs><clipPath id="ishc0"><rect x="21" y="21" width="68.8" height="30" rx="15"/></clipPath><clipPath id="ishc1"><rect x="33" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp2"><rect x="51" y="29" width="26.8" height="14"/></clipPath><clipPath id="ct3"><rect x="51" y="29" width="26.8" height="14"/></clipPath><clipPath id="ishc4"><rect x="111.8" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp5"><rect x="129.8" y="29" width="52.3" height="14"/></clipPath><clipPath id="ct6"><rect x="129.8" y="29" width="52.3" height="14"/></clipPath><clipPath id="ishc7"><rect x="204.1" y="21" width="76.4" height="30" rx="15"/></clipPath><clipPath id="ishc8"><rect x="216.1" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp9"><rect x="234.1" y="29" width="34.4" height="14"/></clipPath><clipPath id="ct10"><rect x="234.1" y="29" width="34.4" height="14"/></clipPath><clipPath id="ishc11"><rect x="302.5" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp12"><rect x="320.5" y="29" width="47.2" height="14"/></clipPath><clipPath id="ct13"><rect x="320.5" y="29" width="47.2" height="14"/></clipPath><clipPath id="ishc14"><rect x="401.7" y="30" width="12" height="12" rx="6"/></clipPath><clipPath id="cp15"><rect x="419.7" y="29" width="41.1" height="14"/></clipPath><clipPath id="ct16"><rect x="419.7" y="29" width="41.1" height="14"/></clipPath></defs><rect x="20" y="20" width="70.8" height="32" rx="16" fill="rgb(214,233,249)"/><g clip-path="url(#ishc0)"><path d="M17 17h76.8v38H17zm19 5h38.8a14 14 0 0 1 14 14 14 14 0 0 1-14 14H36a14 14 0 0 1-14-14 14 14 0 0 1 14-14" fill="rgb(0,117,219)" fill-rule="evenodd"/></g><rect x="20.5" y="20.5" width="70" height="31" rx="15.5" fill="none" stroke="rgb(0,117,219)" stroke-width="1"/><rect x="33" y="30" width="12" height="12" rx="6" fill="rgb(239,68,68)"/><g clip-path="url(#ishc1)"><path d="M29 26h20v20H29zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="51" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(26,93,207)">Bug</text></g></g><rect x="98.8" y="20" width="96.3" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="99.5" y="20.5" width="95" height="31" rx="15.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><rect x="111.8" y="30" width="12" height="12" rx="6" fill="rgb(59,130,246)"/><g clip-path="url(#ishc4)"><path d="M107.8 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp5)"><g clip-path="url(#ct6)"><text x="129.81" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(29,29,31)">Feature</text></g></g><rect x="203.1" y="20" width="78.4" height="32" rx="16" fill="rgb(214,233,249)"/><g clip-path="url(#ishc7)"><path d="M200.1 17h84.4v38h-84.4zm19 5h46.4a14 14 0 0 1 14 14 14 14 0 0 1-14 14h-46.4a14 14 0 0 1-14-14 14 14 0 0 1 14-14" fill="rgb(0,117,219)" fill-rule="evenodd"/></g><rect x="203.5" y="20.5" width="77" height="31" rx="15.5" fill="none" stroke="rgb(0,117,219)" stroke-width="1"/><rect x="216.1" y="30" width="12" height="12" rx="6" fill="rgb(34,197,94)"/><g clip-path="url(#ishc8)"><path d="M212.1 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp9)"><g clip-path="url(#ct10)"><text x="234.06" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(26,93,207)">Docs</text></g></g><rect x="289.5" y="20" width="91.2" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="289.5" y="20.5" width="91" height="31" rx="15.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><rect x="302.5" y="30" width="12" height="12" rx="6" fill="rgb(168,85,247)"/><g clip-path="url(#ishc11)"><path d="M298.5 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp12)"><g clip-path="url(#ct13)"><text x="320.47" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(29,29,31)">Design</text></g></g><rect x="388.7" y="20" width="85.1" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="389.5" y="20.5" width="84" height="31" rx="15.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><rect x="401.7" y="30" width="12" height="12" rx="6" fill="rgb(107,114,128)"/><g clip-path="url(#ishc14)"><path d="M397.7 26h20v20h-20zm10 5a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5" fill="rgba(29,29,31,0.2)" fill-rule="evenodd"/></g><g clip-path="url(#cp15)"><g clip-path="url(#ct16)"><text x="419.7" y="41" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" font-weight="650" fill="rgb(29,29,31)">Chore</text></g></g></svg>