@kerfjs/ui 5.0.0-beta.98 → 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 (407) 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 +487 -77
  47. package/ai/skill.md +40 -13
  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 +22 -5
  59. package/dist/catalog.js +50 -31
  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-AZWE2FB5.js → chunk-CMETHPKR.js} +12 -15
  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-527IAEWG.js → chunk-PYBBYSZ3.js} +10 -17
  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-Cp3Y8kJ9.d.ts → nav-stack-VXTumzKa.d.ts} +48 -3
  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.js +1 -1
  189. package/dist/skeleton.js +1 -1
  190. package/dist/spacer.js +1 -1
  191. package/dist/split-button.js +1 -1
  192. package/dist/split-view.d.ts +7 -5
  193. package/dist/split-view.js +6 -3
  194. package/dist/split-view.js.map +1 -1
  195. package/dist/state-banner.d.ts +6 -1
  196. package/dist/state-banner.js +1 -1
  197. package/dist/styles/anchored-surface.css +27 -1
  198. package/dist/styles/catalog/components/catalog-example.css +33 -0
  199. package/dist/styles/catalog/components/catalog-stage.css +7 -0
  200. package/dist/styles/chip.css +70 -0
  201. package/dist/styles/components/actions/popup-menu/popup-menu.css +14 -4
  202. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +75 -1
  203. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +66 -0
  204. package/dist/styles/components/collections/list/list.css +77 -7
  205. package/dist/styles/components/collections/list-action-row/list-action-row.css +177 -22
  206. package/dist/styles/components/collections/list-item/list-item.css +158 -11
  207. package/dist/styles/components/feedback/anchored-surface/anchored-surface.css +27 -1
  208. package/dist/styles/components/feedback/chip/chip.css +70 -0
  209. package/dist/styles/components/feedback/state-banner/state-banner.css +24 -13
  210. package/dist/styles/components/forms/composer/composer.css +100 -0
  211. package/dist/styles/components/forms/token-search-field/token-search-field.css +13 -2
  212. package/dist/styles/components/layout/pane/pane.css +81 -2
  213. package/dist/styles/components/navigation/nav-stack/nav-stack.css +84 -0
  214. package/dist/styles/components/navigation/tab-bar/tab-bar.css +15 -4
  215. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +79 -4
  216. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +195 -0
  217. package/dist/styles/composer.css +100 -0
  218. package/dist/styles/foundation.css +40 -0
  219. package/dist/styles/list-action-row.css +177 -22
  220. package/dist/styles/list-item.css +158 -11
  221. package/dist/styles/list.css +77 -7
  222. package/dist/styles/nav-stack.css +84 -0
  223. package/dist/styles/pane.css +81 -2
  224. package/dist/styles/popup-menu.css +14 -4
  225. package/dist/styles/state-banner.css +24 -13
  226. package/dist/styles/styles.css +1 -0
  227. package/dist/styles/surface-scaffold.css +195 -0
  228. package/dist/styles/tab-bar.css +15 -4
  229. package/dist/styles/tab-navigator.css +79 -4
  230. package/dist/styles/token-search-field.css +13 -2
  231. package/dist/styles/toolbar-control-group.css +75 -1
  232. package/dist/styles/toolbar-text.css +66 -0
  233. package/dist/surface-scaffold-register.d.ts +6 -1
  234. package/dist/surface-scaffold-register.js +89 -3
  235. package/dist/surface-scaffold-register.js.map +1 -1
  236. package/dist/surface-scaffold.d.ts +66 -7
  237. package/dist/surface-scaffold.js +1 -1
  238. package/dist/tab-navigator.d.ts +23 -2
  239. package/dist/tab-navigator.js +2 -0
  240. package/dist/tab-navigator.js.map +1 -1
  241. package/dist/token-search-field.d.ts +1 -1
  242. package/dist/token-search-field.js +1 -1
  243. package/dist/{token-search-model-DTcTXWMS.d.ts → token-search-model-aYR0YAei.d.ts} +32 -2
  244. package/dist/token-search-model.d.ts +1 -1
  245. package/dist/toolbar-control-group.js +1 -1
  246. package/dist/toolbar-text.js +1 -1
  247. package/dist/toolbar.d.ts +1 -0
  248. package/dist/value-table.js +1 -1
  249. package/dist/wire-catalog.d.ts +3 -2
  250. package/dist/wire-catalog.js +2 -2
  251. package/dist/wire-composers.d.ts +16 -0
  252. package/dist/wire-composers.js +3 -0
  253. package/dist/wire-composers.js.map +1 -0
  254. package/dist/wire-list-focus-scroll.d.ts +16 -0
  255. package/dist/wire-list-focus-scroll.js +3 -0
  256. package/dist/wire-list-focus-scroll.js.map +1 -0
  257. package/dist/wire-nav-stack.js +135 -0
  258. package/dist/wire-nav-stack.js.map +1 -1
  259. package/dist/wire-scroll-dividers.d.ts +9 -0
  260. package/dist/wire-scroll-dividers.js +1 -1
  261. package/dist/wire-tab-bars.js +1 -1
  262. package/dist/wire-tab-navigator.d.ts +3 -1
  263. package/dist/wire-tab-navigator.js +133 -1
  264. package/dist/wire-tab-navigator.js.map +1 -1
  265. package/dist/wire-token-search-fields.d.ts +1 -1
  266. package/dist/wire-token-search-fields.js +1 -1
  267. package/dist/workbench.d.ts +8 -3
  268. package/dist/workbench.js +2 -1
  269. package/docs/accessibility.md +132 -3
  270. package/docs/anchored-surface.md +46 -4
  271. package/docs/app-layouts.md +23 -2
  272. package/docs/apple-hig.md +1 -0
  273. package/docs/catalog.md +54 -8
  274. package/docs/collapsible-panel.md +9 -6
  275. package/docs/component-contract.md +41 -12
  276. package/docs/component-selection.md +143 -19
  277. package/docs/composer.md +143 -0
  278. package/docs/design/templates/choice-chips/multiple-dark.svg +1 -0
  279. package/docs/design/templates/choice-chips/multiple.svg +1 -0
  280. package/docs/design/templates/choice-chips-dark.svg +1 -1
  281. package/docs/design/templates/choice-chips.svg +1 -1
  282. package/docs/design/templates/composer/chat-dark.svg +1 -0
  283. package/docs/design/templates/composer/chat.svg +1 -0
  284. package/docs/design/templates/composer/prompt-dark.svg +1 -0
  285. package/docs/design/templates/composer/prompt.svg +1 -0
  286. package/docs/design/templates/composer-dark.svg +1 -0
  287. package/docs/design/templates/composer.svg +1 -0
  288. package/docs/design/templates/empty-state/filtered-dark.svg +1 -1
  289. package/docs/design/templates/empty-state/filtered.svg +1 -1
  290. package/docs/design/templates/empty-state/with-action-dark.svg +1 -1
  291. package/docs/design/templates/empty-state/with-action.svg +1 -1
  292. package/docs/design/templates/empty-state-dark.svg +1 -1
  293. package/docs/design/templates/empty-state.svg +1 -1
  294. package/docs/design/templates/list/compact-dark.svg +1 -1
  295. package/docs/design/templates/list/compact.svg +1 -1
  296. package/docs/design/templates/list-action-row/default-dark.svg +1 -1
  297. package/docs/design/templates/list-action-row/default.svg +1 -1
  298. package/docs/design/templates/list-action-row/icon-tile-dark.svg +1 -0
  299. package/docs/design/templates/list-action-row/icon-tile.svg +1 -0
  300. package/docs/design/templates/list-action-row/selected-dark.svg +1 -1
  301. package/docs/design/templates/list-action-row/selected.svg +1 -1
  302. package/docs/design/templates/list-action-row/trailing-menu-dark.svg +1 -1
  303. package/docs/design/templates/list-action-row/trailing-menu.svg +1 -1
  304. package/docs/design/templates/list-action-row-dark.svg +1 -1
  305. package/docs/design/templates/list-action-row.svg +1 -1
  306. package/docs/design/templates/list-dark.svg +1 -1
  307. package/docs/design/templates/list-header/size-l-dark.svg +1 -1
  308. package/docs/design/templates/list-header/size-l.svg +1 -1
  309. package/docs/design/templates/list-header/size-m-dark.svg +1 -1
  310. package/docs/design/templates/list-header/size-m.svg +1 -1
  311. package/docs/design/templates/list-header/size-xl-dark.svg +1 -1
  312. package/docs/design/templates/list-header/size-xl.svg +1 -1
  313. package/docs/design/templates/list-header-dark.svg +1 -1
  314. package/docs/design/templates/list-header.svg +1 -1
  315. package/docs/design/templates/list-item/icon-tile-dark.svg +1 -0
  316. package/docs/design/templates/list-item/icon-tile.svg +1 -0
  317. package/docs/design/templates/list-item/selected-dark.svg +1 -1
  318. package/docs/design/templates/list-item/selected.svg +1 -1
  319. package/docs/design/templates/list-item-dark.svg +1 -1
  320. package/docs/design/templates/list-item.svg +1 -1
  321. package/docs/design/templates/list.svg +1 -1
  322. package/docs/design/templates/segmented-control-dark.svg +1 -1
  323. package/docs/design/templates/segmented-control.svg +1 -1
  324. package/docs/design/templates/skeleton-dark.svg +1 -1
  325. package/docs/design/templates/skeleton.svg +1 -1
  326. package/docs/design/templates/state-banner/danger-dark.svg +1 -1
  327. package/docs/design/templates/state-banner/danger.svg +1 -1
  328. package/docs/design/templates/state-banner/info-badge-dark.svg +1 -1
  329. package/docs/design/templates/state-banner/info-badge.svg +1 -1
  330. package/docs/design/templates/state-banner/info-dark.svg +1 -1
  331. package/docs/design/templates/state-banner/info.svg +1 -1
  332. package/docs/design/templates/state-banner/strip-dark.svg +1 -1
  333. package/docs/design/templates/state-banner/strip.svg +1 -1
  334. package/docs/design/templates/state-banner/success-dark.svg +1 -1
  335. package/docs/design/templates/state-banner/success.svg +1 -1
  336. package/docs/design/templates/state-banner/warning-dark.svg +1 -1
  337. package/docs/design/templates/state-banner/warning.svg +1 -1
  338. package/docs/design/templates/state-banner-dark.svg +1 -1
  339. package/docs/design/templates/state-banner.svg +1 -1
  340. package/docs/design/templates/tab-bar-dark.svg +1 -1
  341. package/docs/design/templates/tab-bar.svg +1 -1
  342. package/docs/design/templates/token-search-field-dark.svg +1 -1
  343. package/docs/design/templates/token-search-field.svg +1 -1
  344. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  345. package/docs/design/templates/toolbar-control-group.svg +1 -1
  346. package/docs/design/templates/toolbar-dark.svg +1 -1
  347. package/docs/design/templates/toolbar-text/default-dark.svg +1 -1
  348. package/docs/design/templates/toolbar-text/default.svg +1 -1
  349. package/docs/design/templates/toolbar-text/large-dark.svg +1 -1
  350. package/docs/design/templates/toolbar-text/large.svg +1 -1
  351. package/docs/design/templates/toolbar-text-dark.svg +1 -1
  352. package/docs/design/templates/toolbar-text.svg +1 -1
  353. package/docs/design/templates/toolbar.svg +1 -1
  354. package/docs/design/templates/value-table/details-dark.svg +1 -1
  355. package/docs/design/templates/value-table/details.svg +1 -1
  356. package/docs/design/templates/value-table-dark.svg +1 -1
  357. package/docs/design/templates/value-table.svg +1 -1
  358. package/docs/design-philosophy.md +9 -1
  359. package/docs/examples/command-palette-adapter.tsx +3 -3
  360. package/docs/layout.md +147 -14
  361. package/docs/nav-stack.md +112 -2
  362. package/docs/recipes.md +9 -9
  363. package/docs/split-view.md +3 -3
  364. package/docs/sunken-panel.md +2 -2
  365. package/docs/surface-scaffold.md +138 -7
  366. package/docs/tab-navigator.md +38 -1
  367. package/docs/type-contracts.md +16 -16
  368. package/docs/ui-analyzer.md +3 -1
  369. package/docs/ui-doctor.md +71 -0
  370. package/docs/ux-demo.md +6 -6
  371. package/docs/workbench.md +10 -6
  372. package/docs/writing.md +152 -0
  373. package/doctor/copy-capitalization-checks.mjs +591 -0
  374. package/doctor/index.mjs +17 -8
  375. package/llms.txt +105 -11
  376. package/package.json +15 -1
  377. package/ux-demo/recipes/app-shell.tsx +11 -11
  378. package/ux-demo/recipes/collapsible-sidebar.tsx +12 -11
  379. package/ux-demo/recipes/compact-toolbar.tsx +10 -10
  380. package/ux-demo/recipes/composer-form.tsx +8 -8
  381. package/ux-demo/recipes/list-detail-dialog.tsx +13 -13
  382. package/ux-demo/recipes/list-workspace-states.tsx +13 -13
  383. package/ux-demo/recipes/loading-inspector.tsx +10 -10
  384. package/ux-demo/recipes/navigation-sidebar.tsx +7 -7
  385. package/ux-demo/recipes/navigation-stack.tsx +5 -5
  386. package/ux-demo/recipes/workspace-header.tsx +5 -5
  387. package/dist/chunk-36TL6BCR.js.map +0 -1
  388. package/dist/chunk-4LOK4634.js +0 -21
  389. package/dist/chunk-4LOK4634.js.map +0 -1
  390. package/dist/chunk-527IAEWG.js.map +0 -1
  391. package/dist/chunk-6BRRUPJY.js.map +0 -1
  392. package/dist/chunk-6STQXRT4.js.map +0 -1
  393. package/dist/chunk-76PWEN5Y.js +0 -64
  394. package/dist/chunk-76PWEN5Y.js.map +0 -1
  395. package/dist/chunk-AZWE2FB5.js.map +0 -1
  396. package/dist/chunk-DQDTZE4W.js +0 -70
  397. package/dist/chunk-DQDTZE4W.js.map +0 -1
  398. package/dist/chunk-FOQK4F3L.js.map +0 -1
  399. package/dist/chunk-GJYQ27C2.js.map +0 -1
  400. package/dist/chunk-JYD7G4RG.js.map +0 -1
  401. package/dist/chunk-KQDU7Z7P.js.map +0 -1
  402. package/dist/chunk-P74VXMB6.js.map +0 -1
  403. package/dist/chunk-PBT3CPPY.js.map +0 -1
  404. package/dist/chunk-PRG4CL3Q.js.map +0 -1
  405. package/dist/chunk-ROZLJULF.js.map +0 -1
  406. package/dist/chunk-U657IHFB.js.map +0 -1
  407. package/dist/chunk-V6W56V3N.js.map +0 -1
