@kerfjs/ui 5.0.0-beta.17 → 5.0.0-beta.18

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 (213) hide show
  1. package/README.md +108 -79
  2. package/ai/component-catalog-extension.schema.json +79 -0
  3. package/ai/component-catalog.json +499 -0
  4. package/ai/component-catalog.schema.json +15 -0
  5. package/ai/public-api-signatures-v1.md +67 -31
  6. package/ai/skill.md +58 -49
  7. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  8. package/dist/app-tab.d.ts +1 -1
  9. package/dist/app-tab.js +1 -1
  10. package/dist/catalog.d.ts +10 -3
  11. package/dist/catalog.js +279 -90
  12. package/dist/catalog.js.map +1 -1
  13. package/dist/chunk-43FUQH35.js +83 -0
  14. package/dist/chunk-43FUQH35.js.map +1 -0
  15. package/dist/{chunk-YGN7OMLT.js → chunk-4TRDV6RL.js} +2 -2
  16. package/dist/chunk-4TRDV6RL.js.map +1 -0
  17. package/dist/chunk-6ETZKKAE.js +38 -0
  18. package/dist/chunk-6ETZKKAE.js.map +1 -0
  19. package/dist/chunk-6WTETUZ6.js +21 -0
  20. package/dist/chunk-6WTETUZ6.js.map +1 -0
  21. package/dist/chunk-7L6EHUMV.js +31 -0
  22. package/dist/chunk-7L6EHUMV.js.map +1 -0
  23. package/dist/chunk-BBRXX6PF.js +108 -0
  24. package/dist/chunk-BBRXX6PF.js.map +1 -0
  25. package/dist/chunk-DPDBVKNA.js +113 -0
  26. package/dist/chunk-DPDBVKNA.js.map +1 -0
  27. package/dist/chunk-ELSJX4W7.js +68 -0
  28. package/dist/chunk-ELSJX4W7.js.map +1 -0
  29. package/dist/{chunk-6NLRBSVX.js → chunk-ENVTNLYC.js} +125 -68
  30. package/dist/chunk-ENVTNLYC.js.map +1 -0
  31. package/dist/{chunk-AHQI52QO.js → chunk-FEY65TBV.js} +6 -2
  32. package/dist/chunk-FEY65TBV.js.map +1 -0
  33. package/dist/chunk-JTHUXDN2.js +58 -0
  34. package/dist/chunk-JTHUXDN2.js.map +1 -0
  35. package/dist/chunk-LPOMRC4C.js +25 -0
  36. package/dist/chunk-LPOMRC4C.js.map +1 -0
  37. package/dist/chunk-NENBUYX5.js +79 -0
  38. package/dist/chunk-NENBUYX5.js.map +1 -0
  39. package/dist/{chunk-XXMOARTH.js → chunk-NVWXKU37.js} +57 -16
  40. package/dist/chunk-NVWXKU37.js.map +1 -0
  41. package/dist/chunk-PIDTEQ3U.js +105 -0
  42. package/dist/chunk-PIDTEQ3U.js.map +1 -0
  43. package/dist/chunk-PX3YSS5C.js +32 -0
  44. package/dist/chunk-PX3YSS5C.js.map +1 -0
  45. package/dist/chunk-PZQVSDLH.js +61 -0
  46. package/dist/chunk-PZQVSDLH.js.map +1 -0
  47. package/dist/chunk-QIRXYKCE.js +113 -0
  48. package/dist/chunk-QIRXYKCE.js.map +1 -0
  49. package/dist/chunk-QO55FN2Y.js +20 -0
  50. package/dist/chunk-QO55FN2Y.js.map +1 -0
  51. package/dist/chunk-QQFJMRIB.js +45 -0
  52. package/dist/chunk-QQFJMRIB.js.map +1 -0
  53. package/dist/chunk-S3HKPI7I.js +218 -0
  54. package/dist/chunk-S3HKPI7I.js.map +1 -0
  55. package/dist/{chunk-WXMGNX2Y.js → chunk-S3PCZ7NV.js} +2 -2
  56. package/dist/chunk-S3PCZ7NV.js.map +1 -0
  57. package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
  58. package/dist/chunk-SRSJO5QE.js.map +1 -0
  59. package/dist/chunk-U5IFGQCD.js +117 -0
  60. package/dist/chunk-U5IFGQCD.js.map +1 -0
  61. package/dist/{chunk-56RJXPHG.js → chunk-Y6LYKFXY.js} +1 -1
  62. package/dist/{chunk-56RJXPHG.js.map → chunk-Y6LYKFXY.js.map} +1 -1
  63. package/dist/chunk-ZK2G3VR5.js +35 -0
  64. package/dist/chunk-ZK2G3VR5.js.map +1 -0
  65. package/dist/chunk-ZVI5YST2.js +41 -0
  66. package/dist/chunk-ZVI5YST2.js.map +1 -0
  67. package/dist/collapsible-panel.d.ts +2 -2
  68. package/dist/collapsible-panel.js +47 -6
  69. package/dist/collapsible-panel.js.map +1 -1
  70. package/dist/device-class.js +33 -5
  71. package/dist/device-class.js.map +1 -1
  72. package/dist/disclosure-arrow.js +1 -1
  73. package/dist/empty-state.d.ts +1 -1
  74. package/dist/empty-state.js +1 -1
  75. package/dist/floating-toolbar.d.ts +1 -1
  76. package/dist/floating-toolbar.js +1 -1
  77. package/dist/index.js +25 -25
  78. package/dist/list-action-row.d.ts +1 -1
  79. package/dist/list-action-row.js +1 -1
  80. package/dist/list-header.d.ts +1 -1
  81. package/dist/list-header.js +1 -1
  82. package/dist/list-inset-control.d.ts +1 -1
  83. package/dist/list-inset-control.js +1 -1
  84. package/dist/list-inset-text.d.ts +1 -1
  85. package/dist/list-inset-text.js +1 -1
  86. package/dist/list-item.d.ts +1 -1
  87. package/dist/list-item.js +1 -1
  88. package/dist/loading-spinner.js +1 -1
  89. package/dist/nav-stack.d.ts +1 -1
  90. package/dist/nav-stack.js +1 -1
  91. package/dist/panel-header.d.ts +1 -1
  92. package/dist/panel-header.js +1 -1
  93. package/dist/resizable-region.d.ts +1 -1
  94. package/dist/resizable-region.js +1 -1
  95. package/dist/segmented-control.js +1 -1
  96. package/dist/select.d.ts +1 -1
  97. package/dist/select.js +1 -1
  98. package/dist/skeleton.d.ts +1 -1
  99. package/dist/skeleton.js +1 -1
  100. package/dist/split-view.d.ts +1 -1
  101. package/dist/split-view.js +70 -10
  102. package/dist/split-view.js.map +1 -1
  103. package/dist/state-banner.d.ts +1 -1
  104. package/dist/state-banner.js +1 -1
  105. package/dist/styles/catalog.css +26 -0
  106. package/dist/tab-bar.d.ts +1 -1
  107. package/dist/tab-bar.js +1 -1
  108. package/dist/tab-scaffold.d.ts +1 -1
  109. package/dist/tab-scaffold.js +43 -8
  110. package/dist/tab-scaffold.js.map +1 -1
  111. package/dist/token-search-field.js +1 -1
  112. package/dist/toolbar-control-group.d.ts +1 -1
  113. package/dist/toolbar-control-group.js +1 -1
  114. package/dist/toolbar-text.js +1 -1
  115. package/dist/toolbar.d.ts +1 -1
  116. package/dist/toolbar.js +1 -1
  117. package/dist/value-table.d.ts +2 -2
  118. package/dist/value-table.js +1 -1
  119. package/dist/wire-catalog.d.ts +31 -2
  120. package/dist/wire-catalog.js +203 -8
  121. package/dist/wire-catalog.js.map +1 -1
  122. package/dist/wire-nav-stack.js +22 -7
  123. package/dist/wire-nav-stack.js.map +1 -1
  124. package/dist/wire-resizable-regions.d.ts +1 -1
  125. package/dist/wire-resizable-regions.js +1 -1
  126. package/dist/wire-sidebar.js +52 -41
  127. package/dist/wire-sidebar.js.map +1 -1
  128. package/dist/wire-tab-bars.js +1 -1
  129. package/dist/wire-tab-scaffold.js +9 -4
  130. package/dist/wire-tab-scaffold.js.map +1 -1
  131. package/dist/wire-token-search-fields.d.ts +1 -1
  132. package/dist/wire-token-search-fields.js +1 -1
  133. package/dist/workbench.d.ts +1 -1
  134. package/dist/workbench.js +56 -9
  135. package/dist/workbench.js.map +1 -1
  136. package/docs/app-layouts.md +7 -7
  137. package/docs/catalog.md +108 -24
  138. package/docs/collapsible-panel.md +38 -11
  139. package/docs/component-contract.md +30 -2
  140. package/docs/component-selection.md +71 -63
  141. package/docs/design/templates.md +1 -1
  142. package/docs/design-philosophy.md +5 -5
  143. package/docs/device-class.md +8 -8
  144. package/docs/examples/command-palette-adapter.tsx +67 -17
  145. package/docs/examples/component-catalog-extension.json +45 -0
  146. package/docs/layout.md +19 -19
  147. package/docs/nav-stack.md +15 -6
  148. package/docs/recipes.md +4 -4
  149. package/docs/split-view.md +3 -3
  150. package/docs/tab-scaffold.md +16 -7
  151. package/docs/webawesome-theme.md +8 -8
  152. package/docs/workbench.md +18 -5
  153. package/llms.txt +20 -14
  154. package/package.json +6 -4
  155. package/ux-demo/recipes/app-shell.tsx +206 -10
  156. package/ux-demo/recipes/collapsible-sidebar.tsx +167 -44
  157. package/ux-demo/recipes/compact-toolbar.tsx +114 -12
  158. package/ux-demo/recipes/composer-form.tsx +106 -18
  159. package/ux-demo/recipes/list-detail-dialog.tsx +144 -8
  160. package/ux-demo/recipes/list-workspace-states.tsx +165 -8
  161. package/ux-demo/recipes/loaders.ts +5 -2
  162. package/ux-demo/recipes/loading-inspector.tsx +124 -41
  163. package/ux-demo/recipes/mount-recipe.ts +27 -6
  164. package/ux-demo/recipes/navigation-sidebar.tsx +120 -9
  165. package/ux-demo/recipes/navigation-stack.tsx +61 -19
  166. package/ux-demo/recipes/types.ts +3 -1
  167. package/ux-demo/recipes/workspace-header.tsx +57 -2
  168. package/dist/chunk-2Z5TWHKQ.js +0 -34
  169. package/dist/chunk-2Z5TWHKQ.js.map +0 -1
  170. package/dist/chunk-37T3VNIZ.js +0 -19
  171. package/dist/chunk-37T3VNIZ.js.map +0 -1
  172. package/dist/chunk-55DATV3A.js +0 -10
  173. package/dist/chunk-55DATV3A.js.map +0 -1
  174. package/dist/chunk-5ATCDSQS.js +0 -42
  175. package/dist/chunk-5ATCDSQS.js.map +0 -1
  176. package/dist/chunk-5E2HVJ33.js +0 -10
  177. package/dist/chunk-5E2HVJ33.js.map +0 -1
  178. package/dist/chunk-6NLRBSVX.js.map +0 -1
  179. package/dist/chunk-7BR3DO2A.js.map +0 -1
  180. package/dist/chunk-7JB7VPRI.js +0 -34
  181. package/dist/chunk-7JB7VPRI.js.map +0 -1
  182. package/dist/chunk-7LDCTU7D.js +0 -134
  183. package/dist/chunk-7LDCTU7D.js.map +0 -1
  184. package/dist/chunk-AHQI52QO.js.map +0 -1
  185. package/dist/chunk-AQNKJRFH.js +0 -79
  186. package/dist/chunk-AQNKJRFH.js.map +0 -1
  187. package/dist/chunk-AVZHJN6C.js +0 -14
  188. package/dist/chunk-AVZHJN6C.js.map +0 -1
  189. package/dist/chunk-B24C4A6T.js +0 -22
  190. package/dist/chunk-B24C4A6T.js.map +0 -1
  191. package/dist/chunk-CH4SQ6H5.js +0 -39
  192. package/dist/chunk-CH4SQ6H5.js.map +0 -1
  193. package/dist/chunk-DAFFDO33.js +0 -14
  194. package/dist/chunk-DAFFDO33.js.map +0 -1
  195. package/dist/chunk-I7INRJO4.js +0 -17
  196. package/dist/chunk-I7INRJO4.js.map +0 -1
  197. package/dist/chunk-L4OKFDJY.js +0 -24
  198. package/dist/chunk-L4OKFDJY.js.map +0 -1
  199. package/dist/chunk-M5JRI6MN.js +0 -27
  200. package/dist/chunk-M5JRI6MN.js.map +0 -1
  201. package/dist/chunk-RNPGFQ33.js +0 -10
  202. package/dist/chunk-RNPGFQ33.js.map +0 -1
  203. package/dist/chunk-VPCTCUED.js +0 -10
  204. package/dist/chunk-VPCTCUED.js.map +0 -1
  205. package/dist/chunk-WRTAQ7BZ.js +0 -23
  206. package/dist/chunk-WRTAQ7BZ.js.map +0 -1
  207. package/dist/chunk-WVPYVDXY.js +0 -15
  208. package/dist/chunk-WVPYVDXY.js.map +0 -1
  209. package/dist/chunk-WXMGNX2Y.js.map +0 -1
  210. package/dist/chunk-XXMOARTH.js.map +0 -1
  211. package/dist/chunk-YGN7OMLT.js.map +0 -1
  212. package/dist/chunk-ZQQ6P4SV.js +0 -20
  213. package/dist/chunk-ZQQ6P4SV.js.map +0 -1
