@kerfjs/ui 5.0.0-beta.48 → 5.0.0-beta.50

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 (260) hide show
  1. package/README.md +49 -13
  2. package/ai/catalog-authoring.json +12 -1
  3. package/ai/catalog-authoring.schema.json +49 -1
  4. package/ai/compile-time-contracts-v1.json +14 -0
  5. package/ai/compile-time-contracts-v1.schema.json +1 -0
  6. package/ai/component-catalog-v2-overrides.json +7 -0
  7. package/ai/component-catalog-v2.json +164 -23
  8. package/ai/component-catalog.json +184 -27
  9. package/ai/public-api-signatures-v1.md +239 -168
  10. package/ai/skill.md +27 -12
  11. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  12. package/dist/app-tab.d.ts +3 -1
  13. package/dist/app-tab.js +1 -1
  14. package/dist/badge.d.ts +24 -0
  15. package/dist/badge.js +3 -0
  16. package/dist/badge.js.map +1 -0
  17. package/dist/browser/badge.js +3 -0
  18. package/dist/browser/grid.js +3 -0
  19. package/dist/browser/list-header.js +1 -0
  20. package/dist/browser/state-banner.js +1 -0
  21. package/dist/catalog-resources.d.ts +2 -2
  22. package/dist/catalog.d.ts +24 -125
  23. package/dist/catalog.js +387 -261
  24. package/dist/catalog.js.map +1 -1
  25. package/dist/{chunk-OGTJNHRG.js → chunk-2WMSE4MK.js} +5 -3
  26. package/dist/chunk-2WMSE4MK.js.map +1 -0
  27. package/dist/{chunk-GYGM363P.js → chunk-2YOUUEWR.js} +16 -6
  28. package/dist/chunk-2YOUUEWR.js.map +1 -0
  29. package/dist/{chunk-UDHHKZWO.js → chunk-3V75EQIP.js} +8 -4
  30. package/dist/chunk-3V75EQIP.js.map +1 -0
  31. package/dist/{chunk-W2FK7ZNY.js → chunk-4R777DUZ.js} +10 -2
  32. package/dist/chunk-4R777DUZ.js.map +1 -0
  33. package/dist/{chunk-T7DOWS6T.js → chunk-54HM36IQ.js} +6 -3
  34. package/dist/chunk-54HM36IQ.js.map +1 -0
  35. package/dist/{chunk-DAO2VFTT.js → chunk-5CE64WGC.js} +6 -4
  36. package/dist/chunk-5CE64WGC.js.map +1 -0
  37. package/dist/{chunk-SOK5EUF7.js → chunk-6ZXOQEAN.js} +4 -2
  38. package/dist/chunk-6ZXOQEAN.js.map +1 -0
  39. package/dist/{chunk-OGNE3OFT.js → chunk-AQSKJ6U6.js} +4 -2
  40. package/dist/chunk-AQSKJ6U6.js.map +1 -0
  41. package/dist/{chunk-VDUJZF5G.js → chunk-CMUIXJZM.js} +2 -2
  42. package/dist/{chunk-VDUJZF5G.js.map → chunk-CMUIXJZM.js.map} +1 -1
  43. package/dist/{chunk-OMSRSL3B.js → chunk-COUPLRZG.js} +5 -3
  44. package/dist/chunk-COUPLRZG.js.map +1 -0
  45. package/dist/{chunk-2TYUFZUC.js → chunk-D7GWXIAS.js} +4 -2
  46. package/dist/chunk-D7GWXIAS.js.map +1 -0
  47. package/dist/{chunk-AQFWBG23.js → chunk-DARTMI5Z.js} +5 -3
  48. package/dist/chunk-DARTMI5Z.js.map +1 -0
  49. package/dist/{chunk-V6GQKOX2.js → chunk-DJKWFYAA.js} +5 -3
  50. package/dist/chunk-DJKWFYAA.js.map +1 -0
  51. package/dist/{chunk-6NDBE2OK.js → chunk-E36EVCKG.js} +5 -3
  52. package/dist/chunk-E36EVCKG.js.map +1 -0
  53. package/dist/chunk-ELRMBQEC.js +47 -0
  54. package/dist/chunk-ELRMBQEC.js.map +1 -0
  55. package/dist/{chunk-TIGSG6BX.js → chunk-HJKRCKDR.js} +7 -3
  56. package/dist/chunk-HJKRCKDR.js.map +1 -0
  57. package/dist/{chunk-Y6LYKFXY.js → chunk-HMQ2ZDGG.js} +5 -3
  58. package/dist/chunk-HMQ2ZDGG.js.map +1 -0
  59. package/dist/{chunk-WN52USHV.js → chunk-J3EE6XWD.js} +2 -2
  60. package/dist/{chunk-WN52USHV.js.map → chunk-J3EE6XWD.js.map} +1 -1
  61. package/dist/{chunk-CK6P5AUK.js → chunk-KKNK3JUT.js} +7 -4
  62. package/dist/chunk-KKNK3JUT.js.map +1 -0
  63. package/dist/{chunk-ZPWZIVI6.js → chunk-N3GKMPDW.js} +7 -3
  64. package/dist/chunk-N3GKMPDW.js.map +1 -0
  65. package/dist/{chunk-DYBPI5O2.js → chunk-NDUGJ4TU.js} +4 -2
  66. package/dist/chunk-NDUGJ4TU.js.map +1 -0
  67. package/dist/{chunk-WSXUPSID.js → chunk-NN6GYOA2.js} +7 -3
  68. package/dist/chunk-NN6GYOA2.js.map +1 -0
  69. package/dist/{chunk-VXFGMDA7.js → chunk-OQI7RCRP.js} +10 -2
  70. package/dist/chunk-OQI7RCRP.js.map +1 -0
  71. package/dist/{chunk-E34TE2ZI.js → chunk-S6ZTTTV7.js} +5 -3
  72. package/dist/chunk-S6ZTTTV7.js.map +1 -0
  73. package/dist/{chunk-V6HCTCVD.js → chunk-SIU2AC5W.js} +8 -4
  74. package/dist/chunk-SIU2AC5W.js.map +1 -0
  75. package/dist/{chunk-WVLMZ5SG.js → chunk-UMGY5JSP.js} +4 -2
  76. package/dist/chunk-UMGY5JSP.js.map +1 -0
  77. package/dist/{chunk-I4V3UDSA.js → chunk-V6RZWDII.js} +7 -5
  78. package/dist/chunk-V6RZWDII.js.map +1 -0
  79. package/dist/{chunk-CAB6NQ2T.js → chunk-VOCPXGQN.js} +3 -1
  80. package/dist/chunk-VOCPXGQN.js.map +1 -0
  81. package/dist/chunk-XD7DWYXG.js +34 -0
  82. package/dist/chunk-XD7DWYXG.js.map +1 -0
  83. package/dist/{chunk-B6JYRX3P.js → chunk-ZIYOJFZD.js} +43 -4
  84. package/dist/chunk-ZIYOJFZD.js.map +1 -0
  85. package/dist/{chunk-EYALJQQD.js → chunk-ZK5EGVFP.js} +4 -2
  86. package/dist/chunk-ZK5EGVFP.js.map +1 -0
  87. package/dist/{chunk-6WTETUZ6.js → chunk-ZLWQZUGF.js} +8 -2
  88. package/dist/chunk-ZLWQZUGF.js.map +1 -0
  89. package/dist/{chunk-XKFRMGMW.js → chunk-ZNZ2JDBW.js} +3 -1
  90. package/dist/chunk-ZNZ2JDBW.js.map +1 -0
  91. package/dist/{chunk-I45QO2IZ.js → chunk-ZSYLBVWG.js} +6 -4
  92. package/dist/chunk-ZSYLBVWG.js.map +1 -0
  93. package/dist/collapsible-panel.d.ts +3 -1
  94. package/dist/collapsible-panel.js +4 -2
  95. package/dist/collapsible-panel.js.map +1 -1
  96. package/dist/disclosure-arrow.d.ts +3 -1
  97. package/dist/disclosure-arrow.js +1 -1
  98. package/dist/empty-state.d.ts +3 -1
  99. package/dist/empty-state.js +1 -1
  100. package/dist/floating-toolbar.d.ts +3 -1
  101. package/dist/floating-toolbar.js +1 -1
  102. package/dist/grid.d.ts +20 -0
  103. package/dist/grid.js +4 -0
  104. package/dist/grid.js.map +1 -0
  105. package/dist/index.d.ts +4 -2
  106. package/dist/index.js +33 -31
  107. package/dist/list-action-row.d.ts +3 -1
  108. package/dist/list-action-row.js +1 -1
  109. package/dist/list-header.d.ts +6 -1
  110. package/dist/list-header.js +2 -1
  111. package/dist/list-inset-control.d.ts +3 -1
  112. package/dist/list-inset-control.js +1 -1
  113. package/dist/list-inset-text.d.ts +3 -1
  114. package/dist/list-inset-text.js +1 -1
  115. package/dist/list-item.d.ts +3 -1
  116. package/dist/list-item.js +1 -1
  117. package/dist/list.d.ts +3 -1
  118. package/dist/list.js +1 -1
  119. package/dist/loading-spinner.d.ts +3 -1
  120. package/dist/loading-spinner.js +1 -1
  121. package/dist/lucide-icon.d.ts +3 -1
  122. package/dist/lucide-icon.js +1 -1
  123. package/dist/nav-stack.d.ts +3 -1
  124. package/dist/nav-stack.js +1 -1
  125. package/dist/pane.d.ts +3 -1
  126. package/dist/pane.js +1 -1
  127. package/dist/resizable-region.d.ts +5 -2
  128. package/dist/resizable-region.js +1 -1
  129. package/dist/row.d.ts +3 -1
  130. package/dist/row.js +1 -1
  131. package/dist/segmented-control.d.ts +3 -1
  132. package/dist/segmented-control.js +1 -1
  133. package/dist/select.d.ts +3 -1
  134. package/dist/select.js +1 -1
  135. package/dist/skeleton.d.ts +3 -1
  136. package/dist/skeleton.js +1 -1
  137. package/dist/spacer.d.ts +3 -1
  138. package/dist/spacer.js +1 -1
  139. package/dist/split-view.d.ts +3 -1
  140. package/dist/split-view.js +24 -5
  141. package/dist/split-view.js.map +1 -1
  142. package/dist/state-banner.d.ts +3 -1
  143. package/dist/state-banner.js +2 -1
  144. package/dist/styles/badge.css +86 -0
  145. package/dist/styles/catalog/components/catalog-detail.css +35 -0
  146. package/dist/styles/catalog/components/catalog-example-stack.css +6 -0
  147. package/dist/styles/catalog/components/catalog-example.css +35 -0
  148. package/dist/styles/catalog/components/catalog-resource-footer.css +37 -0
  149. package/dist/styles/catalog/components/catalog-secondary-sections.css +38 -0
  150. package/dist/styles/catalog/components/catalog-section-list.css +22 -0
  151. package/dist/styles/catalog/components/catalog-sidebar.css +82 -0
  152. package/dist/styles/catalog/components/catalog-stage.css +60 -0
  153. package/dist/styles/catalog/components/catalog.css +22 -0
  154. package/dist/styles/catalog.css +10 -587
  155. package/dist/styles/grid.css +11 -0
  156. package/dist/styles/list-header.css +4 -60
  157. package/dist/styles/resizable-region.css +24 -0
  158. package/dist/styles/select.css +0 -3
  159. package/dist/styles/split-view.css +1 -1
  160. package/dist/styles/state-banner.css +0 -27
  161. package/dist/styles/styles.css +2 -0
  162. package/dist/styles/tab-bar.css +6 -1
  163. package/dist/styles/text.css +4 -0
  164. package/dist/styles/token-search-field.css +5 -40
  165. package/dist/styles/toolbar-control-group.css +110 -16
  166. package/dist/styles/toolbar-text.css +4 -0
  167. package/dist/styles/toolbar.css +72 -0
  168. package/dist/styles/webawesome.css +31 -0
  169. package/dist/sunken-panel.d.ts +3 -1
  170. package/dist/sunken-panel.js +1 -1
  171. package/dist/surface-scaffold.d.ts +6 -2
  172. package/dist/surface-scaffold.js +1 -1
  173. package/dist/tab-bar.d.ts +8 -1
  174. package/dist/tab-bar.js +1 -1
  175. package/dist/tab-scaffold.d.ts +3 -1
  176. package/dist/tab-scaffold.js +3 -1
  177. package/dist/tab-scaffold.js.map +1 -1
  178. package/dist/text.d.ts +5 -2
  179. package/dist/text.js +1 -1
  180. package/dist/token-search-field.d.ts +5 -1
  181. package/dist/token-search-field.js +1 -1
  182. package/dist/toolbar-control-group.d.ts +33 -3
  183. package/dist/toolbar-control-group.js +1 -1
  184. package/dist/toolbar-text.d.ts +3 -1
  185. package/dist/toolbar-text.js +1 -1
  186. package/dist/toolbar.d.ts +9 -1
  187. package/dist/toolbar.js +1 -1
  188. package/dist/types-CzgSrFXP.d.ts +64 -0
  189. package/dist/value-table.d.ts +6 -2
  190. package/dist/value-table.js +1 -1
  191. package/dist/wire-resizable-regions.js +1 -1
  192. package/dist/wire-token-search-fields.js +1 -1
  193. package/dist/workbench.d.ts +3 -1
  194. package/dist/workbench.js +3 -1
  195. package/dist/workbench.js.map +1 -1
  196. package/docs/accessibility.md +11 -1
  197. package/docs/catalog.md +25 -7
  198. package/docs/component-contract.md +71 -22
  199. package/docs/component-selection.md +19 -14
  200. package/docs/design-philosophy.md +6 -5
  201. package/docs/layout.md +24 -1
  202. package/docs/recipes.md +5 -5
  203. package/docs/type-contracts.md +1 -0
  204. package/docs/ux-demo.md +4 -4
  205. package/docs/webawesome-theme.md +8 -4
  206. package/llms.txt +35 -8
  207. package/package.json +15 -2
  208. package/ux-demo/recipes/app-shell.css +87 -0
  209. package/ux-demo/recipes/app-shell.tsx +207 -178
  210. package/ux-demo/recipes/collapsible-sidebar.css +83 -0
  211. package/ux-demo/recipes/collapsible-sidebar.tsx +152 -122
  212. package/ux-demo/recipes/compact-toolbar.css +35 -0
  213. package/ux-demo/recipes/compact-toolbar.tsx +104 -100
  214. package/ux-demo/recipes/composer-form.css +19 -0
  215. package/ux-demo/recipes/composer-form.tsx +19 -15
  216. package/ux-demo/recipes/list-detail-dialog.css +42 -0
  217. package/ux-demo/recipes/list-detail-dialog.tsx +127 -125
  218. package/ux-demo/recipes/list-workspace-states.css +30 -0
  219. package/ux-demo/recipes/list-workspace-states.tsx +66 -60
  220. package/ux-demo/recipes/loading-inspector.css +7 -0
  221. package/ux-demo/recipes/loading-inspector.tsx +151 -131
  222. package/ux-demo/recipes/navigation-sidebar.css +12 -0
  223. package/ux-demo/recipes/navigation-sidebar.tsx +116 -105
  224. package/ux-demo/recipes/navigation-stack.css +12 -0
  225. package/ux-demo/recipes/navigation-stack.tsx +47 -37
  226. package/ux-demo/recipes/recipe-root.css +67 -0
  227. package/ux-demo/recipes/recipe-root.tsx +87 -0
  228. package/ux-demo/recipes/workspace-header.css +7 -0
  229. package/ux-demo/recipes/workspace-header.tsx +60 -61
  230. package/dist/chunk-2TYUFZUC.js.map +0 -1
  231. package/dist/chunk-6NDBE2OK.js.map +0 -1
  232. package/dist/chunk-6WTETUZ6.js.map +0 -1
  233. package/dist/chunk-AQFWBG23.js.map +0 -1
  234. package/dist/chunk-B6JYRX3P.js.map +0 -1
  235. package/dist/chunk-CAB6NQ2T.js.map +0 -1
  236. package/dist/chunk-CK6P5AUK.js.map +0 -1
  237. package/dist/chunk-DAO2VFTT.js.map +0 -1
  238. package/dist/chunk-DYBPI5O2.js.map +0 -1
  239. package/dist/chunk-E34TE2ZI.js.map +0 -1
  240. package/dist/chunk-EYALJQQD.js.map +0 -1
  241. package/dist/chunk-GYGM363P.js.map +0 -1
  242. package/dist/chunk-I45QO2IZ.js.map +0 -1
  243. package/dist/chunk-I4V3UDSA.js.map +0 -1
  244. package/dist/chunk-OGNE3OFT.js.map +0 -1
  245. package/dist/chunk-OGTJNHRG.js.map +0 -1
  246. package/dist/chunk-OMSRSL3B.js.map +0 -1
  247. package/dist/chunk-SOK5EUF7.js.map +0 -1
  248. package/dist/chunk-T7DOWS6T.js.map +0 -1
  249. package/dist/chunk-TIGSG6BX.js.map +0 -1
  250. package/dist/chunk-UDHHKZWO.js.map +0 -1
  251. package/dist/chunk-V6GQKOX2.js.map +0 -1
  252. package/dist/chunk-V6HCTCVD.js.map +0 -1
  253. package/dist/chunk-VXFGMDA7.js.map +0 -1
  254. package/dist/chunk-W2FK7ZNY.js.map +0 -1
  255. package/dist/chunk-WSXUPSID.js.map +0 -1
  256. package/dist/chunk-WVLMZ5SG.js.map +0 -1
  257. package/dist/chunk-XKFRMGMW.js.map +0 -1
  258. package/dist/chunk-Y6LYKFXY.js.map +0 -1
  259. package/dist/chunk-ZPWZIVI6.js.map +0 -1
  260. package/ux-demo/recipes/recipes.css +0 -453