@@ -18,7 +18,7 @@ These are normative contracts for package components and consuming applications.
18
18
  - Interactive `ContentItem` cards are keyboard reachable. Set `selectionMode="toggle"` for a pressed button, `selectionMode="single"` for an option inside a listbox, or `selectionMode="multiple"` for a selectable row with a gridcell that can contain native controls. Place multiple-mode rows inside a labeled `List` or `Grid` with `selectionMode="multiple"`; the app owns the selection set and range gestures. Wire `wireContentItems` once so Enter and Space on the card activate its delegated action without activating it from a nested control. Disabled cards expose `aria-disabled` and leave the Tab order.
19
19
  - An unlabeled `Skeleton` (including the value slots a component's `placeholder` prop renders) is decorative loading chrome: it is `aria-hidden`, conveys no information a user needs to act on, and is deliberately exempt from the WCAG 3:1 non-text-contrast target. Inside a solid `Badge` it stays subtle (about 2.1–4.4:1 in light, 2.4–2.8:1 in dark, depending on tone); `tests/browser/tone-contrast.spec.ts` holds those values as a regression floor rather than an accessibility threshold. Announce loading through a labeled `Skeleton`, `LoadingSpinner`, or `aria-busy` on the owning region, never through the skeleton's visibility.
20
20
  - `StateBanner` defaults to polite `role="status"`; use `urgency="alert"` only for an attention-requiring failure. Its optional `badge` is persistent inline status/count content beside the title and is announced as part of the banner; keep it terse and do not rely on color alone for its meaning.
21
- - A removable `Chip` has a native button with a specific `removeLabel`, such as "Remove Urgent tag". The button is absent for a plain chip and disabled for a disabled chip. Its leading `icon` is decorative; the visible label names the tag. `truncate` retains the full plain-text label as a native tooltip. Tag text remains visible because tone does not convey meaning alone.
21
+ - A removable `Chip` has a native button with a specific `removeLabel`, such as "Remove Urgent tag". The button is absent for a plain chip and disabled for a disabled chip. Its leading `icon` is decorative; the visible label names the tag. `truncate` retains the full plain-text label as a native tooltip. Tag text remains visible because tone does not convey meaning alone. A link `Chip` (`href`) is a native anchor named by its visible label, with no remove button; `external` adds `target="_blank"`, `rel="noopener noreferrer"`, and an "(opens in new tab)" name suffix for a text label, and a disabled link chip drops `href`, sets `aria-disabled`, and leaves the tab order. `tone="inherit"` paints no fill, so its label keeps the surrounding text's contrast (a brand-loud bubble's white text included), and its focus ring is drawn in the surrounding foreground so it stays visible on a brand-colored surface.
22
22
  - `EmptyState` reports busy state through `aria-busy` and never relies on an illustration as its label.