@@ -29,18 +29,18 @@ CSS".
29
29
  Use the [complete recipe guide](./recipes.md) when several primitives form one
30
30
  application boundary:
31
31
 
32
- | Task | Stable catalog route |
33
- | --- | --- |
34
- | Desktop application shell | [Catalog](../ux-demo/) · `?component=recipe-app-shell` |
35
- | Navigation sidebar | [Catalog](../ux-demo/) · `?component=recipe-navigation-sidebar` |
36
- | Workspace header | [Catalog](../ux-demo/) · `?component=recipe-workspace-header` |
37
- | List-detail dialog | [Catalog](../ux-demo/) · `?component=recipe-list-detail-dialog` |
38
- | Composer form | [Catalog](../ux-demo/) · `?component=recipe-composer-form` |
39
- | List workspace states | [Catalog](../ux-demo/) · `?component=recipe-list-workspace-states` |
40
- | Compact toolbar choices and actions | [Catalog](../ux-demo/) · `?component=recipe-compact-toolbar` |
41
- | Navigation stack | [Catalog](../ux-demo/) · `?component=recipe-navigation-stack` |
42
- | Loading inspector | [Catalog](../ux-demo/) · `?component=recipe-loading-inspector` |
43
- | Collapsible sidebar | [Catalog](../ux-demo/) · `?component=recipe-collapsible-sidebar` |
32
+ | Task | Stable catalog route |
33
+ | ----------------------------------- | ------------------------------------------------------------------ |
34
+ | Desktop application shell | [Catalog](../ux-demo/) · `?component=recipe-app-shell` |
35
+ | Navigation sidebar | [Catalog](../ux-demo/) · `?component=recipe-navigation-sidebar` |
36
+ | Workspace header | [Catalog](../ux-demo/) · `?component=recipe-workspace-header` |
37
+ | List-detail dialog | [Catalog](../ux-demo/) · `?component=recipe-list-detail-dialog` |
38
+ | Composer form | [Catalog](../ux-demo/) · `?component=recipe-composer-form` |
39
+ | List workspace states | [Catalog](../ux-demo/) · `?component=recipe-list-workspace-states` |
40
+ | Compact toolbar choices and actions | [Catalog](../ux-demo/) · `?component=recipe-compact-toolbar` |
41
+ | Navigation stack | [Catalog](../ux-demo/) · `?component=recipe-navigation-stack` |
42
+ | Loading inspector | [Catalog](../ux-demo/) · `?component=recipe-loading-inspector` |
43
+ | Collapsible sidebar | [Catalog](../ux-demo/) · `?component=recipe-collapsible-sidebar` |
44
44
 