@@ -7,7 +7,7 @@ decision procedure.
7
7
 
8
8
  1. Search the [Kerf UX catalog](./ux-demo.md) and the supported Web Awesome set.
9
9
  2. Reuse a primitive when its purpose, anatomy, state, and interaction match.
10
- 3. Compose primitives for recurring layout. Prefer public props and tokens; when a composition needs a selector, target only classes listed in the catalog's `publicClasses` contract.
10
+ 3. Compose primitives for recurring layout through public props and tokens. Do not select one component from another component's stylesheet.
11
11
  4. Add a thin application adapter for product copy, domain mapping, actions, routing, persistence, permissions, and transport.
12
12
  5. Use custom markup only when the semantic contract differs. If the pattern recurs across products, open an upstream component or recipe request.
13
13
 
@@ -15,12 +15,13 @@ The application adapter is usually a plain function that maps domain state to
15
15
  component props plus stable `data-action` values. It is not a fork of package
16
16
  markup or CSS.
17
17
 
18
- **Don't fight the components.** The package is built to look right unstyled, so
19
- custom CSS is the exception. Before adding `padding`, `margin`, `width`, `height`,
20
- `border`, `background`, a wrapper card, or a decoration, check whether the
21
- component, the pane, or the content-item already owns it — it almost always does,
22
- and adding more usually double-insets or fights it. Trust component defaults and
23
- fix the surrounding layout instead of overriding a control. See
18
+ **Don't fight the components.** The package is built to look right without an
19
+ application customization layer. Before adding `padding`, `margin`, `width`,
20
+ `height`, `border`, `background`, a wrapper card, or a decoration, check whether
21
+ the component, the pane, or the content-item already owns it — it almost always
22
+ does. Express presentation through props and tokens. Encapsulate genuinely new
23
+ structure in a self-styled application component, and never reach into a nested
24
+ Kerf or Web Awesome component from that stylesheet. See
24
25
  [`design-philosophy.md`](./design-philosophy.md) "Reach for the primitive, not for