23
23
  - `Select` follows the Web Awesome host's standard `input`/`change` events. Application tests verify the live `value`, focus, and events—not attributes alone. Its decorative option icons and value-dependent selected content remain present after controlled rerenders, so the visible choice does not silently lose its non-text cue.
24
24
  - `DisclosureArrow` is an 18px root-scaled decorative visual by default, not an interaction target. Its owning native control supplies a stable accessible name, pointer and keyboard interaction, and `aria-expanded`; multiple arrows keep independently controlled state. Consumers may resize it with the typed `size` prop without changing that ownership. Configured directions animate over the shortest path; a 180-degree closed-to-open tie uses counterclockwise rotation.
@@ -220,7 +220,18 @@ name from its content and ignores an `aria-label` on the host, so an icon-only
220
220
  trigger requires `label`, which renders as visually hidden text inside the
221
221
  button; a trigger with visible `text` is named by that text. An icon-only
222
222
  trigger also shows its `label` as an `aria-hidden` help tag on hover and
223
- keyboard focus (see [Help tags on icon-only triggers](#help-tags-on-icon-only-triggers)). Group headings
223
+ keyboard focus (see [Help tags on icon-only triggers](#help-tags-on-icon-only-triggers)).
224
+ The open menu carries that same name. Web Awesome renders the `role="menu"`
225
+ panel inside the dropdown's shadow root, where no ID reference can reach the
226
+ trigger (the context anchor is `aria-hidden` light DOM, and a button trigger's
227
+ real `<button>` sits in `wa-button`'s own shadow root). So `PopupMenu` renders
228
+ the name it already knows (a context or icon-only `label`, or the visible
229
+ `text`) as `data-menu-label` on the dropdown, and the register copies it to the
230
+ panel as `aria-label` on first render and on every open. A submenu panel shares
231
+ a shadow root with its parent item's label and is `aria-labelledby` it, so it
232
+ is announced as, for example, "More Actions, menu".
233
+ `tests/browser/popup-menu-accessible-name.spec.ts` finds each panel with
234
+ `getByRole('menu', { name })` in Chromium, Firefox, and WebKit. Group headings
224
235
  render as the same uppercase group titles a grouped `Select` shows, and dividers
225
236
  are presentational. A keyboard-focused item is the menu's current row, painted
226
237
  with the Select's current-option fill rather than a second focus ring inside the
@@ -466,6 +477,41 @@ focused radio, and the checked pill adds a second pixel of border so the
466
477
  selection does not rely on hue alone; forced-colors mode paints it with
467
478
  `Highlight`.
468
479
 
480
+ With `multiple`, the group renders `role="group"` with the same naming, and
481
+ each choice is a native `<input type="checkbox">` inside its pill. Every pill
482
+ is its own Tab stop, Space toggles it, assistive technology reads its native
483
+ checked state, and every checked box submits its value under `name`
484
+ (`FormData.getAll(name)`). Native `required` on a checkbox demands that one
485
+ box, so a multiple group takes no `required`; validate an "at least one" rule
486
+ in the application. Every chip color is a theme token resolved through
487
+ `light-dark()`, so inside a dark appearance scope (`.wa-dark`, `.wa-invert`,
488
+ or any `color-scheme: dark` ancestor) the pills, labels, checked tint, and
489
+ focus ring read on inverse chrome without overrides.
490
+
491
+ ## Composer
492
+
493
+ `Composer`'s message field is a native `<textarea>` named by `label`
494
+ (`aria-label`, title style). With an `id`, the `hint` renders with the id
495
+ `<id>-hint` and becomes the field's `aria-describedby`; without an `id`, the
496
+ hint is visible only. The field draws no border or outline of its own: the
497
+ frame is its boundary, and while focus is anywhere within the composer the
498
+ frame's border turns brand (`Highlight` in forced-colors mode, with a 2px
499
+ outline). Each trailing action keeps its own name and focus ring. `disabled`
500
+ disables the field only; the app disables its actions. Sending and
501
+ announcing a sent message stay with the app.
502
+
503
+ With `submitOnEnter` and `wireComposers`, the field carries
504
+ `aria-keyshortcuts="Enter"`, so assistive technology announces the shortcut.
505
+ Enter and Cmd/Ctrl+Enter activate the first trailing action, the same control
506
+ a pointer or Tab-and-Space user reaches; Shift+Enter inserts a newline. An
507
+ Enter that commits an IME composition (`isComposing`, keyCode 229, or between
508
+ `compositionstart` and `compositionend`) is left to the input method, so
509
+ people composing Japanese, Chinese, or Korean text never send a half-written
510
+ message. An empty or whitespace-only message, an auto-repeating key, a
511
+ disabled field, or a disabled, loading, or `aria-disabled` action sends
512
+ nothing. A visible hint may also state the shortcut when the audience may not
513
+ expect it.
514
+
469
515
  ## TokenSearchField
470
516
 
471
517
  Use `presentation="form-field"` when a structured search query sits beside
@@ -481,7 +527,10 @@ form-control tokens, so the two controls read as one form.
481
527
 
482
528
  `TokenSearchField` exposes the editable surface as a named `searchbox`. Each
483
529
  chip is atomic (`contenteditable="false"`) and contains separately named edit
484
- and remove buttons; the clear action is also named. Disabled fields publish
530
+ and remove buttons; the clear action is also named. A `trailingAction` is a
531
+ named button whose `label` is also its tooltip; a help toggle sets `expanded`
532
+ (`aria-expanded`, with `haspopup` and `controls` for the surface it opens) and
533
+ an on/off toggle sets `pressed` (`aria-pressed`). Disabled fields publish
485
534
  `aria-disabled` and stop editing without hiding the current expression. The
486
535
  application owns query parsing and must announce result-count or loading
487
536
  changes separately when that feedback is useful. Use `readTokenSearchField()`
@@ -612,6 +661,86 @@ opened it, else the restore control, else a control whose `aria-controls` names
612
661
  panel; an open overlay panel takes focus and keeps Tab inside it (the ARIA dialog
613
662
  pattern). See [`workbench.md`](workbench.md).
614
663
 
664
+ ## Chrome material
665
+
666
+ Translucent chrome, such as a `Pane` header with
667
+ `headerPresentation="overlay"`, a `NavStack` top bar with
668
+ `chromePresentation="overlay"`, or a `NavStack` bottom toolbar with
669
+ `bottomToolbarPresentation="overlay"`, or a `TabNavigator` tab bar with
670
+ `barPresentation="overlay"`, paints `--kui-chrome-material`: the surface at
671
+ 80% opacity behind a blur-and-saturate backdrop filter
672
+ (`--kui-chrome-material-filter`). Content scrolls visibly beneath it, so the
673
+ backdrop under its text is not a fixed color. The contrast rule measures the
674
+ worst case: the material composited over a solid black backdrop in the light
675
+ theme and a solid white backdrop in the dark theme. The blur only averages the
676
+ content, so it can never be darker or lighter than that.
677
+
678
+ Chrome on a raised surface, such as a `DialogSurface` title bar with
679
+ `headerPresentation="floating"`, paints `--kui-chrome-material-raised`
680
+ instead: the raised surface at the same 80%, so the bar matches the dialog
681
+ panel behind it. The same rule and fallbacks apply.
682
+
683
+ - **Normal text and icons pass AA over any content.** `--kui-color-text`
684
+ measures about 10.5:1 (light) and 8.3:1 (dark) on that worst case, and
685
+ about 10.5:1 (light) and 6.7:1 (dark) over the raised material. 80% is
686
+ above the minimum opacity that keeps 4.5:1 there (about 55% light and 60%
687
+ dark). A theme that overrides `--kui-chrome-material` or
688
+ `--kui-chrome-material-raised` must keep its text at 4.5:1 or better
689
+ against the same backdrops.
690
+ - **Quiet text does not, so `ToolbarText` lifts it.** `--kui-color-text-quiet`
691
+ falls to about 3.2:1 (light) and 4.1:1 (dark) over the worst case, and the
692
+ default-size title color to about 3.8:1 (light). Inside an overlay `Pane`
693
+ header or overlay `NavStack` chrome or bottom toolbar, or slotted as the `label` (or into
694
+ `header-actions`) of a `DialogSurface` floating header, an untoned
695
+ `ToolbarText` therefore takes
696
+ `--kui-color-text` for its title at every size, and its `description` mixes
697
+ the quiet color halfway toward the normal one, about 5.9:1 in both themes
698
+ (about 4.9:1 dark over the raised material). That keeps AA while the
699
+ description still reads as secondary. Controls lift the same way in an
700
+ overlay `Pane` header, overlay `NavStack` chrome or bottom toolbar, and a
701
+ `DialogSurface` floating header's `header-actions`: a default-tone
702
+ `ToolbarControlGroup`'s quiet control color over its own translucent fill
703
+ measures about 3.0:1 (light) and 3.5:1 (dark) over the worst case, so its
704
+ controls take `--kui-color-text` there. An overlay `TabNavigator` bar's
705
+ unselected labels lift halfway toward `--kui-color-text`, and its selected
706
+ label mixes 40% of it into the brand color: the plain brand color measured
707
+ about 3.2:1 (light) over the worst case. `wa-dialog`'s own close button is an
708
+ icon, held to the 3:1 non-text bar, and measures about 3.8:1 (light) and
709
+ 4.1:1 (dark) over the raised material. Any other quiet copy you place in
710
+ overlay chrome yourself has no such lift; keep it in the content.
711
+ - **The material turns opaque when transparency would hurt.** Under
712
+ `prefers-reduced-transparency: reduce` or `prefers-contrast: more`, and where
713
+ `backdrop-filter` is unsupported, it resolves to the opaque surface. In forced
714
+ colors, the header (or NavStack chrome) paints `Canvas` with a `CanvasText`
715
+ bottom edge, and an overlay NavStack bottom toolbar paints `Canvas` with a
716
+ `CanvasText` top edge, so content passing beneath it never shows through.
717
+
718
+ `tests/browser/overlay-header.spec.ts` (Pane),
719
+ `tests/browser/overlay-nav-stack.spec.ts` (NavStack top chrome),
720
+ `tests/browser/overlay-nav-stack-bottom.spec.ts` (NavStack bottom toolbar,
721
+ which also measures its description, a control, the top chrome's back
722
+ control, and increased contrast), and
723
+ `tests/browser/dialog-floating-header.spec.ts` (DialogSurface) measure the
724
+ title over the material on both worst-case backdrops in Chromium, Firefox, and
725
+ WebKit, and the Pane and DialogSurface specs also measure the description and
726
+ a header control group (DialogSurface also its close button). In
727
+ Chromium they also check the opaque fallbacks for reduced transparency and
728
+ forced colors, and the Pane and DialogSurface specs check increased contrast.
729
+
730
+ **Firefox and WebKit screenshots omit the blur.** In Playwright's Firefox and
731
+ WebKit, `page.screenshot()` (headless or headed) shows the content beneath the
732
+ material crisp, as if `backdrop-filter` did nothing. Stock Firefox's own
733
+ `--headless --screenshot` does the same. The filter is still applied: a
734
+ `recordVideo` frame of the same page in the same session, which records the
735
+ compositor's output, shows the content blurred. Firefox and WebKit draw
736
+ `backdrop-filter` only when compositing (WebRender in Firefox, Core Animation
737
+ in WebKit), and their snapshot paths repaint the page without it. Chromium's
738
+ screenshot shows the blur. Plain test pages with no kerf CSS behave the same
739
+ way, so no ancestor style in these components is blocking the filter. When
740
+ you review the blur in Firefox or WebKit, use a `recordVideo` frame, not a
741
+ screenshot. A crisp screenshot is not a defect, and contrast is unaffected
742
+ because the rule above already assumes no blur.
743
+
615
744
  ## Verification matrix
616
745
 
617
746
  For each changed component, inspect default, hover, focus, disabled, selected/pressed, busy/error, long-content, wide, narrow, light, dark, increased-contrast, reduced-motion, keyboard-only, and 200%-zoom states where applicable. DOM order must match reading and focus order, with no clipping or unreachable action.
@@ -23,10 +23,15 @@ focusable anchor on close (or the previously focused element for a nonfocusable
23
23
  anchor). Pass `initialFocus: false` for read-only
24
24
  help that leaves focus on its trigger. Escape and an outside press dismiss it;
25
25
  `dismiss`, `onDismiss`, and `outsideIgnore` can customize that behavior.
26
- `placement` (`bottom` or `top`), `align` (`start` or `end`), and `gap` feed the
27
- shared `kerfjs/overlay` positioning core: it flips vertically when the other
28
- side fits, clamps horizontally, and follows scroll and resize. Content taller
29
- than the viewport scrolls inside the surface.
26
+ `placement` (`bottom` or `top`), `align` (`start` or `end`), `gap`, and
27
+ `viewportMargin` feed the shared `kerfjs/overlay` positioning core: it flips
28
+ vertically when the other side fits, keeps the surface `viewportMargin` (default
29
+ 8px) inside the viewport edges, and follows scroll and resize. The surface fits
30
+ its height to the room beside the anchor: when neither side holds it, it opens
31
+ on the roomier side, caps its height to that room, and scrolls its content.
32
+ The room is exposed to the content as `--kui-anchored-surface-available-height`
33
+ and refreshed on every reposition, so a tall help surface fits beside a field on
34
+ a phone without application media queries or `max-height` overrides.
30
35
 
31
36
  ```tsx
32
37
  openAnchoredSurfaceAt(
@@ -52,6 +57,43 @@ Closing the anchor's modal dialog also closes the surface. Escape closes only
52
57
  the surface; the dialog beneath stays open. The temporary pointer anchor is
53
58
  removed when the surface closes.
54
59
 
60
+ ## Dark tone for media chrome
61
+
62
+ A popup anchored to dark media chrome (a volume control in a
63
+ `ToolbarControlGroup tone="dark"` over a video or image preview) passes
64
+ `tone: 'dark'`:
65
+
66
+ ```tsx
67
+ openAnchoredSurface(volumeButton, () => <VolumeControls />, {
68
+ label: 'Volume',
69
+ tone: 'dark',
70
+ placement: 'top',
71
+ });
72
+ ```
73
+
74
+ The tone is the same loud, inverse tone as a dark `ToolbarControlGroup` or
75
+ `ToolbarText`: dark beside a light page and light beside a dark one, so the
76
+ popup matches the group that opened it. When the surface opens it takes the
77
+ opposite of the anchor's color scheme (an explicit `color-scheme` on the anchor
78
+ or an ancestor wins; `light dark` follows the user's preference), and the
79
+ surface is marked `data-tone="dark"`. Nothing else changes. Every semantic
80
+ color token is a `light-dark()` pair, so the surface fill, border, and shadow,
81
+ and the text, borders, and focus rings of the Kerf UI and Web Awesome
82
+ components inside it (a `ListItem` mute row, a `wa-slider`), resolve to the
83
+ other side by inheritance. The content needs no tone props and no overrides.
84
+ Text and focus rings keep WCAG AA in both page schemes. A theme that replaces a
85
+ semantic token with a single color instead of a `light-dark()` pair opts that
86
+ token out of the flip. While the surface is open it follows a live switch: an
87
+ OS preference change, or a theme class, attribute, or inline `color-scheme`
88
+ changed on the anchor or one of its ancestors, flips it to the new opposite
89
+ (the content's own color transitions run, as the page's do). A scheme changed
90
+ only by swapping a stylesheet is not observed. `openAnchoredSurfaceAt` reads the
91
+ scheme from its `context` (the element under the pointer), so a region with its
92
+ own `color-scheme` is honored; without a connected `context` it reads the modal
93
+ scope's (or the page's).
94
+
95
+ ## Theming
96
+
55
97
  Override `--kui-anchored-surface-inset` and
56
98
  `--kui-anchored-surface-radius` for a theme; content can compose any Kerf UI
57
99
  components without the surface applying action policy. Prefer `PopupMenu` for
@@ -41,8 +41,19 @@ the layouts handle the insets for you. There is nothing to configure:
41
41
  status bar or home indicator but its first and last items can always be
42
42
  scrolled clear.
43
43
  - **Interior edges get nothing.** A `Workbench` center next to an expanded rail,
44
- a `SplitView` detail beside its list, or a `NavStack` view under its chrome is
45
- not inset on that edge. Collapse the rail and the center picks up the edge.
44
+ a `SplitView` detail beside its list, or a `NavStack` view under its docked
45
+ chrome is not inset on that edge. Collapse the rail and the center picks up
46
+ the edge. A `NavStack` with `chromePresentation="overlay"` is the exception
47
+ by design: its views reach under the translucent chrome, and the measured
48
+ chrome height (top safe area included) becomes their block-start inset, so
49
+ content scrolls beneath the bar yet rests clear of it (see
50
+ [Navigation stack › Overlay chrome](nav-stack.md#overlay-chrome)). Its
51
+ `bottomToolbarPresentation="overlay"` does the same at the bottom: the
52
+ measured toolbar height (bottom safe area included) becomes the views'
53
+ block-end inset, so the last item rests just above the toolbar. A
54
+ `TabNavigator` with `barPresentation="overlay"` does the same for its tab
55
+ bar: the measured bar height (bottom safe area included) becomes the
56
+ scenes' block-end inset.
46
57
  - **No double inset.** Whichever region applies an inset clears it for its
47
58
  descendants. A layout region whose only child is a `Pane` or another layout
48
59
  lets that child own the insets. That child fills the region — a `Workbench`
@@ -130,6 +141,16 @@ single scrolling slot; the negative margins change only its inline reach. The
130
141
  [`Pane` UX catalog example](../ux-demo/demos/pane.tsx) shows both `deepInset`
131
142
  states with the same table component.
132
143
 
144
+ ### Scrolling beneath overlaying chrome
145
+
146
+ `Pane.scrollClearance` (`"block-start"`, `"block-end"`, or `"both"`) lets the
147
+ scrolling slot pass beneath chrome laid over the pane's edge, such as a
148
+ `DialogSurface` floating footer over a filling Pane. The slot adds the
149
+ inherited `--kui-scroll-clearance-block-*` context that the chrome publishes to
150
+ its block padding and `scroll-padding`, alongside the safe-area and
151
+ `deepInset` insets, so its first and last items still rest clear of the
152
+ chrome. See `docs/layout.md` "Scroll clearance".
153
+
133
154
  ## Decision matrix
134
155
 
135
156
  | Situation | Layout | Device threshold |
package/docs/apple-hig.md CHANGED
@@ -12,6 +12,7 @@ Native Apple applications should use native platform controls and follow the Hum
12
12
  - Use one icon family and one metaphor per action. Directional icons may mirror in RTL; checkmarks, logos, and real-world objects do not.
13
13
  - Motion is brief, causal, and optional. Honor reduced-motion and reduced-transparency preferences; repeating indicators stop moving under reduced motion.
14
14
  - Writing is concise, specific, and action-led. Labels describe the result, not implementation details.
15
+ - Capitalize by element type, consistently: names of commands, controls, views, and fields use title style; descriptions, messages, tooltips, and option text use sentence style. See [Writing interface copy](./writing.md#capitalization).
15
16
 
16
17
  ## Interaction
17
18
 
package/docs/catalog.md CHANGED
@@ -85,7 +85,7 @@ action. Filtering does not change the selected
85
85
  entry or its preview.
86
86
 
87
87
  Per-entry `resources` render as "open in new tab" links in the footer, and
88
- `related` renders a "Related entries" `PopupMenu` in a `single`
88
+ `related` renders a "Related Entries" `PopupMenu` in a `single`
89
89
  `ToolbarControlGroup` (grouped by each entry's `group`), so register its elements
90
90
  with `@kerfjs/ui/popup-menu/register` when you use it.
91
91
 
@@ -142,18 +142,64 @@ entry's `tags` array.
142
142
  classes. A supplied `label` also names the row's `section`, so each specimen
143
143
  is a named region that assistive technology and
144
144
  `getByRole("region", { name })` can address.
145
+ - Example and stack labels are headings, so they use title style ("Heading
146
+ Identity Stays Whole", "Icon Trigger in a Toolbar Group"); the `note` that
147
+ explains a specimen stays sentence style. Write a label as a name, not a
148
+ sentence. See [Writing interface copy](./writing.md#capitalization);
149
+ `kerf-ui-doctor` reports a sentence-case label as `KUI-D040`.
145
150
  - Focused demos do not import local stylesheets, use inline `style`, or invent
146
151
  styling-only `demo-*` classes. Prefer a public Kerf component, then a Web
147
152
  Awesome surface when Kerf has no equivalent, then ordinary semantic HTML.
148
153
  Use `CatalogExample.viewport` for bounded specimen width, height, frame,
149
154
  surface, overflow, responsive visibility, and public custom-property values;
150
155
  keep those constraints catalog-owned instead of recreating a demo stylesheet.
156
+ Widths run `sidebar` (272px, a production rail, for sidebar-pane content),
157
+ `compact` (440px), `medium` (640px), and `wide` (864px), each capped at the
158
+ stage, plus `full`; `text` and `control` size a single label or control.
151
159
  Component heights run `short` (220px) through `tall` (360px); frame an app
152
160
  shell, whole-screen layout, or other application-sized recipe with
153
161
  `height: "app"` (592px), and give its root `fill` rather than a frame `Pane`.
154
162
  An app frame stands in for the screen: it is the containing block for
155
163
  screen-fixed chrome, so a compact `CollapsiblePanel` overlay, its backdrop,
156
164
  and restore controls dock to the frame's edges rather than the page's.
165
+ `minHeight` (`short`, `medium`, or `app` at 592px) is a floor the frame
166
+ may grow past rather than a definite height.
167
+ - A top-layer specimen (a `popover`, a modal) that production anchors to a
168
+ named region with CSS anchor positioning anchors to the frame instead when
169
+ the frame takes that name: `viewport.anchorName` is a CSS dashed-ident
170
+ (`"--app-shell-main"`, validated; anything else throws) that becomes the
171
+ frame's `anchor-name`, so the specimen's own `position-anchor` rule resolves
172
+ to the frame. Do not wrap the specimen in a stage element to name it. Anchor
173
+ names are document-wide, so give each frame on a page its own. The frame
174
+ only names the anchor: the specimen keeps owning its placement, and as a
175
+ top-layer element it is not clipped by the frame or the stage. Chromium,
176
+ Firefox, and WebKit with CSS anchor positioning place it against the frame;
177
+ an engine without anchor positioning ignores the name, and the specimen
178
+ falls back to whatever placement its own CSS gives it there.
179
+ - For a centered reading column inside a wider frame, set
180
+ `viewport.contentWidth` (`compact` 440px, `medium` 640px, or `wide` 864px,
181
+ each capped at the frame) and `contentAlign: "center"` (the default is
182
+ `start`). Each in-flow child fills that column; `[popover]` children keep
183
+ their own geometry. The column applies to block, `grid`, and `flex-column`
184
+ frames, and `contentAlign` does nothing without `contentWidth`.
185
+
186
+ ```tsx
187
+ <CatalogExample
188
+ label="Request Docked to the Main Column"
189
+ viewport={{
190
+ layout: "flex-column",
191
+ width: "full",
192
+ minHeight: "app",
193
+ anchorName: "--app-shell-main",
194
+ contentWidth: "medium",
195
+ contentAlign: "center",
196
+ }}
197
+ >
198
+ <Transcript />
199
+ <PermissionRequestPopup /> {/* its CSS: position-anchor: --app-shell-main */}
200
+ </CatalogExample>
201
+ ```
202
+
157
203
  - The specimen is an immediate child of `CatalogExample`. A focused component
158
204
  row should place the component root there, without a decorative card or
159
205
  spacing wrapper. A composition row may place the composition root there.
@@ -171,7 +217,7 @@ import { CatalogExample, CatalogExampleStack } from "@kerfjs/ui/catalog";
171
217
 
172
218
  const buttonPreview = (
173
219
  <CatalogExampleStack
174
- label="Button variants"
220
+ label="Button Variants"
175
221
  rootAttributes={{ "data-demo": "button" }}
176
222
  >
177
223
  <CatalogExample label="Icon" note="A bare glyph." align="glyph">
@@ -180,7 +226,7 @@ const buttonPreview = (
180
226
  <CatalogExample label="Control" align="inline-control">
181
227
  <SegmentedControl id="view" label="View" value="list" choices={choices} />
182
228
  </CatalogExample>
183
- <CatalogExample label="Authoring note" note="Explain the specimen below.">
229
+ <CatalogExample label="Authoring Note" note="Explain the specimen below.">
184
230
  <p>Use the public helper contract.</p>
185
231
  </CatalogExample>
186
232
  </CatalogExampleStack>
@@ -367,23 +413,23 @@ const sections: CatalogSection[] = [
367
413
  const renderers: Record<string, () => SafeHtml> = {
368
414
  button: () => (
369
415
  <CatalogExampleStack
370
- label="Button states"
416
+ label="Button States"
371
417
  rootAttributes={{ "data-demo": "button" }}
372
418
  >
373
419
  <CatalogExample label="Default" align="inline-control">
374
420
  <Button label="Save" />
375
421
  </CatalogExample>
376
- <CatalogExample label="Authoring note" note="Explain the specimen below.">
422
+ <CatalogExample label="Authoring Note" note="Explain the specimen below.">
377
423
  <p>The application owns product copy and actions.</p>
378
424
  </CatalogExample>
379
425
  </CatalogExampleStack>
380
426
  ),
381
427
  "profile-form": () => (
382
428
  <CatalogExampleStack
383
- label="Profile form composition"
429
+ label="Profile Form Composition"
384
430
  rootAttributes={{ "data-demo": "profile-form" }}
385
431
  >
386
- <CatalogExample label="Complete composition">
432
+ <CatalogExample label="Complete Composition">
387
433
  <ProfileForm />
388
434
  </CatalogExample>
389
435
  </CatalogExampleStack>
@@ -499,7 +545,7 @@ revealCatalogEntry(app, initial, { block: "center" });
499
545
 
500
546
  ## UX Review capture
501
547
 
502
- On a local Vite dev or preview server the catalog can offer a **Capture region**
548
+ On a local Vite dev or preview server the catalog can offer a **Capture Region**
503
549
  header link that starts a UX Review region screenshot, preset with the project
504
550
  and the active entry. Static hosting has no local project, so the link never
505
551
  appears there. Three pieces wire it:
@@ -147,7 +147,9 @@ top toolbar over a `Pane`:
147
147
  place groups wherever the open panel needs them;
148
148
  - a `ToolbarControlGroup` marked `relocateOnCollapse` stays available in the
149
149
  work area when the panel closes; the standard `toggle: { action, name,
150
- showLabel?, hideLabel? }` is always the final group.
150
+ showLabel?, hideLabel? }` is always the final group. Give `name` as the
151
+ panel's title-style name ("Navigator"), which labels the toggle group and
152
+ completes "Show Navigator" / "Hide Navigator".
151
153
 
152
154
  Busy groups keep their inert control, spinner, and adjacent live status together
153
155
  when relocated. Clearing `busy` restores the same action in its current toolbar;
@@ -166,7 +168,8 @@ divider of its own unless `dividerSides` says so. The `Pane` under it draws
166
168
  the line under the toolbar only while its content is scrolled (see
167
169
  [Scroll dividers](./layout.md#scroll-dividers)), and takes the panel's `pane`
168
170
  (`PaneConfig`: `contentElement`, `contentLabel`, `separators`,
169
- `safeAreaEdges`, `chromeDividers`) — for example `{ contentElement: "nav",
171
+ `safeAreaEdges`, `chromeDividers`, `appearance`, `deepInset`,
172
+ `scrollClearance`, `headerPresentation`, `tabIndex`, `outlined`) — for example `{ contentElement: "nav",
170
173
  contentLabel: "Sections" }` for a navigation rail. An omitted or `undefined`
171
174
  field keeps the `Pane` default; without a `toolbar` there is no `Pane`, so
172
175
  `pane` is ignored.
@@ -187,8 +190,8 @@ inert; avoid duplicate `id` attributes on marked groups.
187
190
  const toolbar: CollapsiblePanelToolbar = {
188
191
  label: "Navigator",
189
192
  leading: <NewFileGroup />,
190
- trailing: <ToolbarControlGroup relocateOnCollapse label="Search files"><SearchButton /></ToolbarControlGroup>,
191
- toggle: { action: "toggle-nav", name: "navigator" },
193
+ trailing: <ToolbarControlGroup relocateOnCollapse label="Search Files"><SearchButton /></ToolbarControlGroup>,
194
+ toggle: { action: "toggle-nav", name: "Navigator" },
192
195
  };
193
196
 
194
197
  <Row fill gap="none">
@@ -307,7 +310,7 @@ mount(app, () => (
307
310
  side="left"
308
311
  collapsed
309
312
  action="toggle-nav"
310
- label="Show navigator"
313
+ label="Show Navigator"
311
314
  />
312
315
  )}
313
316
  {/* content */}
@@ -362,7 +365,7 @@ sizes, content, and any per-project persistence — exactly as with the other la
362
365
 
363
366
  ## Recipe and coverage
364
367
 
365
- The catalog ships a runnable **Collapsible sidebar** recipe — a left rail and a
368
+ The catalog ships a runnable **Collapsible Sidebar** recipe — a left rail and a
366
369
  bottom drawer with the standard toggles, the compact overlay, and per-panel
367
370
  persistence: [open it](../ux-demo/?component=recipe-collapsible-sidebar) or read
368
371
  [`recipes.md`](recipes.md#collapsible-sidebar) · [TSX source](../ux-demo/recipes/collapsible-sidebar.tsx).
@@ -89,8 +89,12 @@ only cataloged `descendantElements` and composed JSX evidence.
89
89
  An entry's optional `cssValueProps` makes CSS-adjacent prop grammar equally
90
90
  machine-evaluable. Each path records the property grammar, finite shorthands,
91
91
  canonical versus exceptional spacing choices, standalone and expression-only
92
- helpers, raw/unsafe policy, and serialization examples. Nested paths use the
93
- bounded `items[].property` form. Consumer catalogs can publish the same facts;
92
+ helpers, raw/unsafe policy, and serialization examples. Nested paths are
93
+ dot-separated object members, each optionally suffixed with `[]` to visit
94
+ every array element: `items[].property` for a value inside each array item,
95
+ `iconTile.color` for a member of an object-valued prop. A member that is not a
96
+ literal object or array (a tone string such as `iconTile="brand"`, a variable)
97
+ is skipped rather than guessed. Consumer catalogs can publish the same facts;
94
98
  ESLint, `kerf-ui-analyze`, and `kerf-ui-doctor` then issue the same stable
95
99
  `KUI-L013`–`KUI-L017` feedback for application and third-party components.
96
100
 
@@ -246,10 +250,10 @@ product that genuinely needs one of these elements records a narrow `KUI-L301`
246
250
  profile exception for the file that renders it.
247
251
 
248
252
  Catalog detail footers use one standard resource vocabulary and order. Build
249
- them with `catalogResources()` from `@kerfjs/ui/catalog-resources`: `Demo source`
250
- first, optional `Component source` and `Design template`, then `Guidance`.
253
+ them with `catalogResources()` from `@kerfjs/ui/catalog-resources`: `Demo Source`
254
+ first, optional `Component Source` and `Design Template`, then `Guidance`.
251
255
  Third-party entries use the typed `integrationGuidance` kind, which renders
252
- `Integration guidance`, and omit `Component source` when the implementation is
256
+ `Integration Guidance`, and omit `Component Source` when the implementation is
253
257
  not owned by the catalog's project. Prefer these semantic kinds over local labels
254
258
  such as “View source” or “Read UI guidance”; the helper makes that convention a
255
259
  compile-time choice while still returning ordinary `CatalogResource[]` values.
@@ -333,6 +337,8 @@ visual anchor.
333
337
 
334
338
  `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.
335
339
 
340
+ `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.
341
+
336
342
  ### Public CSS anatomy
337
343
 
338
344
  The `publicClasses` array on each entry in
@@ -396,7 +402,12 @@ The documented exceptions live with their reasons in
396
402
  check, so the list only shrinks. The layout regions' inherited edge-inset
397
403
  context (`--kui-edge-inset-*` handed to a sole Pane or layout child and cleared
398
404
  for any other content, and a Toolbar clearing it for its zones) is the one
399
- design exception; the others name the open fix ticket.
405
+ design exception; the others name the open fix ticket. The scroll-clearance
406
+ context (`--kui-scroll-clearance-block-*`) needs no exception: overlaying
407
+ chrome publishes it on its own element (a `DialogSurface` on its dialog body,
408
+ a `Pane` overlay header, through `wireScrollDividers`, on its own Pane root).
409
+ A scroll owner reads it only through its own `scrollClearance` prop, or, for an
410
+ overlay-header Pane's content, through its own `headerPresentation`.
400
411
 
401
412
  Applications and component packages built on the package follow the same
402
413
  rules, and the downstream tools enforce them against the composition catalog
@@ -628,10 +639,14 @@ contract in `foundation.css`. A capped popup that scrolls (a long listbox, a
628
639
  menu, or a phone submenu) shows a soft shadow along each edge with more
629
640
  choices beyond it, so overlay scrollbars never leave it looking complete; the
630
641
  cue is CSS-only, appears only toward hidden rows, and a popup whose choices
631
- all fit draws none. Checkbox items reserve a check column; plain commands start
632
- at the checkmark's leading edge, beside the group title rather than the
633
- farther-in Select option label. Checked submenu items keep their checkmark and
634
- label inside the submenu surface. Compact mixed controls retain the standard
642
+ all fit draws none. Checkbox items reserve a check column, and so does every
643
+ plain command in a level (the top menu or one submenu) that holds a checkbox
644
+ item, through an `aria-hidden` icon-slot spacer, so check, icon, and label
645
+ columns align across the level. A level without a checkbox item keeps the
646
+ compact start: plain commands begin at the checkmark's leading edge, beside the
647
+ group title rather than the farther-in Select option label. Checked submenu
648
+ items keep their checkmark inside the submenu surface with the same check-column
649
+ advance as the top level. Compact mixed controls retain the standard
635
650
  item padding, omit an internal separator, and let the raised selected item paint
636
651
  over the outer border instead of shrinking to an inset highlight. The Web
637
652
  Awesome trigger's shadow base owns max-content sizing, so icons, localized text,
@@ -743,7 +758,12 @@ propagating before the window, or a press without a click, collapses in the
743
758
  next task instead.
744
759
  `TokenSearchField.trailingAction` supplies an icon, accessible `label`, delegated
745
760
  `action`, and optional `id`; the field renders a 24px wide button that fills its
746
- 34px line slot, including the hit target, disabled state, hover, and focus ring.
761
+ 34px line slot (32px in `toolbar-group`), including the hit target, disabled
762
+ state, hover, focus ring, and the clear button's radius in each presentation.
763
+ `expanded` renders `aria-expanded` and `pressed` renders `aria-pressed` (one or
764
+ the other, never both); either `true` state keeps the hover fill at full text
765
+ strength. `controls` and `haspopup` render `aria-controls` and `aria-haspopup`,
766
+ and `attributes` carries domain `data-*` metadata but never `data-action`.
747
767
  Use `trailing` for passive content. The two props are mutually exclusive.
748
768
  For a collapsible search in a stretched Toolbar center, `fill` lets the expanded
749
769
  field and its enclosing search `ToolbarControlGroup` consume the available width;
@@ -786,7 +806,16 @@ When a leading AppTab is pinned, the leading scroll divider appears at that
786
806
  tab's trailing edge while peer tabs scroll beneath it, including in RTL.
787
807
  With `snapTabs`, `wireTabBars` measures the pinned tab and settles peers at
788
808
  whole-tab starts after scroll and selection changes, reserving end room so the
789
- last peer can align there. The application still owns selection and tab order.
809
+ last peer can align there. That room is scrollable content after the last tab,
810
+ inside the strip, so it lengthens the scroll range without widening the strip:
811
+ the strip still shrinks to its allocation, and `trailing` and `end` stay inside
812
+ the bar. The room is still in-flow content, so it counts toward the strip's
813
+ content-based flex basis: when another zone also shrinks (a trailing zone
814
+ given `--kui-tab-bar-trailing-flex: 0 1 auto` with shrinkable content), the
815
+ strip keeps a proportionally larger share. The share converges within a few
816
+ frames and never pushes `trailing` or `end` out of the bar; keep the trailing
817
+ zone non-shrinking with `snapTabs` to avoid it. The application still owns
818
+ selection and tab order.
790
819
  ResizeObserver notifications coalesce snap measurements into the next animation
791
820
  frame, so inset writes cannot resize the observed strip during observer delivery.
792
821
  `wireTabBars` watches the whole root it is given, but a mutation re-measures only