45
45
  Recipes use public production exports and show ownership boundaries; they are
46
46
  copyable reference compositions, not new monolithic components.
@@ -58,33 +58,33 @@ an upstream component or recipe request.
58
58
 
59
59
  ## Problem-to-component matrix
60
60
 
61
- | Interface need | Use when | Do not use when; nearest alternative | Required wiring | Application owns | Import | Recipe |
62
- | --- | --- | --- | --- | --- | --- | --- |
63
- | 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) |
64
- | 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) |
65
- | Application toolbar — `Toolbar` | Leading identity, optional centered content, and trailing controls form one horizontal app bar. | Do not use it for a page, panel, or dialog heading; use `PanelHeader`. | Compose `ToolbarText` and `ToolbarControlGroup` where their contracts fit. | Actions, command availability, responsive relocation, and state. | `@kerfjs/ui/toolbar` | [Toolbar composition](../README.md#component-subpaths) |
66
- | Toolbar control cluster — `ToolbarControlGroup` | Related toolbar controls need contained, borderless, pressed, or single-control treatment. | Do not use it merely to align unrelated buttons; use toolbar slots or ordinary layout. Web Awesome `wa-button-group` is only for an exceptional grouped-action contract. | Delegate child actions; use `SegmentedControl` for an exclusive choice. | Actions, pressed/expanded state, and policy. | `@kerfjs/ui/toolbar-control-group` | [Component ownership](./component-contract.md#ownership-boundaries) |
67
- | 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) |
68
- | 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` (or prefer `PanelHeader`, which forwards it). | 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) |
69
- | 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. | Routing, selection, permissions, copy, action handling, and domain `data-*` values. | `@kerfjs/ui/list-item` | [Pane geometry](../README.md#pane-and-content-geometry) |
70
- | 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) |
71
- | 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 `PanelHeader`. | 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) |
72
- | Inset self-bordered control — `ListInsetControl` | A control that owns its own border and padding but no outer margin (a search input, a `SegmentedControl`) must line up inside a `.kui-content` list with the standard 8px inline margins and stretch to fill the row. | Do not wrap a `.kui-content-item` or a `ListItem`/`ListHeader` that already owns its inline margin — that double-insets it. Do not add ad-hoc `margin`/`padding` around a bare control to align it; use this instead. | Place the self-bordered control(s) as children; they stretch to fill. It applies only the inline margin, flex stretch, and 8px gap — the child owns its own border and padding. | The control(s), their state, and action wiring. | `@kerfjs/ui/list-inset-control` | [Pane geometry](../README.md#component-subpaths) |
73
- | Inset bare text — `ListInsetText` | A plain string or inline content with no margin, border, or padding of its own must sit in a `.kui-content` list with its text edge aligned to the bordered items around it. | Do not use it to wrap a component that already owns content-item geometry (`ListItem`, `StateBanner`, a `.kui-content-item`) — that double-insets it. Do not hand-roll the 8px margin / 1px border / 8px padding. | Pass the text or inline `SafeHtml` as children; it supplies the 8px inline margin, 1px transparent border, and 8px padding so the text edge lands at the standard 17px inset. Pass `horizontalOnly` to keep that horizontal inset but drop the vertical margin, border, and padding for tight text layout. | The text, copy, and localization. | `@kerfjs/ui/list-inset-text` | [Pane geometry](../README.md#component-subpaths) |
74
- | Application layout composition | A sidebar, main area, inspector, or dialog needs shared toolbar/content/footer and child geometry. | Do not pad the pane shell, wrap child-owned geometry in competing insets, invent unrelated centered measures, or leave an icon-only rail for a hidden pane. | Use `.kui-pane` and one `.kui-pane__content`; add `.kui-content` and `.kui-content-item` as needed. A visible pane owns collapse in its toolbar; move a hidden inline-start pane's restore control to the main toolbar leading edge and an inline-end pane's restore control to its trailing edge. | Layout hierarchy, reading width, scroll ownership, responsive relocation, and pane visibility state. | `@kerfjs/ui/layout.css` | [Pane anatomy](./layout.md#anatomy) |
75
- | 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) |
76
- | 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) |
77
- | One application tab — `AppTab` | A controlled app tab needs selection, close, drag, leading/trailing anatomy, 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`. | Tab identity, order, selection, close policy, content, and domain metadata values. | `@kerfjs/ui/app-tab` | [Tabs contract](./accessibility.md#tabs) |
78
- | Application tab strip — `TabBar`, `wireTabBars`, `reorderTabs` | Tabs switch page regions and may overflow, close, or reorder. | 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. | Ordered tabs, selection, panels, routing, closing, and persistence. | `@kerfjs/ui/tab-bar` plus `@kerfjs/ui/wire-tab-bars` | [Tabs contract](./accessibility.md#tabs) |
79
- | Panel, dialog, or page heading — `PanelHeader` | A panel, dialog, or page needs a heading with an extra-large title, an optional icon and subtitle, and trailing actions. | Do not use it as persistent app chrome; use `Toolbar`. It supplies header structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. | Connect the title id and any provided summary id to the dialog or panel host, pass the trailing controls (typically a `ToolbarControlGroup`), and delegate their actions. **For a page or view title set `headingLevel` (usually `1`)** so the title is a real heading landmark (`role="heading"` + `aria-level`) for screen-reader heading navigation; omit it for a dialog title, which is referenced via `aria-labelledby` to `titleId` and needs no heading. | Open state, focus lifecycle, dismissal, the trailing controls, labels, copy, and the heading level for page use. | `@kerfjs/ui/panel-header` | [Header ownership](./component-contract.md#extracted-versus-application-specific) |
80
- | 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) |
81
- | Indeterminate activity — `LoadingSpinner` | A Kerf surface needs compact, labeled or decorative indeterminate progress. | Do not use it for known progress; use Web Awesome `wa-progress-bar` or `wa-progress-ring`. Direct Web Awesome UI may use `wa-spinner`; do not mix spinner systems within one surface. | None; pass a label when the spinner conveys status. | Loading lifecycle and adjacent status copy. | `@kerfjs/ui/loading-spinner` | [Accessibility](./accessibility.md#shared-rules) |
82
- | Loading placeholder — `Skeleton` + a component's `placeholder` prop | A value or a whole component is still loading and should hold its space as a subtle, unanimated block, keeping the layout stable — an inspector or detail view rendering its real chrome with per-record values absent. | Do not use it for known progress (use `LoadingSpinner`), do not animate it, and do not hand-rebuild a component's empty state — set `placeholder` on the component instead. Prefer it over `wa-skeleton`, which the pure-Kerf primitives avoid to stay Web-Awesome-free. | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `PanelHeader`, `SegmentedControl`, `StateBanner`, `AppTab`, `ToolbarText`, `ListActionRow`) to render skeletons in its value slots with interactivity disabled; use the standalone `Skeleton` for a custom slot. | Loading lifecycle, which slots are unknown, and announcing the loading region. | `@kerfjs/ui/skeleton` | [Accessibility](./accessibility.md#shared-rules) |
83
- | Value selection — `Select` | A controlled form value comes from a moderate or long choice list, possibly grouped or icon-bearing. | Do not use it for commands; use a real action menu. Do not use it for a small visible choice set; use `SegmentedControl`. Prefer it over direct `wa-select`, `wa-option`, or value-like `wa-dropdown`/`wa-dropdown-item` composition. | Import `@kerfjs/ui/select/register` once; listen for standard input/change events. | Controlled value, validation, choices, and domain mapping. | `@kerfjs/ui/select` | [Web Awesome integration](../README.md#web-awesome-theme) |
84
- | Small exclusive choice — `SegmentedControl` | A few visible choices switch a compact view or setting, with toolbar, rounded, or pill presentation. | Do not use it for tabpanel semantics; use `TabBar`. Do not use it for many choices; use `Select`. Prefer it over `wa-button-group` when the controls select one value. | Delegate its action, read `data-segment-value`, update `value`, and rerender. | Controlled value, labels, action, and persistence. | `@kerfjs/ui/segmented-control` | [SegmentedControl contract](./accessibility.md#segmentedcontrol) |
85
- | Structured search editor — `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`, `wireTokenSearchFields` | Free text and ordered, editable, removable filter tokens share one searchbox; enable `collapsible` when an empty, unfocused field should reduce to one iconic action, standalone or in a toolbar group. | Do not use it for ordinary text entry; use a native input or Web Awesome `wa-input`. Do not use it when filters belong in separate form controls. | Read DOM-owned text on input, empty `textContent` on clear, and use `placeTokenSearchCaret` after explicit controlled focus changes. Call `wireTokenSearchFields` from `@kerfjs/ui/wire-token-search-fields` once so Enter submits without adding a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. In `collapsible` mode it also manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse); bind the field's `expanded` to the signal on the returned handle (`handle.expanded(id)`) or adopt your own via `collapsible.signals`, and opt out per behavior only when the app must own it. | Parsing, suggestions, tokens, query execution, results, announcements, and — only if overriding the default — the collapsible `expanded` signal. | `@kerfjs/ui/token-search-field` | [TokenSearchField contract](./accessibility.md#tokensearchfield) |
86
- | Persistent inline status — `StateBanner` | A neutral, info, success, warning, or danger message belongs next to the affected work. | Do not use it for a no-content screen; use `EmptyState`. Do not use it for transient confirmation; use a toast. Web Awesome `wa-callout` is the ecosystem alternative for Web Awesome-owned content. | Delegate an optional action; choose alert urgency only for attention-requiring failure. | State mapping, message lifetime, retry/action behavior, and copy. | `@kerfjs/ui/state-banner` | [Feedback accessibility](./accessibility.md#shared-rules) |
87
- | Empty or busy content area — `EmptyState` | A content region has no items, cannot proceed, or is loading and needs explanation plus an optional action. | Do not use it for an inline status update; use `StateBanner`. Do not use it for transient success; use `wa-toast`/`wa-toast-item` or the application's toast system. | Delegate its optional action; it composes `LoadingSpinner` when busy. | Empty/busy policy, recovery action, illustration, and copy. | `@kerfjs/ui/empty-state` | [Feedback ownership](./component-contract.md#extracted-versus-application-specific) |
61
+ | Interface need | Use when | Do not use when; nearest alternative | Required wiring | Application owns | Import | Recipe |
62
+ | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------- |
63
+ | 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) |
64
+ | 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) |
65
+ | Application toolbar — `Toolbar` | Leading identity, optional centered content, and trailing controls form one horizontal app bar. | Do not use it for a page, panel, or dialog heading; use `PanelHeader`. | Compose `ToolbarText` and `ToolbarControlGroup` where their contracts fit. | Actions, command availability, responsive relocation, and state. | `@kerfjs/ui/toolbar` | [Toolbar composition](../README.md#component-subpaths) |
66
+ | Toolbar control cluster — `ToolbarControlGroup` | Related toolbar controls need contained, borderless, pressed, or single-control treatment. | Do not use it merely to align unrelated buttons; use toolbar slots or ordinary layout. Web Awesome `wa-button-group` is only for an exceptional grouped-action contract. | Delegate child actions; use `SegmentedControl` for an exclusive choice. | Actions, pressed/expanded state, and policy. | `@kerfjs/ui/toolbar-control-group` | [Component ownership](./component-contract.md#ownership-boundaries) |
67
+ | 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) |
68
+ | 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` (or prefer `PanelHeader`, which forwards it). | 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) |
69
+ | 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. | Routing, selection, permissions, copy, action handling, and domain `data-*` values. | `@kerfjs/ui/list-item` | [Pane geometry](../README.md#pane-and-content-geometry) |
70
+ | 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) |
71
+ | 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 `PanelHeader`. | 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) |
72
+ | Inset self-bordered control — `ListInsetControl` | A control that owns its own border and padding but no outer margin (a search input, a `SegmentedControl`) must line up inside a `.kui-content` list with the standard 8px inline margins and stretch to fill the row. | Do not wrap a `.kui-content-item` or a `ListItem`/`ListHeader` that already owns its inline margin — that double-insets it. Do not add ad-hoc `margin`/`padding` around a bare control to align it; use this instead. | Place the self-bordered control(s) as children; they stretch to fill. It applies only the inline margin, flex stretch, and 8px gap — the child owns its own border and padding. | The control(s), their state, and action wiring. | `@kerfjs/ui/list-inset-control` | [Pane geometry](../README.md#component-subpaths) |
73
+ | Inset bare text — `ListInsetText` | A plain string or inline content with no margin, border, or padding of its own must sit in a `.kui-content` list with its text edge aligned to the bordered items around it. | Do not use it to wrap a component that already owns content-item geometry (`ListItem`, `StateBanner`, a `.kui-content-item`) — that double-insets it. Do not hand-roll the 8px margin / 1px border / 8px padding. | Pass the text or inline `SafeHtml` as children; it supplies the 8px inline margin, 1px transparent border, and 8px padding so the text edge lands at the standard 17px inset. Pass `horizontalOnly` to keep that horizontal inset but drop the vertical margin, border, and padding for tight text layout. | The text, copy, and localization. | `@kerfjs/ui/list-inset-text` | [Pane geometry](../README.md#component-subpaths) |
74
+ | Application layout composition | A sidebar, main area, inspector, or dialog needs shared toolbar/content/footer and child geometry. | Do not pad the pane shell, wrap child-owned geometry in competing insets, invent unrelated centered measures, or leave an icon-only rail for a hidden pane. | Use `.kui-pane` and one `.kui-pane__content`; add `.kui-content` and `.kui-content-item` as needed. A visible pane owns collapse in its toolbar; move a hidden inline-start pane's restore control to the main toolbar leading edge and an inline-end pane's restore control to its trailing edge. | Layout hierarchy, reading width, scroll ownership, responsive relocation, and pane visibility state. | `@kerfjs/ui/layout.css` | [Pane anatomy](./layout.md#anatomy) |
75
+ | 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) |
76
+ | 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) |
77
+ | One application tab — `AppTab` | A controlled app tab needs selection, close, drag, leading/trailing anatomy, 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`. | Tab identity, order, selection, close policy, content, and domain metadata values. | `@kerfjs/ui/app-tab` | [Tabs contract](./accessibility.md#tabs) |
78
+ | Application tab strip — `TabBar`, `wireTabBars`, `reorderTabs` | Tabs switch page regions and may overflow, close, or reorder. | 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. | Ordered tabs, selection, panels, routing, closing, and persistence. | `@kerfjs/ui/tab-bar` plus `@kerfjs/ui/wire-tab-bars` | [Tabs contract](./accessibility.md#tabs) |
79
+ | Panel, dialog, or page heading — `PanelHeader` | A panel, dialog, or page needs a heading with an extra-large title, an optional icon and subtitle, and trailing actions. | Do not use it as persistent app chrome; use `Toolbar`. It supplies header structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. | Connect the title id and any provided summary id to the dialog or panel host, pass the trailing controls (typically a `ToolbarControlGroup`), and delegate their actions. **For a page or view title set `headingLevel` (usually `1`)** so the title is a real heading landmark (`role="heading"` + `aria-level`) for screen-reader heading navigation; omit it for a dialog title, which is referenced via `aria-labelledby` to `titleId` and needs no heading. | Open state, focus lifecycle, dismissal, the trailing controls, labels, copy, and the heading level for page use. | `@kerfjs/ui/panel-header` | [Header ownership](./component-contract.md#extracted-versus-application-specific) |
80
+ | 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) |
81
+ | Indeterminate activity — `LoadingSpinner` | A Kerf surface needs compact, labeled or decorative indeterminate progress. | Do not use it for known progress; use Web Awesome `wa-progress-bar` or `wa-progress-ring`. Direct Web Awesome UI may use `wa-spinner`; do not mix spinner systems within one surface. | None; pass a label when the spinner conveys status. | Loading lifecycle and adjacent status copy. | `@kerfjs/ui/loading-spinner` | [Accessibility](./accessibility.md#shared-rules) |
82
+ | Loading placeholder — `Skeleton` + a component's `placeholder` prop | A value or a whole component is still loading and should hold its space as a subtle, unanimated block, keeping the layout stable — an inspector or detail view rendering its real chrome with per-record values absent. | Do not use it for known progress (use `LoadingSpinner`), do not animate it, and do not hand-rebuild a component's empty state — set `placeholder` on the component instead. Prefer it over `wa-skeleton`, which the pure-Kerf primitives avoid to stay Web-Awesome-free. | Set `placeholder` on a value-bearing component (`Select`, `ListHeader`, `ListItem`, `ValueTableRow`, `PanelHeader`, `SegmentedControl`, `StateBanner`, `AppTab`, `ToolbarText`, `ListActionRow`) to render skeletons in its value slots with interactivity disabled; use the standalone `Skeleton` for a custom slot. | Loading lifecycle, which slots are unknown, and announcing the loading region. | `@kerfjs/ui/skeleton` | [Accessibility](./accessibility.md#shared-rules) |
83
+ | Value selection — `Select` | A controlled form value comes from a moderate or long choice list, possibly grouped or icon-bearing. | Do not use it for commands; use a real action menu. Do not use it for a small visible choice set; use `SegmentedControl`. Prefer it over direct `wa-select`, `wa-option`, or value-like `wa-dropdown`/`wa-dropdown-item` composition. | Import `@kerfjs/ui/select/register` once; listen for standard input/change events. | Controlled value, validation, choices, and domain mapping. | `@kerfjs/ui/select` | [Web Awesome integration](../README.md#web-awesome-theme) |
84
+ | Small exclusive choice — `SegmentedControl` | A few visible choices switch a compact view or setting, with toolbar, rounded, or pill presentation. | Do not use it for tabpanel semantics; use `TabBar`. Do not use it for many choices; use `Select`. Prefer it over `wa-button-group` when the controls select one value. | Delegate its action, read `data-segment-value`, update `value`, and rerender. | Controlled value, labels, action, and persistence. | `@kerfjs/ui/segmented-control` | [SegmentedControl contract](./accessibility.md#segmentedcontrol) |
85
+ | Structured search editor — `TokenSearchField`, `readTokenSearchField`, `placeTokenSearchCaret`, `wireTokenSearchFields` | Free text and ordered, editable, removable filter tokens share one searchbox; enable `collapsible` when an empty, unfocused field should reduce to one iconic action, standalone or in a toolbar group. | Do not use it for ordinary text entry; use a native input or Web Awesome `wa-input`. Do not use it when filters belong in separate form controls. | Read DOM-owned text on input, empty `textContent` on clear, and use `placeTokenSearchCaret` after explicit controlled focus changes. Call `wireTokenSearchFields` from `@kerfjs/ui/wire-token-search-fields` once so Enter submits without adding a line break and keyboard chip deletion restores focus plus the text-relative caret after controlled replacement. In `collapsible` mode it also manages the transient expand/collapse/focus by default (activate to reveal + focus, Escape or empty blur to collapse); bind the field's `expanded` to the signal on the returned handle (`handle.expanded(id)`) or adopt your own via `collapsible.signals`, and opt out per behavior only when the app must own it. | Parsing, suggestions, tokens, query execution, results, announcements, and — only if overriding the default — the collapsible `expanded` signal. | `@kerfjs/ui/token-search-field` | [TokenSearchField contract](./accessibility.md#tokensearchfield) |
86
+ | Persistent inline status — `StateBanner` | A neutral, info, success, warning, or danger message belongs next to the affected work. | Do not use it for a no-content screen; use `EmptyState`. Do not use it for transient confirmation; use a toast. Web Awesome `wa-callout` is the ecosystem alternative for Web Awesome-owned content. | Delegate an optional action; choose alert urgency only for attention-requiring failure. | State mapping, message lifetime, retry/action behavior, and copy. | `@kerfjs/ui/state-banner` | [Feedback accessibility](./accessibility.md#shared-rules) |
87
+ | Empty or busy content area — `EmptyState` | A content region has no items, cannot proceed, or is loading and needs explanation plus an optional action. | Do not use it for an inline status update; use `StateBanner`. Do not use it for transient success; use `wa-toast`/`wa-toast-item` or the application's toast system. | Delegate its optional action; it composes `LoadingSpinner` when busy. | Empty/busy policy, recovery action, illustration, and copy. | `@kerfjs/ui/empty-state` | [Feedback ownership](./component-contract.md#extracted-versus-application-specific) |
88
88
 
89
89
  ## Ambiguous choices
90
90
 
@@ -108,13 +108,13 @@ one wrapper that composes these for you as a panel/dialog/page heading.
108
108
 
109
109
  Common toolbar patterns:
110
110
 
111
- | Want | Put in the zone | Notes |
112
- | --- | --- | --- |
113
- | Identity or title text | `<ToolbarText text="…" size="large" />` (or `xlarge` for a page/panel title) | Wrap in a `single` borderless group only when it must align with adjacent control pills |
114
- | One or more icon/text buttons | `<ToolbarControlGroup>{buttons}</ToolbarControlGroup>` | Use `buttonAppearance="push"` for toggle buttons with `aria-pressed`; `single` for a lone control |
115
- | An exclusive view switch | `<ToolbarControlGroup><SegmentedControl … /></ToolbarControlGroup>` | Not `TabBar`, which switches tabpanels |
116
- | A value list | `<ToolbarControlGroup><Select … /></ToolbarControlGroup>` | Register `@kerfjs/ui/select/register` once |
117
- | A collapsible search box | `<ToolbarControlGroup single><TokenSearchField collapsible … /></ToolbarControlGroup>` | The group animates the iconic ↔ expanded states; `wireTokenSearchFields` manages expand/collapse/focus by default |
111
+ | Want | Put in the zone | Notes |
112
+ | ----------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
113
+ | Identity or title text | `<ToolbarText text="…" size="large" />` (or `xlarge` for a page/panel title) | Wrap in a `single` borderless group only when it must align with adjacent control pills |
114
+ | One or more icon/text buttons | `<ToolbarControlGroup>{buttons}</ToolbarControlGroup>` | Use `buttonAppearance="push"` for toggle buttons with `aria-pressed`; `single` for a lone control |
115
+ | An exclusive view switch | `<ToolbarControlGroup><SegmentedControl … /></ToolbarControlGroup>` | Not `TabBar`, which switches tabpanels |
116
+ | A value list | `<ToolbarControlGroup><Select … /></ToolbarControlGroup>` | Register `@kerfjs/ui/select/register` once |
117
+ | A collapsible search box | `<ToolbarControlGroup single><TokenSearchField collapsible … /></ToolbarControlGroup>` | The group animates the iconic ↔ expanded states; `wireTokenSearchFields` manages expand/collapse/focus by default |
118
118
 
119
119
  A **popup menu in a toolbar** is a `single` `ToolbarControlGroup` wrapping a Web
120
120
  Awesome `wa-dropdown`: its `slot="trigger"` `wa-button` is the toolbar button and
@@ -128,7 +128,9 @@ own DOM.
128
128
  <wa-button slot="trigger" appearance="plain" with-caret aria-label="Sort">
129
129
  <LucideIcon icon={ArrowDownAZ} name="arrow-down-a-z" />
130
130
  </wa-button>
131
- <wa-dropdown-item data-action="sort-recent">Recently updated</wa-dropdown-item>
131
+ <wa-dropdown-item data-action="sort-recent">
132
+ Recently updated
133
+ </wa-dropdown-item>
132
134
  <wa-dropdown-item data-action="sort-priority">Priority</wa-dropdown-item>
133
135
  </wa-dropdown>
134
136
  </ToolbarControlGroup>
@@ -143,9 +145,15 @@ geometry and 44px targets.
143
145
  <aside class="kui-pane">
144
146
  <nav class="kui-pane__content kui-content">
145
147
  <section>
146
- <ListHeader label="Workspace" />
147
- <ListItem action="open" label="Inbox" icon={inboxIcon} />
148
- <ListActionRow action="open-file" label="main.ts" trailingAction="file-actions" trailingActionLabel="Actions for main.ts" trailingActionIcon={moreIcon} />
148
+ <ListHeader label="Workspace" />
149
+ <ListItem action="open" label="Inbox" icon={inboxIcon} />
150
+ <ListActionRow
151
+ action="open-file"
152
+ label="main.ts"
153
+ trailingAction="file-actions"
154
+ trailingActionLabel="Actions for main.ts"
155
+ trailingActionIcon={moreIcon}
156
+ />
149
157
  </section>
150
158
  <div class="kui-content-item">Workspace details</div>
151
159
  </nav>
@@ -172,20 +180,20 @@ marks the superseded or exceptional choices below with a visible `Discouraged`
172
180
  tag; Popup remains untagged because low-level anchored positioning can be the
173
181
  right primitive.
174
182
 
175
- | Web Awesome choice | Kerf decision |
176
- | --- | --- |
177
- | `wa-button`, `wa-dropdown`, `wa-dropdown-item` | Use buttons and command menus for actions. Use `ListItem` for a navigation row and `Select` when the user chooses a value. |
178
- | `wa-button-group` | Use only for exceptional grouped actions; use `SegmentedControl` for one-of-many selection. |
179
- | `wa-input`, `wa-tag` | Use for ordinary text and tags; use `TokenSearchField` only when text and ordered filter tokens form one editor. |
180
- | `wa-select`, `wa-option` | Use `Select`, which owns Kerf spacing, controlled rendering, icon stability, and explicit registration. |
181
- | `wa-tab-group`, `wa-tab`, `wa-tab-panel` | Use `TabBar`/`AppTab` for application tabs or `SegmentedControl` for compact local views. |
182
- | `wa-icon` | Use `LucideIcon` in application UI. |
183
- | `wa-split-panel` | Use `ResizableRegion` for Kerf application panes; retain Split Panel only when its distinct API is required. |
183
+ | Web Awesome choice | Kerf decision |
184
+ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
185
+ | `wa-button`, `wa-dropdown`, `wa-dropdown-item` | Use buttons and command menus for actions. Use `ListItem` for a navigation row and `Select` when the user chooses a value. |
186
+ | `wa-button-group` | Use only for exceptional grouped actions; use `SegmentedControl` for one-of-many selection. |
187
+ | `wa-input`, `wa-tag` | Use for ordinary text and tags; use `TokenSearchField` only when text and ordered filter tokens form one editor. |
188
+ | `wa-select`, `wa-option` | Use `Select`, which owns Kerf spacing, controlled rendering, icon stability, and explicit registration. |
189
+ | `wa-tab-group`, `wa-tab`, `wa-tab-panel` | Use `TabBar`/`AppTab` for application tabs or `SegmentedControl` for compact local views. |
190
+ | `wa-icon` | Use `LucideIcon` in application UI. |
191
+ | `wa-split-panel` | Use `ResizableRegion` for Kerf application panes; retain Split Panel only when its distinct API is required. |
184
192
  | `wa-spinner`, `wa-progress-bar`, `wa-progress-ring`, `wa-skeleton` | Use `LoadingSpinner` for compact Kerf indeterminate activity; choose the ecosystem component when its distinct progress or placeholder semantics fit. |
185
- | `wa-callout`, `wa-toast`, `wa-toast-item` | Use `StateBanner` for persistent inline app status, `EmptyState` for absent content, and toasts only for transient feedback. |
186
- | `wa-popup`, `wa-tooltip`, `wa-popover` | Prefer the high-level interaction whose semantics fit. Use Popup only when its low-level anchored positioning removes custom placement code. |
187
- | `wa-tree`, `wa-tree-item`, `wa-animated-image`, `wa-comparison` | Use only for the specialized behavior named by the component. |
188
- | `wa-zoomable-frame` | Avoid for application UI; keep embedded-media behavior application-owned. |
193
+ | `wa-callout`, `wa-toast`, `wa-toast-item` | Use `StateBanner` for persistent inline app status, `EmptyState` for absent content, and toasts only for transient feedback. |
194
+ | `wa-popup`, `wa-tooltip`, `wa-popover` | Prefer the high-level interaction whose semantics fit. Use Popup only when its low-level anchored positioning removes custom placement code. |
195
+ | `wa-tree`, `wa-tree-item`, `wa-animated-image`, `wa-comparison` | Use only for the specialized behavior named by the component. |
196
+ | `wa-zoomable-frame` | Avoid for application UI; keep embedded-media behavior application-owned. |
189
197
 
190
198
  All other entries in the [Web Awesome theme contract](./webawesome-theme.md#coverage)
191
199
  remain supported when their native semantic contract matches the product need.
@@ -104,7 +104,7 @@ starting point and:
104
104
  tokens and `layout.css`), and any of your own component CSS.
105
105
  3. Enumerate the presentation combinations in the per-component manifest.
106
106
  4. Capture each variant with `domotion capture <page.html> --selector <css>
107
- --text-mode system-font -o <variant>.svg`, once per theme with
107
+ --text-mode system-font -o <variant>.svg`, once per theme with
108
108
  `--color-scheme light` / `--color-scheme dark` (if your components theme with
109
109
  `light-dark()`), then write a light and a dark library file that each embed an
110
110
  inline copy of every variant (namespacing each copy's ids/font-family names so
@@ -18,7 +18,7 @@ A reference image defines hierarchy, relationships, and intended feel. It does n
18
18
 
19
19
  ### Continuity is correctness
20
20
 
21
- Unrelated updates must not close controls, move focus, reset a draft, replace a selection, or jump scroll. Give every durable and transient state an explicit owner. The default owner is the application: domain data and persisted layout preferences — a navigation stack, a selection, a tab order, a pane size, a `collapsed`/`expanded` disclosure flag — are the app's signals, which it reads to render. A `wire…` helper owns only the *ephemeral mechanics* around that state (a push/pop animation, overflow autoscroll, a live resize preview) and reports committed changes through callbacks. A helper takes over a piece of transient state itself only when hand-rolling that behavior is substantial and error-prone enough to cause real, inconsistent variation across apps — `wireTokenSearchFields`'s collapsible reveal/focus/Escape/blur-collapse is the bar; a one-line boolean toggle is not. Preserve DOM identity when meaning has not changed. Distinguish blocking foreground work from background synchronization.
21
+ Unrelated updates must not close controls, move focus, reset a draft, replace a selection, or jump scroll. Give every durable and transient state an explicit owner. The default owner is the application: domain data and persisted layout preferences — a navigation stack, a selection, a tab order, a pane size, a `collapsed`/`expanded` disclosure flag — are the app's signals, which it reads to render. A `wire…` helper owns only the _ephemeral mechanics_ around that state (a push/pop animation, overflow autoscroll, a live resize preview) and reports committed changes through callbacks. A helper takes over a piece of transient state itself only when hand-rolling that behavior is substantial and error-prone enough to cause real, inconsistent variation across apps — `wireTokenSearchFields`'s collapsible reveal/focus/Escape/blur-collapse is the bar; a one-line boolean toggle is not. Preserve DOM identity when meaning has not changed. Distinguish blocking foreground work from background synchronization.
22
22
 
23
23
  ### Hierarchy precedes decoration
24
24
 
@@ -26,7 +26,7 @@ Establish one reading order and one dominant action per decision point. Group fi
26
26
 
27
27
  ### Reach for the primitive, not for CSS
28
28
 
29
- The package is designed to look right *unstyled*. A screen built from the
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
31
  custom CSS is the exception. Before adding any `padding`, `margin`, `width`,
32
32
  `height`, `border`, `background`, wrapper card, or decoration, check whether the
@@ -53,9 +53,9 @@ Use the package composition so the geometry has one owner:
53
53
  <aside class="kui-pane">
54
54
  <nav class="kui-pane__content kui-content">
55
55
  <section>
56
- <ListHeader label="Workspace" />
57
- <ListItem action="open" label="Inbox" icon={inboxIcon} />
58
- <ListItem action="open" label="Drafts" />
56
+ <ListHeader label="Workspace" />
57
+ <ListItem action="open" label="Inbox" icon={inboxIcon} />
58
+ <ListItem action="open" label="Drafts" />
59
59
  </section>
60
60
  <div class="kui-content-item">Panel contents</div>
61
61
  </nav>
@@ -7,7 +7,7 @@ the foundation the app/dialog layouts build on (see
7
7
  [`../../docs/23-app-layouts.md`](../../docs/23-app-layouts.md)).
8
8
 
9
9
  ```ts
10
- import { deviceClass } from '@kerfjs/ui/device-class';
10
+ import { deviceClass } from "@kerfjs/ui/device-class";
11
11
 
12
12
  const device = deviceClass(); // ReadonlySignal<DeviceClass>
13
13
 
@@ -19,14 +19,14 @@ effect(() => {
19
19
 
20
20
  ## `DeviceClass`
21
21
 
22
- | Field | Meaning |
23
- | --- | --- |
24
- | `size` | `xs-mobile` \| `mobile` \| `tablet` \| `desktop` \| `xl-desktop` |
25
- | `orientation` | `portrait` \| `landscape` |
22
+ | Field | Meaning |
23
+ | ------------------------------- | -------------------------------------------------------------------------- |
24
+ | `size` | `xs-mobile` \| `mobile` \| `tablet` \| `desktop` \| `xl-desktop` |
25
+ | `orientation` | `portrait` \| `landscape` |
26
26
  | `segments` / `verticalSegments` | viewport segment counts (foldables / dual-screen); `1` on ordinary devices |
27
- | `handset` | `xs-mobile` or `mobile` |
28
- | `compact` | "one pane at a time" — a handset or a portrait tablet |
29
- | `atLeast(size)` | true when the current size is `size` or larger |
27
+ | `handset` | `xs-mobile` or `mobile` |
28
+ | `compact` | "one pane at a time" — a handset or a portrait tablet |
29
+ | `atLeast(size)` | true when the current size is `size` or larger |
30
30
 
31
31
  Reading `device.value` inside an `effect`/`computed` re-runs when the viewport
32
32
  crosses a breakpoint, rotates, or changes its segment count. One shared viewport
@@ -26,17 +26,57 @@ export function mountCommandPaletteAdapter(
26
26
  const query = signal('');
27
27
  const render = () => {
28
28
  const needle = query.value.trim().toLocaleLowerCase();
29
- const matches = commands.filter((command) => !needle ||
30
- [command.label, ...(command.keywords ?? [])].some((value) => value.toLocaleLowerCase().includes(needle)));
31
- return <section class="app-command-palette kui-content" aria-label="Command palette">
32
- <div class="kui-content-item"><label for="app-command-query">Search commands</label><input id="app-command-query" type="search" value={query.value} autocomplete="off" data-command-query /></div>
33
- <p class="kui-content-item" aria-live="polite">{matches.length} matching commands</p>
34
- <ul class="kui-content-item" aria-label="Matching commands">{matches.map((command) => <li><button type="button" data-action="run-command" data-command-id={command.id}>{command.label}</button></li>)}</ul>
35
- <footer class="kui-control-cluster kui-content-item" aria-label="Command palette actions">
36
- <button type="button" data-action="clear-query">Clear search</button>
37
- <button type="button" data-action="close-palette">Close</button>
38
- </footer>
39
- </section>;
29
+ const matches = commands.filter(
30
+ (command) =>
31
+ !needle ||
32
+ [command.label, ...(command.keywords ?? [])].some((value) =>
33
+ value.toLocaleLowerCase().includes(needle),
34
+ ),
35
+ );
36
+ return (
37
+ <section
38
+ class="app-command-palette kui-content"
39
+ aria-label="Command palette"
40
+ >
41
+ <div class="kui-content-item">
42
+ <label for="app-command-query">Search commands</label>
43
+ <input
44
+ id="app-command-query"
45
+ type="search"
46
+ value={query.value}
47
+ autocomplete="off"
48
+ data-command-query
49
+ />
50
+ </div>
51
+ <p class="kui-content-item" aria-live="polite">
52
+ {matches.length} matching commands
53
+ </p>
54
+ <ul class="kui-content-item" aria-label="Matching commands">
55
+ {matches.map((command) => (
56
+ <li>
57
+ <button
58
+ type="button"
59
+ data-action="run-command"
60
+ data-command-id={command.id}
61
+ >
62
+ {command.label}
63
+ </button>
64
+ </li>
65
+ ))}
66
+ </ul>
67
+ <footer
68
+ class="kui-control-cluster kui-content-item"
69
+ aria-label="Command palette actions"
70
+ >
71
+ <button type="button" data-action="clear-query">
72
+ Clear search
73
+ </button>
74
+ <button type="button" data-action="close-palette">
75
+ Close
76
+ </button>
77
+ </footer>
78
+ </section>
79
+ );
40
80
  };
41
81
 
42
82
  const stopMount = mount(root, render);
@@ -52,12 +92,22 @@ export function mountCommandPaletteAdapter(
52
92
  },
53
93
  'close-palette': onClose,
54
94
  });
55
- const stopInput = delegate<HTMLInputElement>(root, 'input', '[data-command-query]', (_event, input) => {
56
- query.value = input.value;
57
- });
58
- const stopEscape = delegate(root, 'keydown', '[data-command-query]', (event) => {
59
- if ((event as KeyboardEvent).key === 'Escape') onClose();
60
- });
95
+ const stopInput = delegate<HTMLInputElement>(
96
+ root,
97
+ 'input',
98
+ '[data-command-query]',
99
+ (_event, input) => {
100
+ query.value = input.value;
101
+ },
102
+ );
103
+ const stopEscape = delegate(
104
+ root,
105
+ 'keydown',
106
+ '[data-command-query]',
107
+ (event) => {
108
+ if ((event as KeyboardEvent).key === 'Escape') onClose();
109
+ },
110
+ );
61
111
  let disposed = false;
62
112
  return () => {
63
113
  if (disposed) return;
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "../../ai/component-catalog-extension.schema.json",
3
+ "schemaVersion": 1,
4
+ "package": "@acme/ui",
5
+ "description": "Application-owned components composed with Kerf UI.",
6
+ "entries": [
7
+ {
8
+ "id": "acme-filter-chip",
9
+ "name": "FilterChip",
10
+ "kind": "component",
11
+ "purpose": "A removable application filter displayed inside a token-search workflow.",
12
+ "useWhen": [
13
+ "A saved application filter needs a compact visible label and remove action."
14
+ ],
15
+ "avoidWhen": [
16
+ "The value belongs to TokenSearchField's own controlled token collection."
17
+ ],
18
+ "publicClasses": ["acme-filter-chip"],
19
+ "publicTokens": ["--acme-filter-chip-accent"],
20
+ "geometry": {
21
+ "margin": "none",
22
+ "border": "self",
23
+ "padding": "self"
24
+ },
25
+ "documentation": "docs/filter-chip.md"
26
+ },
27
+ {
28
+ "id": "acme-inspector",
29
+ "name": "Inspector",
30
+ "kind": "composition",
31
+ "purpose": "An application inspector composed from Kerf panel, content, and value primitives.",
32
+ "useWhen": ["A selected record needs a titled fact-and-action region."],
33
+ "avoidWhen": ["A ValueTable alone expresses the complete interaction."],
34
+ "geometry": {
35
+ "margin": "parent",
36
+ "border": "child",
37
+ "padding": "child",
38
+ "notes": [
39
+ "The embedding pane positions the inspector; PanelHeader, ValueTable, and action groups retain their own geometry."
40
+ ]
41
+ },
42
+ "documentation": "docs/inspector.md"
43
+ }
44
+ ]
45
+ }
package/docs/layout.md CHANGED
@@ -46,16 +46,16 @@ semantic status.
46
46
 
47
47
  ## Public roles and tokens
48
48
 
49
- | Need | Class | Token / default |
50
- | --- | --- | --- |
51
- | Unpadded toolbar/content/footer structure | `.kui-pane` | — |
52
- | Scrolling pane content | `.kui-pane__content` | — |
53
- | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
54
- | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
55
- | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
56
- | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
57
- | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
58
- | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
49
+ | Need | Class | Token / default |
50
+ | ----------------------------------------- | ------------------------- | ------------------------------------- |
51
+ | Unpadded toolbar/content/footer structure | `.kui-pane` | — |
52
+ | Scrolling pane content | `.kui-pane__content` | — |
53
+ | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
54
+ | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
55
+ | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
56
+ | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
57
+ | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
58
+ | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
59
59
 
60
60
  The component layer applies the same contract to `Toolbar`, `ListHeader`,
61
61
  `ListItem`, `PanelHeader`, `StateBanner`, `ValueTable`,
@@ -81,16 +81,16 @@ expressing exactly one relationship — pick the step by **how connected two
81
81
  elements are**, not by eye. Every scalable value is `remify`-authored against the
82
82
  fixed 16px baseline, so it delivers as `rem`.
83
83
 
84
- | Value | Token | Relationship — when to use |
85
- | --- | --- | --- |
86
- | 0px | `--kui-space-none` | No separation. The elements read as a single unit (a control and its own affordance, adjacent `ListItem` rows). |
87
- | 4px | `--kui-space-2xs` | Very minor. Still one connected cluster, but readability or aesthetics demand a hair of air (inline metadata, an icon beside its label). |
88
- | 8px | `--kui-space-xs` | Standard. Between elements **within a group** — the content-item padding/gutter, gaps between toolbar controls in a group. |
89
- | 16px | `--kui-space-m` | Minor. Between **homogeneous groups** — two lists, two sibling sections of the same kind. |
90
- | 24px | `--kui-space-l` | Major. Between **heterogeneous groups** — the `.kui-content` rhythm between major, differing regions. |
84
+ | Value | Token | Relationship — when to use |
85
+ | ----- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | 0px | `--kui-space-none` | No separation. The elements read as a single unit (a control and its own affordance, adjacent `ListItem` rows). |
87
+ | 4px | `--kui-space-2xs` | Very minor. Still one connected cluster, but readability or aesthetics demand a hair of air (inline metadata, an icon beside its label). |
88
+ | 8px | `--kui-space-xs` | Standard. Between elements **within a group** — the content-item padding/gutter, gaps between toolbar controls in a group. |
89
+ | 16px | `--kui-space-m` | Minor. Between **homogeneous groups** — two lists, two sibling sections of the same kind. |
90
+ | 24px | `--kui-space-l` | Major. Between **heterogeneous groups** — the `.kui-content` rhythm between major, differing regions. |
91
91
 
92
- The two relationships that get confused most are 8px vs 24px: 8px is *inside* a
93
- group, 24px is *between* major, differing regions. 16px sits between them for
92
+ The two relationships that get confused most are 8px vs 24px: 8px is _inside_ a
93
+ group, 24px is _between_ major, differing regions. 16px sits between them for
94
94
  same-kind groups.
95
95
 
96
96
  `--kui-space-s` (12px) and `--kui-space-xl` (32px) exist but are **off the