25
26
  CSS".
26
27
 
@@ -38,7 +39,7 @@ foreground/background contrast relationships and visible focus ring.
38
39
 
39
40
  ### Typed runtime dimensions
40
41
 
41
- Use `Row` or `List` spacing shorthands for repeated sibling spacing: `gap="xs"`,
42
+ Use `Row`, `Grid`, or `List` spacing shorthands for repeated sibling spacing: `gap="xs"`,
42
43
  `gap="m"`, and the other `UiSpaceName` values resolve to their matching
43
44
  `--kui-space-*` token. Use `Spacer` for one deliberate empty width or height, or
44
45
  pass `flex` to consume the remaining space along a flex parent's main axis.
@@ -53,23 +54,25 @@ application-owned length tokens. `plus` creates a non-standalone
53
54
 
54
55
  ```tsx
55
56
  import { calc, pct, plus, rem, space } from "@kerfjs/ui/css-values";
57
+ import { Grid } from "@kerfjs/ui/grid";
56
58
  import { List } from "@kerfjs/ui/list";
57
59
  import { Row } from "@kerfjs/ui/row";
58
60
 
59
61
  <List gap="xs">...</List>;
62
+ <Grid columns={3} gap="m">...</Grid>;
60
63
  <Row hAlign="full" vAlign="middle" gap="m">...</Row>;
61
64
  <List gap={space("m")}>...</List>;
62
65
  <List gap={calc(plus(rem(0.25), pct(10)))}>...</List>;
63
66
  ```
64
67
 
65
68
  `CssValue` is the common branded base, not an arbitrary-string constructor.
66
- Raw strings such as `"0.25rem"` no longer satisfy `Row.gap` or `List.gap`; this breaking
69
+ Raw strings such as `"0.25rem"` do not satisfy `Row.gap`, `Grid.gap`, or `List.gap`; this
67
70
  contract lands before the 5.0 stable release so misspelled tokens and incomplete
68
71
  expressions fail during typechecking rather than in the browser.
69
72
 
70
73
  Other CSS-valued props use distinct contracts: `Spacer.width` and
71
74
  `Spacer.height` take `UiSpaceName | CssLength`; `flex()` creates `CssFlex` for
72
- `Row.flex` and `List.flex`; `Skeleton` dimensions take typed lengths (plus finite intrinsic
75
+ `Row.flex`, `Grid.flex`, and `List.flex`; `Skeleton` dimensions take typed lengths (plus finite intrinsic
73
76
  keywords for width/height); and `uiColor()`/`colorVar()` create `CssColor` for
74
77
  `SelectChoice.color`. Do not substitute one brand for another. Row components
75
78
  have no declaration-string `style` prop; use `className`, documented public
@@ -119,13 +122,15 @@ an upstream component or recipe request.
119
122
  | Interface need | Use when | Do not use when; nearest alternative | Required wiring | Application owns | Import | Recipe |
120
123
  | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
121
124
  | Icon — `LucideIcon` | A decorative or explicitly labeled Lucide-compatible icon belongs in app UI. | Do not use an icon as the only name of an unfamiliar action; add visible or accessible text. Prefer it over Web Awesome `wa-icon`. | None. | Icon choice and meaningful label. | `@kerfjs/ui/lucide-icon` | [Accessibility](./accessibility.md#shared-rules) |
125
+ | Compact metadata — `Badge` | A short status, count, category, or metadata label needs self-owned visual treatment configured by semantic props. | Do not use it as a button, link, or removable chip. Do not restyle consuming-component descendants to create a badge. | Choose `tone`, `appearance`, `shape`, and `size`; supply `label` for abbreviated content or `ariaHidden` when a surrounding component already owns the same accessible text. | Metadata text, semantic tone mapping, and abbreviated-content labeling. | `@kerfjs/ui/badge` | [Component ownership](./component-contract.md#ownership-boundaries) |
122
126
  | Disclosure indicator — `DisclosureArrow` | A control needs one animated 18px root-scaled visual for open and closed state, including configurable directions or a replacement icon. | Do not use it as the interactive control or accessible name; place it inside the button or control that exposes expanded state. | Pass the controlled `open` state, render it inside the owning control, author replacement icon content facing right before transforms, and override `--kui-disclosure-arrow-size` only when another visual size is required. Direction changes use the shortest rotation path, with counterclockwise chosen for a 180-degree closed-to-open tie. | Open state, interaction, accessible name, size override, replacement glyph, direction choices, and shortest-path rotation. | `@kerfjs/ui/disclosure-arrow` | [Component ownership](./component-contract.md#ownership-boundaries) |
123
127
  | Application toolbar or heading — `Toolbar` | Leading identity, optional centered content, and trailing controls form one horizontal app bar or a page/panel/dialog heading. | Do not add bespoke heading geometry or a private wrapper; use the same zone contract everywhere. | Put identity/title text directly in a zone as `ToolbarText`; group controls and icon tiles in `ToolbarControlGroup`. | Actions, command availability, responsive relocation, state, and supporting copy below a heading. | `@kerfjs/ui/toolbar` | [Toolbar composition](../README.md#component-subpaths) |
124
- | Toolbar control cluster — `ToolbarControlGroup` | Related toolbar controls need contained/borderless chrome, compact/tight geometry, icon/text/mixed/avatar anatomy, selected treatment, or nested dropdown sizing. | Do not use it merely to align unrelated buttons; use Toolbar's named `SafeHtml` prop slots or ordinary layout. Web Awesome `wa-button-group` is only for an exceptional grouped-action contract. | Delegate child actions; declare typed content and appearance instead of overriding descendants or `::part(base)`; use `avatarImage` instead of inserting an `img` (single groups paint the outer group; multi-button groups paint the pressed highlight); use `SegmentedControl` for an exclusive choice. | Actions, pressed/expanded state, content, and policy. | `@kerfjs/ui/toolbar-control-group` | [Component ownership](./component-contract.md#ownership-boundaries) |
128
+ | Toolbar control cluster — `ToolbarControlGroup`, `ToolbarActionLink` | Related toolbar buttons, semantic links, compact/tight controls, avatar actions, or a dropdown trigger/menu surface need one configured chrome contract. | Do not use it merely to align unrelated controls. Do not restyle anchors or dropdown parts in application CSS; use `ToolbarActionLink`, `overflow`, and `menuInset`. Web Awesome `wa-button-group` is only for an exceptional grouped-action contract. | Delegate child actions; use `ToolbarActionLink` for native navigation, `overflow="scroll"` for bounded action rows, and `menuInset` for dropdown surface padding. Use `avatarImage` instead of inserting an `img`; use `SegmentedControl` for an exclusive choice. | Actions, destinations, pressed/expanded state, content, and policy. | `@kerfjs/ui/toolbar-control-group` | [Component ownership](./component-contract.md#ownership-boundaries) |
125
129
  | Floating controls over content — `FloatingToolbar` | A small cluster of controls (e.g. a drawer restore) must float over scrolling content — distinct and forced-dark — inside a positioned container. | Do not use it for a primary page or panel toolbar (use `Toolbar`), or for anything that must sit above dialogs/overlays — it is not top-layer. | Compose `ToolbarControlGroup`s and delegate their actions; the app owns visibility and position. | Controls, visibility, and position. | `@kerfjs/ui/floating-toolbar` | [Component subpaths](../README.md#component-subpaths) |
126
130
  | Toolbar identity text — `ToolbarText` | A toolbar needs extra-large (page/panel title), large, default, or compact textual identity. | Plain text is not a heading by default; when a title needs heading semantics pass `headingLevel`. | Optional `headingLevel` for `role="heading"` + `aria-level`. Defaults to one line, ellipsized when it does not fit; `wrap` flows onto multiple lines, `ellipsis` toggles the trailing …, and `maxLines` caps a wrap. | Text, heading level, overflow behavior, and responsive priority. | `@kerfjs/ui/toolbar-text` | [Toolbar composition](../README.md#component-subpaths) |
127
131
  | Semantic heading or paragraph — `Text` | Ordinary application copy needs native `h1`–`h6` or `p` semantics plus standard geometry, or a semantic quiet/danger tone, compact size, or monospace font. | Do not choose heading levels for appearance alone or recreate muted, error, small, or code utility classes. Use `ToolbarText` for toolbar identity/title content and `ListInsetText` when list-aligned prose needs full margin/border/padding geometry. | Omit `variant` for the default `p`, or choose `h1`–`h6` from the document outline. Independently compose `tone="quiet | danger"`, `size="compact"`, and `font="monospace"`; ordinary native global, `data-_`, and `aria-_` attributes pass through. | Copy, localization, semantic variant, presentation roles, and document heading hierarchy. | `@kerfjs/ui/text` | [Text](./layout.md#text) |
128
132
  | Vertical stack — `List` | Rows, sections, or arbitrary components need a consistent vertical stretch layout, optional standard/custom gap, flex growth, one vertical scroll owner, or edge dividers. | It is layout-only and does not provide `ul`/`ol` semantics. Do not nest scroll owners or use it when a horizontal layout is required. | Choose `gap`, `flex`, `scrollable`, and canonical `dividerSides` (`t`, `r`, `b`, `l` combinations in that order); bound the block size when scrolling. | Child semantics/content, containing height, and which optional layout behaviors apply. | `@kerfjs/ui/list` | [Pane geometry](../README.md#pane-and-content-geometry) |
133
+ | Equal-width columns — `Grid` | A fixed positive number of sibling columns must divide the available width evenly, including when child content has different intrinsic widths. | Do not use it for intrinsic, asymmetric, spanning, auto-fit, or masonry tracks; use application-owned CSS grid. Do not use it for a user-operable separator; use `ResizableRegion`. | Choose `columns`, a typed `gap`, and optional typed `flex`; preserve source order when the app changes the count responsively. | Child semantics/content, the column count and responsive policy, and typed gap/flex choices. | `@kerfjs/ui/grid` | [Grid](./layout.md#grid) |
129
134
  | Navigation row — `ListItem` | A pane or navigation area needs a selectable, disabled, dormant-trailing, or multiline action row. | Do not put a control in `trailing`; use `ListActionRow` when the trailing region must be independently interactive. Use an `<a>` for navigation that must retain link behavior, a native `<button>` for an ordinary action, or implement the complete ARIA menu widget. | Delegate its `data-action`; compose inside a `.kui-content` section. Put domain event/drop metadata in `rootAttributes` rather than adding wrapper markup. The rounded root clips overflowing descendants; set `multiline` when the primary label should wrap. | Routing, selection, permissions, copy, action handling, and domain `data-*` values. | `@kerfjs/ui/list-item` | [Pane geometry](../README.md#pane-and-content-geometry) |
130
135
  | Navigation row with trailing action — `ListActionRow` | A full-width row needs a selectable primary action and an independently focusable trailing action. | Use `ListItem` when trailing content is dormant metadata. Do not put controls inside the row's `label`, `icon`, or `trailingActionIcon` SafeHtml slots. Do not use `AppTab` outside tablist semantics or `ToolbarControlGroup` outside a toolbar. | Delegate both action strings; update controlled selection and any popover/context-menu state in the app. | Routing, selection, both action policies, domain metadata, and popover/context-menu behavior. | `@kerfjs/ui/list-action-row` | [Accessibility](./accessibility.md#listactionrow) |
131
136
  | Navigation section heading — `ListHeader` | A menu section needs a full-width label, semantic count, non-count badge, logical-end action, or real disclosure state. | Do not concatenate counts into `label` or put numeric content in `badge`; use `count` with the localized full phrase in `countLabel`. Do not add a disclosure arrow to navigation that reveals nothing. Do not shrink its 44px action target to the 18px visual. Do not use it as a page, panel, or dialog title; use a heading `Toolbar`. | Delegate its optional action; the app controls expanded state and revealed content. Toggle mode supplies `DisclosureArrow` unless `actionIcon` replaces it. Use `triggerAttributes` only for domain `data-*` or a native popover relationship. | Section organization, valid count and localized count label, disclosure state and content, non-count badge content, popover target behavior, and policy. | `@kerfjs/ui/list-header` | [Pane geometry](../README.md#pane-and-content-geometry) |
@@ -139,9 +144,9 @@ an upstream component or recipe request.
139
144
  | Standalone rail or drawer — `CollapsiblePanel` | One left/right rail or bottom drawer needs controlled collapse without adopting the complete `Workbench` shell. | Do not use it for several coordinated workspace panels or retain an empty icon-only rail while collapsed. | Pair it with `CollapsiblePanelToggle`; call `wireSidebar` for focus transfer, Escape/backdrop dismissal, compact overlay trapping, and persistence integration. | Panel content/size, collapsed state, toggle placement, persistence, and compact policy. | `@kerfjs/ui/collapsible-panel` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
140
145
  | Lowered content surface — `SunkenPanel` | A main work area or nested content group needs one visually lowered surface with a compact inset and vertical stack. | Do not use it merely to add padding, as a replacement for `Pane` anatomy, or around children that already own the same outer surface. | Compose the application-owned content as children, choose the default `rounded` shape or `square` for a flush edge-to-edge area, and pass `ariaLabel` only when the surface is a distinct named region. | Region semantics, accessible name, shape, scroll ownership, child ordering, and content. | `@kerfjs/ui/sunken-panel` | [SunkenPanel](./sunken-panel.md) |
141
146
  | Menu composition | Navigation sections need full-size rows and the same content-item geometry as every other pane. | Do not add sidebar-specific wrapper padding, shrink targets to icon size, nest an interactive trailing control in `ListItem`, or use a chevron on a row that does not disclose content. Use ordinary links for a different navigation contract. | Compose `ListHeader`, `ListItem`, and `ListActionRow` in `.kui-content`; use `ListHeader` toggle mode with real controlled content, and use `.kui-content-item` for other surfaces plus a pane footer for toolbar actions. | Information architecture, disclosure content and state, responsive drawer/shell behavior, and token overrides. | `@kerfjs/ui/layout.css` | [Pane geometry](../README.md#pane-and-content-geometry) |
142
- | Resizable application pane — `ResizableRegion`, `clampRegionSize`, `resizeRegionFromPointer` | A controlled split pane needs the Kerf separator, collapse state, pointer plus keyboard resizing, or a product-specific decorative grip. | Do not use it for a static two-column layout; use CSS grid. Prefer it over Web Awesome `wa-split-panel` unless that component's distinct API is required. Keep `handleIcon` noninteractive. | Call `wireResizableRegions` from `@kerfjs/ui/wire-resizable-regions` once and retain its disposer. | Size signal, min/max policy, collapse policy, persistence, and optional decorative handle icon. | `@kerfjs/ui/resizable-region` | [ResizableRegion contract](./accessibility.md#resizableregion) |
147
+ | Resizable application pane — `ResizableRegion`, `clampRegionSize`, `resizeRegionFromPointer` | A controlled split pane needs the Kerf separator, collapse state, pointer plus keyboard resizing, or a product-specific decorative grip. | Do not use it for static equal columns; use `Grid`. Use application CSS grid for static asymmetric or intrinsic tracks. Prefer it over Web Awesome `wa-split-panel` unless that component's distinct API is required. Keep `handleIcon` noninteractive. | Call `wireResizableRegions` from `@kerfjs/ui/wire-resizable-regions` once and retain its disposer. | Size signal, min/max policy, collapse policy, persistence, and optional decorative handle icon. | `@kerfjs/ui/resizable-region` | [ResizableRegion contract](./accessibility.md#resizableregion) |
143
148
  | One application tab — `AppTab` | A controlled app tab needs selection, close, drag, leading/trailing anatomy, compact/segmented/icon-only presentation, label truncation, safe domain metadata, or a product-specific close glyph. | Do not render it alone or use it for a small settings choice; compose in `TabBar`, or use `SegmentedControl`. Keep `closeIcon` noninteractive. | Compose in `TabBar`; let `wireTabBars` manage interaction. Put only domain `data-*` values in `rootAttributes`; use typed appearance props instead of descendant CSS. | Tab identity, order, selection, close policy, content, domain metadata values, and outer placement. | `@kerfjs/ui/app-tab` | [Tabs contract](./accessibility.md#tabs) |
144
- | Application tab strip — `TabBar`, `wireTabBars`, `reorderTabs` | Tabs switch page regions and may overflow, close, reorder, fill a segmented inspector strip, or sit beside a trailing action. | Do not use it for a compact local view toggle; use `SegmentedControl`. Do not use it for a long choice list; use `Select`. Prefer it over Web Awesome `wa-tab-group`, `wa-tab`, and `wa-tab-panel` for Kerf app tabs. | Call `wireTabBars` once, retain the disposer, and apply `onReorder` synchronously; `reorderTabs` is the default array helper. Choose typed presentation/allocation props while the parent owns outer placement. | Ordered tabs, selection, panels, routing, closing, persistence, and outer placement. | `@kerfjs/ui/tab-bar` plus `@kerfjs/ui/wire-tab-bars` | [Tabs contract](./accessibility.md#tabs) |
149
+ | Application tab strip — `TabBar`, `wireTabBars`, `reorderTabs` | Tabs switch page regions and may overflow, close, reorder, fill a segmented inspector strip, sit beside a trailing action, or keep that adjacent action distinct from a far-edge end action. | Do not use it for a compact local view toggle; use `SegmentedControl`. Do not use it for a long choice list; use `Select`. Prefer it over Web Awesome `wa-tab-group`, `wa-tab`, and `wa-tab-panel` for Kerf app tabs. | Call `wireTabBars` once, retain the disposer, and apply `onReorder` synchronously; `reorderTabs` is the default array helper. Combine `trailingPlacement="adjacent"` with `end` for split actions; TabBar owns the shrinking/scrolling tab geometry and keeps both actions visible. Choose typed presentation/allocation props while the parent owns outer placement. | Ordered tabs, selection, panels, routing, closing, persistence, action content, and outer placement. | `@kerfjs/ui/tab-bar` plus `@kerfjs/ui/wire-tab-bars` | [Tabs contract](./accessibility.md#tabs) |
145
150
  | 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. | 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) |
146
151
  | Dialog or popup geometry — `DialogSurface`, `PopupSurface` | Recurring Web Awesome dialogs need standard sizes/presentations/body/footer insets, 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`. | State, focus lifecycle, dismissal, labels, content, and outer placement. | `@kerfjs/ui/surface-scaffold` | [Surface scaffolds](./surface-scaffold.md) |
147
152
  | 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. | Values, formatting, icon meaning, and empty/loading policy. | `@kerfjs/ui/value-table` | [Component ownership](./component-contract.md#ownership-boundaries) |
@@ -158,7 +163,7 @@ an upstream component or recipe request.
158
163
  - `Toolbar` serves persistent app chrome and headings; the zone contents and accessible naming distinguish the purpose.
159
164
  - `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `Select` handles a longer value list.
160
165
  - `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.
161
- - `ResizableRegion` is an interactive controlled pane. CSS grid is the right answer when columns do not need a user-operable separator.
166
+ - `ResizableRegion` is an interactive controlled pane. `Grid` is the right answer for static equal-width columns; application CSS grid remains the answer for asymmetric or intrinsic tracks.
162
167
  - A `ResizableRegion` overlay automatically clamps both its track and fixed-size
163
168
  animated content to the responsive overlay maximum. Set the policy and maximum
164
169
  on the component instead of adding application descendant width/height fixes.
@@ -28,16 +28,17 @@ Establish one reading order and one dominant action per decision point. Group fi
28
28
 
29
29
  The package is designed to look right _unstyled_. A screen built from the
30
30
  primitives, their props, and the semantic tokens should already read well, so
31
- custom CSS is the exception. Before adding any `padding`, `margin`, `width`,
31
+ applications do not carry a component-customization stylesheet. Before adding any `padding`, `margin`, `width`,
32
32
  `height`, `border`, `background`, wrapper card, or decoration, check whether the
33
33
  component, the pane, or the content-item already owns it — it almost always does,
34
34
  and adding more usually **double-insets** or fights the component (the most common
35
35
  mistake). Trust component defaults: render a control at its natural size and color
36
36
  and fix the surrounding layout rather than overriding the control. Legitimate
37
- custom CSS is limited to genuinely new structure, and even then may only join a
38
- component's documented `publicClasses` or override documented `--kui-*` tokens at a
39
- real composition boundary — never size, space, or re-skin a primitive by hand, and
40
- never build a heading, toolbar, card, or pane geometry that a primitive provides.
37
+ CSS for genuinely new structure belongs to the application-owned component that
38
+ renders that structure. It must not select a nested Kerf or Web Awesome component;
39
+ configure those children through props and documented tokens instead. Never size,
40
+ space, or re-skin a primitive by hand, and never build a heading, toolbar, card,
41
+ or pane geometry that a primitive provides.
41
42
 
42
43
  ### Panes share one child-owned geometry
43
44
 
package/docs/layout.md CHANGED
@@ -112,7 +112,7 @@ The two relationships that get confused most are 8px vs 24px: 8px is _inside_ a
112
112
  group, 24px is _between_ major, differing regions. 16px sits between them for
113
113
  same-kind groups.
114
114
 
115
- For `Row.gap` and `List.gap`, pass these token names directly (`gap="xs"`, `gap="m"`) or use
115
+ For `Row.gap`, `Grid.gap`, and `List.gap`, pass these token names directly (`gap="xs"`, `gap="m"`) or use
116
116
  `space('xs')` from `@kerfjs/ui/css-values` when composing a value in JavaScript.
117
117
  Use `rem`, `em`, `px`, `pct`, `lengthVar`, and `calc(plus(...))` only when a
118
118
  named spacing relationship does not express the requirement; do not pass raw
@@ -169,6 +169,29 @@ uses physical width and height. Prefer `Row.gap` or `List.gap` for uniform
169
169
  sibling rhythm, and never use Spacer to cancel or duplicate component-owned
170
170
  insets.
171
171
 
172
+ ## Grid
173
+
174
+ Use `Grid` from `@kerfjs/ui/grid` when a fixed positive number of columns must
175
+ share the available width evenly. Every track is `minmax(0, 1fr)`, so a child
176
+ with wider intrinsic content cannot make its column wider than its peers. Grid
177
+ defaults to the `xs` gap and accepts the same finite spacing names, complete
178
+ typed `CssLength` values, and typed flex-participation contract as Row.
179
+
180
+ ```tsx
181
+ import { Grid } from "@kerfjs/ui/grid";
182
+
183
+ <Grid columns={2} gap="m">
184
+ <label>Quantity <input /></label>
185
+ <label>Unit <input /></label>
186
+ </Grid>;
187
+ ```
188
+
189
+ `columns` must be a positive safe integer; invalid counts throw instead of
190
+ silently producing invalid CSS. Grid is layout-only and adds no ARIA grid role.
191
+ The application owns child semantics and responsive count changes. Use
192
+ application-owned CSS grid for intrinsic, asymmetric, spanning, auto-fit, or
193
+ masonry tracks, and use `ResizableRegion` when people must adjust a boundary.
194
+
172
195
  ## Row and List alignment
173
196
 
174
197
  Use `Row` for a horizontal flex layout and `List` for a vertical one. `Row`
package/docs/recipes.md CHANGED
@@ -28,7 +28,7 @@ specific dispatch; either way, wire once at a stable root and retain disposal.
28
28
 
29
29
  ## Desktop application shell
30
30
 
31
- [Open the recipe](../ux-demo/?component=recipe-app-shell) · [TSX source](../ux-demo/recipes/app-shell.tsx) · [shared CSS](../ux-demo/recipes/recipes.css)
31
+ [Open the recipe](../ux-demo/?component=recipe-app-shell) · [TSX source](../ux-demo/recipes/app-shell.tsx) · [shared composition CSS](../ux-demo/recipes/recipe-components.css)
32
32
 
33
33
  Use `Toolbar`, controlled `ResizableRegion` panes, and one
34
34
  `.kui-pane__content` scroll owner per pane. The recipe owns the shell topology;
@@ -164,9 +164,9 @@ Chromium, Firefox, and WebKit by `tests/browser/collapsible-sidebar-recipe.spec.
164
164
  - For direct `wa-*` JSX, import types from `@kerfjs/ui/webawesome`. Import only
165
165
  individual Web Awesome registration modules and theme them with
166
166
  `@kerfjs/ui/webawesome.css`.
167
- - Prefer public props and variables at the composition boundary. When a recipe
168
- needs responsive topology, join only classes listed in the catalog's
169
- `publicClasses`; do not copy component markup or select descendants by tag,
170
- id, attribute alone, or an unlisted implementation class.
167
+ - Prefer public props and variables at the composition boundary. A recipe's own
168
+ component may style the structure it renders, but its stylesheet must not
169
+ select nested Kerf or Web Awesome components. Add missing configuration to the
170
+ owning component instead of copying markup or reaching through its boundary.
171
171
  - Start from the copyable mount adapter, or reproduce its complete boundary:
172
172
  wire stable `data-action` hooks once and retain every disposer.
@@ -26,6 +26,7 @@ public-signature artifact.
26
26
  | `KUI-T011` | Semantic component zones use the recursive `KerfUiContent` type: `SafeHtml`, runtime-empty booleans/nullish values, and readonly nested arrays are valid; arbitrary strings, numbers, and signals are rejected. Explicit text positions such as `ListInsetText` retain their text exception. |
27
27
  | `KUI-T012` | `List.gap` accepts finite `UiSpaceName` shorthands or a complete branded `CssLength`; raw strings and non-standalone `CssLengthExpression` arithmetic are rejected. `calc(plus(...))` promotes a typed expression to a complete value. |
28
28
  | `KUI-T013` | CSS-valued props preserve property grammar: `List.flex` uses `CssFlex`, `Skeleton` dimensions use `CssSize`/`CssLength`, and choice icons use `CssColor`; row declaration strings are absent. |
29
+ | `KUI-T014` | Stable single-root visual components accept the explicit native `slot` attribute. Multi-root `ResizableRegion` and `CollapsiblePanel` remain excluded because no single returned element can own the assignment, and other arbitrary native attributes remain rejected. |
29
30
 
30
31
  `KerfUiContent` lets conditionals and mapped component collections be direct
31
32
  siblings without an otherwise-unnecessary `Fragment`:
package/docs/ux-demo.md CHANGED
@@ -12,18 +12,18 @@ of the current `src/` and `ux-demo/` inputs, so a stale `dist-demo` cannot pass
12
12
  after a route or component edit. `npm run demo:serve` remains a preview-only
13
13
  command for an already-built catalog.
14
14
 
15
- Seven lazy production-composition routes live under the `Recipes` category.
15
+ Ten lazy production-composition routes live under the `Recipes` category.
16
16
  Each `?component=recipe-*` route uses public package primitives, real wiring,
17
17
  deterministic state, and semantic layout owners from the [recipe guide](./recipes.md).
18
18
 
19
19
  - It imports public component subpaths, exercising their browser-selected reachable CSS rather than a catalog-only style path.
20
20
  - The `ListActionRow` route demonstrates sibling primary/trailing native controls, independent disabled states, controlled selection, first-line leading-icon alignment for wrapped labels, and an application-owned native popover relationship without nesting controls. The `ListItem` route demonstrates the same multiline leading-icon anchor alongside its single-control navigation contract.
21
- - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes without application readouts. The separate `Application tabs composition` route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
21
+ - The `AppTab` and `TabBar` routes are focused component specimens: AppTab shows its selected, compact, segmented, icon-only, truncation, placeholder, metadata, and close-icon axes inside the required tablist context; TabBar shows its rail, segmented, inspector, allocation, placement, and accessory-zone axes, including an adjacent action plus a separately pinned end action whose tabs overflow at narrow widths, without application readouts. The separate `Application tabs` composition route demonstrates controlled selection, matching panel content, overflow, adding, closing, keyboard/pointer reordering, and application-owned ordering. The `ResizableRegion` route likewise replaces only the decorative handle glyph while retaining separator semantics and shared wiring.
22
22
  - Every first-party visual component and every free Web Awesome 3.12 component has its own stable `?component=<id>` URL and focused demo. Composite header, menu, and feedback scenarios remain addressable alongside their subcomponents; the root opens the first Kerf component. The former aggregate Web Awesome theme specimen is intentionally omitted because its component families are covered by those focused routes.
23
- - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Non-supported Web Awesome recommendations project to a visible `Discouraged` sidebar tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
23
+ - `ai/component-catalog.json` is the canonical catalog metadata. A deterministic checked-in projection supplies the demo's unique id, category, kind (`component`, `composition`, or `recipe`), source (`kerf` or `webawesome`), name, description, recommendation, direct `uses` dependencies, existing documentation path, and demo-source path; first-party, recipe, and Web Awesome renderer maps are exhaustively typed against its generated id unions. Functional groups follow their authored importance order; components precede compositions inside each group, equal-importance entries use alphabetical order, and the Recipes group comes last. Composition names stay concise and project a visible `Composition` sidebar tag. Non-supported Web Awesome recommendations project to a visible `Discouraged` tag, with Popup as the deliberate conditional exception. Demo paths resolve to the matching `ui/ux-demo/demos/<id>.tsx` or `ui/ux-demo/recipes/*.tsx` file, or to `ui/ux-demo/webawesome-demos.tsx` for Web Awesome entries. `ui/ux-demo/main.tsx` owns only the catalog shell, shared state, routing, and interaction wiring. First-party component entries additionally derive `ui/src/<public-subpath>.tsx` from their canonical `delivery.browserImport` or opt-in `delivery.moduleImport`.
24
24
  - Every detail footer groups compact `Demo source` and `Guidance` actions; first-party component details also expose `Component source`. Web Awesome details say `Integration guidance` to distinguish the local integration contract from the upstream component implementation. Each button retains its repository-relative path in the DOM for verification and uses a deploy-safe absolute GitHub blob URL with a contextual new-tab name; the demo does not depend on a local source server or add a package runtime export. At phone widths, the resource group and related-component selector stack as full-width rows, with any resource overflow contained locally so labels cannot overlap the selector or widen the document.
25
25
  - The master/detail shell groups first-party routes by category with production `ListHeader` and `ListItem` components. Catalog entries can supply short reusable trailing tags; the Web Awesome projection uses them to label exactly the 15 discouraged choices. The 70 ecosystem components live in a separate `Web Awesome` disclosure with seven subgroups, so they remain discoverable without being presented as first-class `@kerfjs/ui` exports. Direct Web Awesome routes open the disclosure and reveal the selected row at wide sizes; narrow layouts keep the expanded list in document flow rather than introducing a competing nested scroller.
26
- - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with supporting copy in its own aligned row; the current-view heading is also a toolbar. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the complete scrollable preview region rather than a nested card. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
26
+ - The shell itself uses `@kerfjs/ui/layout.css`: sidebars, details, previews, dialogs, and resize specimens share unpadded panes plus self-contained content items. Its sidebar identity uses the Kerf logo beside a vertically centered title, with supporting copy in its own aligned row; the current-view heading is also a toolbar. Collapsing the catalog removes the pane completely and moves its restore action to the main toolbar's leading edge instead of leaving an icon-only rail. The selected view description remains inset below the page toolbar. The checkerboard belongs to the preview scroll owner rather than a viewport-height stage or nested card, and one explicitly repeated SVG tile keeps the pattern continuous through below-fold content. A lightly separated footer keeps live status above compact source, guidance, and relationship groups. The focused `ResizableRegion` specimen stretches its surface through the available stage height and reports committed width in that shared status footer; narrow layouts retain local horizontal access when the pane is resized wider than the stage, and the split remains readable at 200% root scaling. Geometry coverage checks wide, intermediate, narrow, and 200% zoom layouts.
27
27
  - The composer-form recipe demonstrates one coherent visible form surface with toolbar title/supporting-copy hierarchy, fields and actions on the shared 8px control gutter, and 24px major rhythm. Its conditional error or success `StateBanner` is the only nested semantic surface. Reset synchronizes the upgraded Web Awesome fields' live value properties with their empty controlled attributes and announces `Draft reset`; browser coverage preserves behavior and verifies wide, narrow, dark, validation, success, reset, 200% zoom, and forced-colors states.
28
28
  - The list-detail dialog recipe places its header directly on the dialog edge while preserving the header's internal inset, gives the selected project title the complete outer-plus-inner content gutter, lets the value table fill the available detail column between the shared outer margins, and keeps record actions on one outer gutter without an extra content-item inset. Browser geometry coverage protects those relationships at wide and narrow widths.
29
29
  - Catalog-only brand marks, preview dimensions, checkerboard tiles, and the compound display-settings control retain local geometry because they are specimen chrome or intrinsic control anatomy rather than reusable application spacing roles.
@@ -97,6 +97,11 @@ tiers:
97
97
  complete surface and the inset within it. Accordion applies the outer margin
98
98
  once to the group rather than separating connected items; Card's header,
99
99
  body, and footer all receive the full inset.
100
+ - Plain Details use the 8px `--kui-wa-surface-inset` on the trigger and keep the
101
+ body flush; plain Accordion items use that 8px inset on both trigger and body.
102
+ Outlined and sunken Details use the 16px `--kui-wa-container-inset` on the
103
+ trigger and the 8px surface inset on the body, while framed Accordion items
104
+ use the 16px container inset for both regions.
100
105
  - `appearance="sunken"` is a Kerf theme extension for Accordion, Card, and
101
106
  Details. It removes raised chrome, paints the lowered semantic surface, and
102
107
  uses the shared rounded-rectangle radius. Override
@@ -177,10 +182,9 @@ component should declare the no-arrow choice independent of theme context.
177
182
  The UX catalog includes a focused, addressable specimen for every component in
178
183
  this list. Because these are themed ecosystem components rather than
179
184
  first-class `@kerfjs/ui` exports, the sidebar keeps them under a collapsible
180
- `Web Awesome` heading with category subgroups. The aggregate theme route
181
- remains a broad visual-regression surface, while modal, drawer, toast, media,
182
- and helper routes make their own behavior inspectable without opening every
183
- state at once.
185
+ `Web Awesome` heading with category subgroups. There is no aggregate theme
186
+ route: the focused modal, drawer, toast, media, and helper routes make each
187
+ component's behavior inspectable without opening every state at once.
184
188
 
185
189
  ## Badge and tag guidance
186
190
 
package/llms.txt CHANGED
@@ -12,6 +12,10 @@ npm install kerfjs @kerfjs/ui # kerfjs is a peer; Web Awesome is an optional pee
12
12
  Import each component from its own subpath (`@kerfjs/ui/list-item`, `@kerfjs/ui/toolbar`,
13
13
  …); the browser build pulls that component's reachable CSS automatically, so do not
14
14
  maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
15
+ For a public subpath containing several visual components, use a folder with one
16
+ component per source file and one owned stylesheet per visual component. Keep its
17
+ compatibility entrypoints implementation-free: JavaScript/TypeScript only re-exports
18
+ the folder modules, and CSS only imports their owner stylesheets.
15
19
 
16
20
  ## How to build a screen (retrieval order)
17
21
 
@@ -56,9 +60,17 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
56
60
  `ListInsetText`. These are typed props, not native `<slot>` elements,
57
61
  wrapper slot components, or a generic `slots` object. Leave unresolved
58
62
  dynamic expressions unknown.
63
+ Stable single-root Kerf UI components separately accept the explicit native
64
+ `slot="name"` prop for light-DOM composition inside a custom element and
65
+ preserve it across rerenders. Do not infer arbitrary native-attribute
66
+ spreading from this narrow interop contract. `ResizableRegion` and
67
+ `CollapsiblePanel` remain excluded because their optional restore controls
68
+ make their output multi-root; `CollapsiblePanelToggle` remains slot-capable.
59
69
  4. **When authoring a component gallery**, load
60
70
  [catalog-authoring.json](./ai/catalog-authoring.json), then follow its
61
71
  authoritative [Catalog demo authoring contract](./docs/catalog.md#catalog-demo-authoring-contract).
72
+ Use its component/composition/recipe taxonomy and functional-group,
73
+ importance, alphabetical-tie, and recipes-last ordering policy.
62
74
  5. **Apply the rules below and in** [skill.md](./ai/skill.md).
63
75
  6. **Before calling an integration complete**, run
64
76
  [`kerf-ui-doctor`](./docs/ui-doctor.md). Its portable JSON report merges
@@ -81,12 +93,12 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
81
93
  ## Core rules (do the right thing)
82
94
 
83
95
  - **Reach for the primitive, not for CSS.** The package is built to look right
84
- _unstyled_. Before adding `padding`, `margin`, `width`, `height`, `border`,
85
- `background`, a wrapper card, or a decoration, check whether the component, the
86
- pane, or the content-item already owns it — it almost always does. Prefer a
87
- component prop or a `--kui-*` token; write CSS only for genuinely new structure,
88
- and then only join a component's documented `publicClasses` (never reach in by
89
- tag/id/attribute or an unlisted class).
96
+ without an application customization layer. Before adding `padding`, `margin`,
97
+ `width`, `height`, `border`, `background`, a wrapper card, or a decoration,
98
+ check whether the component, pane, or content-item already owns it. Prefer a
99
+ component prop or documented `--kui-*` token. CSS for genuinely new structure
100
+ belongs to the application-owned component that renders it and must not select
101
+ nested Kerf or Web Awesome components.
90
102
  - **Never double-inset.** A `.kui-pane` has no padding; its `.kui-content` children
91
103
  own the geometry (8px inline margin + 1px border + 8px padding). Do not add a
92
104
  container padding on top of a content-item's own margin — pick one owner. Use
@@ -107,10 +119,18 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
107
119
  for List flex, typed lengths for Skeleton geometry, and
108
120
  `uiColor()`/`colorVar()` for choice-icon colors. These brands are intentionally
109
121
  not interchangeable; rows use classes/tokens/props rather than raw `style`.
122
+ - **Use `Grid` for a fixed positive count of equal-width columns.** It uses
123
+ zero-minimum fractional tracks, so intrinsic content cannot make one column
124
+ wider than its peers. The app owns responsive count changes; use custom CSS
125
+ grid for asymmetric/intrinsic tracks and `ResizableRegion` for adjustable
126
+ boundaries. `Grid.gap` and `Grid.flex` use the same typed grammars as Row.
110
127
  - **Keep Web Awesome on the shared inset tiers.** `webawesome.css` uses
111
128
  `--kui-wa-control-inset` (8px controls/items) and
112
129
  `--kui-wa-surface-margin` / `--kui-wa-surface-inset` (8px around/inside
113
130
  Accordion, Card, Details, Callout, and Include), while
131
+ plain Details keep their body flush while plain Accordion items inset both
132
+ trigger and body, framed Details use the 16px container inset only on the
133
+ trigger, and framed Accordion items use it on both regions;
114
134
  `--kui-wa-container-inset` keeps Tab Panel at 16px. Dialog body and footer use
115
135
  the surface (8px) and container (16px) tiers respectively, independent of the
116
136
  dialog's shared `--spacing`. Override the tier at the narrowest useful scope
@@ -200,7 +220,11 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
200
220
  overflow autoscroll, drag preview, and
201
221
  replacement focus after controlled automatic tab activation,
202
222
  `wireResizableRegions` the live drag preview; they report committed changes via
203
- callbacks. Managed TokenSearchField clear also preserves open state and replacement
223
+ callbacks. For a tab-local action plus a separate far-edge action, set
224
+ `TabBar trailingPlacement="adjacent"`, put the local action in `trailing`, and
225
+ the workspace action in `end`; the component keeps both visible while its tab
226
+ list shrinks and scrolls, without application descendant CSS. Managed
227
+ TokenSearchField clear also preserves open state and replacement
204
228
  editor focus before the next input task, so shortcut-letter typing stays in search.
205
229
  Select All deletion does the same; deletion
206
230
  restoration finishes before the next keystroke rather than on a delayed frame.
@@ -237,10 +261,13 @@ maintain a central style list. Configure JSX with `"jsxImportSource": "kerfjs"`.
237
261
  optional string `badge` for a terse count or status beside the title. It
238
262
  inherits the banner tone and is announced with the banner, so keep its text
239
263
  meaningful and never rely on color alone.
264
+ - **Use `Badge` for compact non-interactive metadata.** Configure its semantic
265
+ tone, appearance, shape, and size instead of creating badge CSS in an app or
266
+ consuming component. Use a real button or link for interactive content.
240
267
 
241
268
  ## Public exports
242
269
 
243
- `LucideIcon`, `DisclosureArrow`, `Toolbar`, `ToolbarControlGroup`, `FloatingToolbar`, `ToolbarText`, `Text`,
270
+ `Badge`, `LucideIcon`, `DisclosureArrow`, `Toolbar`, `ToolbarControlGroup`, `ToolbarActionLink`, `FloatingToolbar`, `ToolbarText`, `Text`,
244
271
  `ListItem`, `ListActionRow`, `ListHeader`, `ListInsetControl`,
245
272
  `ListInsetText`, `SegmentedControl`,
246
273
  `Select`, `TokenSearchField` (+ `readTokenSearchField`, `placeTokenSearchCaret`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.48",
3
+ "version": "5.0.0-beta.50",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -58,6 +58,11 @@
58
58
  "browser": "./dist/browser/disclosure-arrow.js",
59
59
  "import": "./dist/disclosure-arrow.js"
60
60
  },
61
+ "./badge": {
62
+ "types": "./dist/badge.d.ts",
63
+ "browser": "./dist/browser/badge.js",
64
+ "import": "./dist/badge.js"
65
+ },
61
66
  "./toolbar": {
62
67
  "types": "./dist/toolbar.d.ts",
63
68
  "browser": "./dist/browser/toolbar.js",
@@ -98,6 +103,11 @@
98
103
  "browser": "./dist/browser/row.js",
99
104
  "import": "./dist/row.js"
100
105
  },
106
+ "./grid": {
107
+ "types": "./dist/grid.d.ts",
108
+ "browser": "./dist/browser/grid.js",
109
+ "import": "./dist/grid.js"
110
+ },
101
111
  "./spacer": {
102
112
  "types": "./dist/spacer.d.ts",
103
113
  "browser": "./dist/browser/spacer.js",
@@ -286,6 +296,7 @@
286
296
  "./webawesome.css": "./dist/styles/webawesome.css",
287
297
  "./styles.css": "./dist/styles/styles.css",
288
298
  "./lucide-icon.css": "./dist/styles/lucide-icon.css",
299
+ "./badge.css": "./dist/styles/badge.css",
289
300
  "./disclosure-arrow.css": "./dist/styles/disclosure-arrow.css",
290
301
  "./toolbar.css": "./dist/styles/toolbar.css",
291
302
  "./toolbar-control-group.css": "./dist/styles/toolbar-control-group.css",
@@ -295,6 +306,7 @@
295
306
  "./text.css": "./dist/styles/text.css",
296
307
  "./list.css": "./dist/styles/list.css",
297
308
  "./row.css": "./dist/styles/row.css",
309
+ "./grid.css": "./dist/styles/grid.css",
298
310
  "./spacer.css": "./dist/styles/spacer.css",
299
311
  "./list-action-row.css": "./dist/styles/list-action-row.css",
300
312
  "./list-item.css": "./dist/styles/list-item.css",
@@ -354,6 +366,7 @@
354
366
  "check:profile": "node scripts/check-application-ui-profile.mjs",
355
367
  "check:demo-conformance": "node scripts/check-catalog-demo-conformance.mjs",
356
368
  "check:recipes": "node scripts/check-recipes.mjs",
369
+ "check:css-ownership": "node scripts/check-css-ownership.mjs",
357
370
  "check:ai-regressions": "node scripts/check-ai-regressions.mjs && node scripts/audit-ai-regression-results.mjs && node scripts/audit-ai-regression-results-v3.mjs",
358
371
  "check:ai-signatures": "node scripts/sync-ai-public-signatures.mjs --check",
359
372
  "ai:signatures:sync": "npm run build && node scripts/sync-ai-public-signatures.mjs",
@@ -379,7 +392,7 @@
379
392
  "test:contract-types:packed": "node scripts/check-packed-type-contracts.mjs",
380
393
  "test:consumer-types": "npm run test:contract-types:source && npm run build && node node_modules/typescript7/bin/tsc -p tests/consumer-types/webawesome/tsconfig.json && npm run test:contract-types:packed",
381
394
  "test:e2e": "playwright test",
382
- "check": "npm run check:catalog && npm run check:component-integrations && npm run check:profile && npm run check:demo-conformance && npm run check:recipes && npm run check:guidance && npm run check:ai-compatibility && npm run check:ai-regressions && npm run lint && npm run typecheck && npm test && npm run test:consumer-types && npm run check:ai-signatures && npm run demo:build && npm run check:design-templates && npm pack --dry-run",
395
+ "check": "npm run check:catalog && npm run check:component-integrations && npm run check:profile && npm run check:demo-conformance && npm run check:recipes && npm run check:css-ownership && npm run check:guidance && npm run check:ai-compatibility && npm run check:ai-regressions && npm run lint && npm run typecheck && npm test && npm run test:consumer-types && npm run check:ai-signatures && npm run demo:build && npm run check:design-templates && npm pack --dry-run",
383
396
  "prepublishOnly": "npm run check",
384
397
  "check:design-templates": "node scripts/check-design-templates.mjs",
385
398
  "check:design-templates:drift": "node scripts/check-design-templates-drift.mjs",
@@ -0,0 +1,87 @@
1
+ .recipe-shell {
2
+ display: grid;
3
+ height: min(37rem, 70vh);
4
+ grid-template-rows: auto minmax(0, 1fr);
5
+ }
6
+
7
+ .recipe-shell__body {
8
+ display: flex;
9
+ min-width: 0;
10
+ min-height: 0;
11
+ }
12
+
13
+ .recipe-shell__nav,
14
+ .recipe-shell__inspector {
15
+ display: grid;
16
+ min-height: 0;
17
+ background: var(--kui-color-surface);
18
+ }
19
+
20
+ .recipe-shell__nav-list,
21
+ .recipe-shell__inspector-body,
22
+ .recipe-shell__main-body {
23
+ min-height: 0;
24
+ }
25
+
26
+ .recipe-shell__main {
27
+ display: grid;
28
+ min-width: 16rem;
29
+ min-height: 0;
30
+ flex: 1;
31
+ background: var(--kui-color-surface-lowered);
32
+ }
33
+
34
+ .recipe-shell__cards {
35
+ display: grid;
36
+ grid-template-columns: repeat(2, minmax(0, 1fr));
37
+ gap: var(--kui-layout-content-gap);
38
+ }
39
+
40
+ .recipe-shell__card {
41
+ --kui-content-item-border: var(--kui-color-border-quiet);
42
+ --kui-content-item-background: var(--kui-color-surface);
43
+ }
44
+
45
+ @container (max-width: 44rem) {
46
+ .recipe-shell__inspector-region {
47
+ display: none;
48
+ }
49
+
50
+ .recipe-shell[data-responsive-pane="inspector"] .recipe-shell__nav-region,
51
+ .recipe-shell[data-responsive-pane="inspector"] .recipe-shell__main {
52
+ display: none;
53
+ }
54
+
55
+ .recipe-shell[data-responsive-pane="inspector"]
56
+ .recipe-shell__inspector-region {
57
+ display: flex;
58
+ min-width: 0;
59
+ flex: 1;
60
+ }
61
+
62
+ .recipe-shell__cards {
63
+ grid-template-columns: 1fr 1fr;
64
+ }
65
+ }
66
+
67
+ @container (max-width: 28rem) {
68
+ .recipe-shell__nav-region {
69
+ display: none;
70
+ }
71
+
72
+ .recipe-shell[data-responsive-pane="navigation"] .recipe-shell__main,
73
+ .recipe-shell[data-responsive-pane="navigation"]
74
+ .recipe-shell__inspector-region {
75
+ display: none;
76
+ }
77
+
78
+ .recipe-shell[data-responsive-pane="navigation"] .recipe-shell__nav-region {
79
+ display: flex;
80
+ min-width: 0;
81
+ flex: 1;
82
+ }
83
+
84
+ .recipe-shell__cards {
85
+ grid-template-columns: 1fr;
86
+ }
87
+ }