@kerfjs/ui 4.4.1 → 5.0.0-beta.10

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 (292) hide show
  1. package/README.md +240 -10
  2. package/ai/component-catalog.json +4766 -0
  3. package/ai/component-catalog.schema.json +79 -0
  4. package/ai/public-api-signatures-v1.md +1327 -0
  5. package/ai/skill.md +73 -11
  6. package/ai/webawesome-jsx-signatures-v1.md +106 -0
  7. package/dist/app-tab.d.ts +14 -2
  8. package/dist/app-tab.js +3 -1
  9. package/dist/browser/app-tab.js +3 -2
  10. package/dist/browser/disclosure-arrow.js +4 -0
  11. package/dist/browser/empty-state.js +3 -3
  12. package/dist/browser/list-action-row.js +4 -0
  13. package/dist/browser/list-header.js +6 -0
  14. package/dist/browser/list-item.js +4 -0
  15. package/dist/browser/loading-spinner.js +2 -2
  16. package/dist/browser/lucide-icon.js +2 -2
  17. package/dist/browser/panel-header.js +7 -0
  18. package/dist/browser/resizable-region.js +2 -2
  19. package/dist/browser/segmented-control.js +3 -2
  20. package/dist/browser/select.js +4 -3
  21. package/dist/browser/skeleton.js +3 -0
  22. package/dist/browser/state-banner.js +3 -2
  23. package/dist/browser/tab-bar.js +2 -2
  24. package/dist/browser/token-search-field.js +4 -0
  25. package/dist/browser/toolbar-control-group.js +2 -2
  26. package/dist/browser/toolbar-text.js +3 -2
  27. package/dist/browser/toolbar.js +2 -2
  28. package/dist/browser/value-table.js +3 -2
  29. package/dist/catalog.d.ts +119 -0
  30. package/dist/catalog.js +143 -0
  31. package/dist/catalog.js.map +1 -0
  32. package/dist/chunk-2Z5TWHKQ.js +34 -0
  33. package/dist/chunk-2Z5TWHKQ.js.map +1 -0
  34. package/dist/chunk-37T3VNIZ.js +19 -0
  35. package/dist/chunk-37T3VNIZ.js.map +1 -0
  36. package/dist/chunk-56RJXPHG.js +43 -0
  37. package/dist/chunk-56RJXPHG.js.map +1 -0
  38. package/dist/chunk-7BR3DO2A.js +31 -0
  39. package/dist/chunk-7BR3DO2A.js.map +1 -0
  40. package/dist/chunk-7JB7VPRI.js +34 -0
  41. package/dist/chunk-7JB7VPRI.js.map +1 -0
  42. package/dist/chunk-7LDCTU7D.js +134 -0
  43. package/dist/chunk-7LDCTU7D.js.map +1 -0
  44. package/dist/{chunk-4HHCYSGT.js → chunk-AQNKJRFH.js} +2 -2
  45. package/dist/{chunk-4HHCYSGT.js.map → chunk-AQNKJRFH.js.map} +1 -1
  46. package/dist/{chunk-MBR3TYNL.js → chunk-AVZHJN6C.js} +3 -3
  47. package/dist/chunk-AVZHJN6C.js.map +1 -0
  48. package/dist/chunk-B24C4A6T.js +22 -0
  49. package/dist/chunk-B24C4A6T.js.map +1 -0
  50. package/dist/chunk-BLK3UEEI.js +23 -0
  51. package/dist/chunk-BLK3UEEI.js.map +1 -0
  52. package/dist/chunk-CVZYNJJ5.js +265 -0
  53. package/dist/chunk-CVZYNJJ5.js.map +1 -0
  54. package/dist/chunk-I7INRJO4.js +17 -0
  55. package/dist/chunk-I7INRJO4.js.map +1 -0
  56. package/dist/chunk-L4OKFDJY.js +24 -0
  57. package/dist/chunk-L4OKFDJY.js.map +1 -0
  58. package/dist/chunk-LC3YSAZC.js +39 -0
  59. package/dist/chunk-LC3YSAZC.js.map +1 -0
  60. package/dist/{chunk-3O7ZEUW2.js → chunk-M5JRI6MN.js} +3 -3
  61. package/dist/chunk-M5JRI6MN.js.map +1 -0
  62. package/dist/chunk-RUNWEPR4.js +10 -0
  63. package/dist/chunk-RUNWEPR4.js.map +1 -0
  64. package/dist/chunk-W5EQ3HYQ.js +42 -0
  65. package/dist/chunk-W5EQ3HYQ.js.map +1 -0
  66. package/dist/{chunk-MQIZAD4A.js → chunk-XXMOARTH.js} +5 -3
  67. package/dist/chunk-XXMOARTH.js.map +1 -0
  68. package/dist/chunk-Y5MRDTLC.js +20 -0
  69. package/dist/chunk-Y5MRDTLC.js.map +1 -0
  70. package/dist/{chunk-TRNBHPNW.js → chunk-YGN7OMLT.js} +11 -8
  71. package/dist/chunk-YGN7OMLT.js.map +1 -0
  72. package/dist/collapsible-panel.d.ts +66 -0
  73. package/dist/collapsible-panel.js +22 -0
  74. package/dist/collapsible-panel.js.map +1 -0
  75. package/dist/device-class.d.ts +62 -0
  76. package/dist/device-class.js +75 -0
  77. package/dist/device-class.js.map +1 -0
  78. package/dist/disclosure-arrow.d.ts +14 -0
  79. package/dist/disclosure-arrow.js +4 -0
  80. package/dist/disclosure-arrow.js.map +1 -0
  81. package/dist/index.d.ts +10 -6
  82. package/dist/index.js +21 -16
  83. package/dist/list-action-row.d.ts +52 -0
  84. package/dist/list-action-row.js +5 -0
  85. package/dist/list-action-row.js.map +1 -0
  86. package/dist/list-header.d.ts +43 -0
  87. package/dist/list-header.js +7 -0
  88. package/dist/list-header.js.map +1 -0
  89. package/dist/list-item.d.ts +33 -0
  90. package/dist/list-item.js +5 -0
  91. package/dist/list-item.js.map +1 -0
  92. package/dist/nav-stack.d.ts +38 -0
  93. package/dist/nav-stack.js +5 -0
  94. package/dist/nav-stack.js.map +1 -0
  95. package/dist/panel-header.d.ts +36 -0
  96. package/dist/panel-header.js +7 -0
  97. package/dist/panel-header.js.map +1 -0
  98. package/dist/resizable-region.d.ts +3 -1
  99. package/dist/resizable-region.js +1 -1
  100. package/dist/segmented-control.d.ts +3 -1
  101. package/dist/segmented-control.js +2 -1
  102. package/dist/select-register.d.ts +5 -1
  103. package/dist/select-register.js +3 -0
  104. package/dist/select-register.js.map +1 -1
  105. package/dist/select.d.ts +5 -2
  106. package/dist/select.js +2 -1
  107. package/dist/skeleton.d.ts +24 -0
  108. package/dist/skeleton.js +3 -0
  109. package/dist/skeleton.js.map +1 -0
  110. package/dist/split-view.d.ts +42 -0
  111. package/dist/split-view.js +21 -0
  112. package/dist/split-view.js.map +1 -0
  113. package/dist/state-banner.d.ts +3 -1
  114. package/dist/state-banner.js +2 -1
  115. package/dist/styles/app-tab.css +152 -0
  116. package/dist/styles/catalog.css +498 -0
  117. package/dist/styles/collapsible-panel.css +146 -0
  118. package/dist/styles/disclosure-arrow.css +23 -0
  119. package/dist/styles/empty-state.css +47 -0
  120. package/dist/styles/foundation.css +347 -0
  121. package/dist/styles/layout.css +73 -0
  122. package/dist/styles/list-action-row.css +160 -0
  123. package/dist/styles/list-header.css +160 -0
  124. package/dist/styles/list-item.css +123 -0
  125. package/dist/styles/loading-spinner.css +21 -0
  126. package/dist/styles/lucide-icon.css +6 -0
  127. package/dist/styles/nav-stack.css +111 -0
  128. package/dist/styles/panel-header.css +64 -0
  129. package/dist/styles/resizable-region.css +111 -0
  130. package/{src → dist/styles}/segmented-control.css +62 -31
  131. package/dist/styles/select.css +138 -0
  132. package/dist/styles/skeleton.css +31 -0
  133. package/dist/styles/split-view.css +35 -0
  134. package/dist/styles/state-banner.css +173 -0
  135. package/dist/styles/styles.css +23 -0
  136. package/dist/styles/tab-bar.css +45 -0
  137. package/dist/styles/tab-scaffold.css +84 -0
  138. package/dist/styles/token-search-field.css +289 -0
  139. package/dist/styles/toolbar-control-group.css +211 -0
  140. package/dist/styles/toolbar-text.css +40 -0
  141. package/dist/styles/toolbar.css +61 -0
  142. package/dist/styles/value-table.css +75 -0
  143. package/{src → dist/styles}/webawesome.css +119 -29
  144. package/dist/styles/workbench.css +94 -0
  145. package/dist/tab-bar.d.ts +8 -1
  146. package/dist/tab-bar.js +1 -1
  147. package/dist/tab-scaffold.d.ts +30 -0
  148. package/dist/tab-scaffold.js +16 -0
  149. package/dist/tab-scaffold.js.map +1 -0
  150. package/dist/token-search-field.d.ts +43 -0
  151. package/dist/token-search-field.js +4 -0
  152. package/dist/token-search-field.js.map +1 -0
  153. package/dist/toolbar-text.d.ts +15 -3
  154. package/dist/toolbar-text.js +2 -1
  155. package/dist/value-table.d.ts +12 -2
  156. package/dist/value-table.js +2 -1
  157. package/dist/webawesome.d.ts +100 -0
  158. package/dist/webawesome.js +3 -0
  159. package/dist/webawesome.js.map +1 -0
  160. package/dist/wire-catalog.d.ts +26 -0
  161. package/dist/wire-catalog.js +48 -0
  162. package/dist/wire-catalog.js.map +1 -0
  163. package/dist/wire-nav-stack.d.ts +15 -0
  164. package/dist/wire-nav-stack.js +88 -0
  165. package/dist/wire-nav-stack.js.map +1 -0
  166. package/dist/wire-resizable-regions.js +1 -1
  167. package/dist/wire-sidebar.d.ts +43 -0
  168. package/dist/wire-sidebar.js +117 -0
  169. package/dist/wire-sidebar.js.map +1 -0
  170. package/dist/wire-tab-bars.d.ts +15 -2
  171. package/dist/wire-tab-bars.js +1 -1
  172. package/dist/wire-tab-scaffold.d.ts +11 -0
  173. package/dist/wire-tab-scaffold.js +16 -0
  174. package/dist/wire-tab-scaffold.js.map +1 -0
  175. package/dist/wire-token-search-fields.d.ts +97 -0
  176. package/dist/wire-token-search-fields.js +5 -0
  177. package/dist/wire-token-search-fields.js.map +1 -0
  178. package/dist/workbench.d.ts +33 -0
  179. package/dist/workbench.js +17 -0
  180. package/dist/workbench.js.map +1 -0
  181. package/docs/accessibility.md +101 -3
  182. package/docs/app-layouts.md +61 -0
  183. package/docs/catalog.md +155 -0
  184. package/docs/collapsible-panel.md +102 -0
  185. package/docs/component-contract.md +169 -4
  186. package/docs/component-selection.md +185 -0
  187. package/docs/design/templates/panel-header/icon-actions-dark.svg +1 -0
  188. package/docs/design/templates/panel-header/icon-actions.svg +1 -0
  189. package/docs/design/templates/panel-header/icon-summary-actions-dark.svg +1 -0
  190. package/docs/design/templates/panel-header/icon-summary-actions.svg +1 -0
  191. package/docs/design/templates/panel-header/no-icon-dark.svg +1 -0
  192. package/docs/design/templates/panel-header/no-icon.svg +1 -0
  193. package/docs/design/templates/panel-header/page-heading-dark.svg +1 -0
  194. package/docs/design/templates/panel-header/page-heading.svg +1 -0
  195. package/docs/design/templates/panel-header/title-only-dark.svg +1 -0
  196. package/docs/design/templates/panel-header/title-only.svg +1 -0
  197. package/docs/design/templates/panel-header-dark.svg +13 -0
  198. package/docs/design/templates/panel-header.svg +13 -0
  199. package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
  200. package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
  201. package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
  202. package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
  203. package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
  204. package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
  205. package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
  206. package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
  207. package/docs/design/templates/toolbar-control-group-dark.svg +11 -0
  208. package/docs/design/templates/toolbar-control-group.svg +11 -0
  209. package/docs/design/templates.md +83 -0
  210. package/docs/design-philosophy.md +61 -1
  211. package/docs/device-class.md +54 -0
  212. package/docs/examples/command-palette-adapter.tsx +70 -0
  213. package/docs/layout.md +131 -0
  214. package/docs/nav-stack.md +47 -0
  215. package/docs/recipes.md +167 -0
  216. package/docs/split-view.md +49 -0
  217. package/docs/tab-scaffold.md +41 -0
  218. package/docs/ux-demo.md +23 -9
  219. package/docs/webawesome-theme.md +15 -0
  220. package/docs/workbench.md +47 -0
  221. package/llms.txt +123 -14
  222. package/package.json +139 -44
  223. package/ux-demo/recipes/app-shell.tsx +32 -0
  224. package/ux-demo/recipes/collapsible-sidebar.tsx +100 -0
  225. package/ux-demo/recipes/compact-toolbar.tsx +43 -0
  226. package/ux-demo/recipes/composer-form.tsx +63 -0
  227. package/ux-demo/recipes/list-detail-dialog.tsx +45 -0
  228. package/ux-demo/recipes/list-workspace-states.tsx +26 -0
  229. package/ux-demo/recipes/loaders.ts +33 -0
  230. package/ux-demo/recipes/loading-inspector.tsx +81 -0
  231. package/ux-demo/recipes/mount-recipe.ts +43 -0
  232. package/ux-demo/recipes/navigation-sidebar.tsx +26 -0
  233. package/ux-demo/recipes/navigation-stack.tsx +76 -0
  234. package/ux-demo/recipes/recipes.css +448 -0
  235. package/ux-demo/recipes/types.ts +17 -0
  236. package/ux-demo/recipes/workspace-header.tsx +12 -0
  237. package/dist/browser/dialog-header.js +0 -3
  238. package/dist/browser/menu-header.js +0 -3
  239. package/dist/browser/menu-item.js +0 -3
  240. package/dist/browser/page-header.js +0 -3
  241. package/dist/chunk-2PES33HS.js +0 -13
  242. package/dist/chunk-2PES33HS.js.map +0 -1
  243. package/dist/chunk-3O7ZEUW2.js.map +0 -1
  244. package/dist/chunk-723YNJBH.js +0 -17
  245. package/dist/chunk-723YNJBH.js.map +0 -1
  246. package/dist/chunk-GY5WH7TO.js +0 -10
  247. package/dist/chunk-GY5WH7TO.js.map +0 -1
  248. package/dist/chunk-MBR3TYNL.js.map +0 -1
  249. package/dist/chunk-MQIZAD4A.js.map +0 -1
  250. package/dist/chunk-NHXX4JI5.js +0 -28
  251. package/dist/chunk-NHXX4JI5.js.map +0 -1
  252. package/dist/chunk-OPCJRCYO.js +0 -19
  253. package/dist/chunk-OPCJRCYO.js.map +0 -1
  254. package/dist/chunk-T5XNMVOI.js +0 -24
  255. package/dist/chunk-T5XNMVOI.js.map +0 -1
  256. package/dist/chunk-TRNBHPNW.js.map +0 -1
  257. package/dist/chunk-VHW5YNIB.js +0 -17
  258. package/dist/chunk-VHW5YNIB.js.map +0 -1
  259. package/dist/chunk-VKH2JLDZ.js +0 -10
  260. package/dist/chunk-VKH2JLDZ.js.map +0 -1
  261. package/dist/chunk-WY4CAEPG.js +0 -14
  262. package/dist/chunk-WY4CAEPG.js.map +0 -1
  263. package/dist/dialog-header.d.ts +0 -14
  264. package/dist/dialog-header.js +0 -3
  265. package/dist/dialog-header.js.map +0 -1
  266. package/dist/menu-header.d.ts +0 -15
  267. package/dist/menu-header.js +0 -3
  268. package/dist/menu-header.js.map +0 -1
  269. package/dist/menu-item.d.ts +0 -22
  270. package/dist/menu-item.js +0 -3
  271. package/dist/menu-item.js.map +0 -1
  272. package/dist/page-header.d.ts +0 -9
  273. package/dist/page-header.js +0 -3
  274. package/dist/page-header.js.map +0 -1
  275. package/src/app-tab.css +0 -20
  276. package/src/dialog-header.css +0 -9
  277. package/src/empty-state.css +0 -6
  278. package/src/foundation.css +0 -106
  279. package/src/loading-spinner.css +0 -4
  280. package/src/lucide-icon.css +0 -1
  281. package/src/menu-header.css +0 -13
  282. package/src/menu-item.css +0 -12
  283. package/src/page-header.css +0 -3
  284. package/src/resizable-region.css +0 -19
  285. package/src/select.css +0 -18
  286. package/src/state-banner.css +0 -31
  287. package/src/styles.css +0 -18
  288. package/src/tab-bar.css +0 -4
  289. package/src/toolbar-control-group.css +0 -25
  290. package/src/toolbar-text.css +0 -4
  291. package/src/toolbar.css +0 -9
  292. package/src/value-table.css +0 -5
@@ -2,13 +2,89 @@
2
2
 
3
3
  `@kerfjs/ui` components are plain functions that return Kerf `SafeHtml`. They have no component instance or lifecycle.
4
4
 
5
+ ## Selection boundary
6
+
7
+ Begin with the [need-first component matrix](./component-selection.md). Reuse a
8
+ primitive only when purpose, anatomy, state, and interaction match. Compose
9
+ public primitives for recurring layout; add a thin application adapter for
10
+ product state and policy; use custom markup when the semantic contract differs.
11
+ Recurring cross-product custom patterns belong in an upstream component or
12
+ recipe request, not in duplicated markup or selectors for undocumented anatomy.
13
+
14
+ The shipped [`component-catalog.json`](../ai/component-catalog.json) is the
15
+ canonical machine-readable inventory. Its facts are projected into the UX
16
+ catalog deterministically; package checks compare them with runtime exports,
17
+ package delivery paths, Web Awesome's installed manifest, AI guidance, and
18
+ documentation links. Nuanced prose in this contract and the selection guide
19
+ remains authored rather than generated.
20
+
5
21
  ## Ownership boundaries
6
22
 
7
23
  - Components own semantic markup, stable anatomy, documented variants, ARIA projection, and package CSS hooks.
8
24
  - Applications own signals/stores, product copy, domain-state mapping, persistence, routing, permissions, and transport.
9
25
  - Actions are `data-action` strings. Wire them at a stable root with `delegate()` or `delegateActions()` and retain the disposer.
10
26
  - A reusable component never owns per-instance mutable module state.
11
- - Consumers style through `--kui-*` semantic tokens and public component classes. Foundation tokens provide opinionated neutral, brand/info, success, warning, and danger fill/border/foreground roles. Stateful components expose local override variables; do not target private descendants when a documented variant or token exists.
27
+ - Consumers style through `--kui-*` semantic tokens and public component classes. Foundation tokens provide opinionated neutral, brand/info, success, warning, and danger fill/border/foreground roles. Stateful components expose local override variables; prefer an equivalent prop or token before writing a selector.
28
+
29
+ `ListItem.rootAttributes`, `ListActionRow.rootAttributes`,
30
+ `ListHeader.rootAttributes`, and `AppTab.rootAttributes` accept
31
+ application-owned `data-*` metadata without
32
+ adding product fields to the shared API. A `ListActionRow` trailing action uses
33
+ `trailingActionAttributes`, and a `ListHeader` action/disclosure uses
34
+ `triggerAttributes`, for `data-*`, native popover target/action attributes, and
35
+ the corresponding `aria-controls`/`aria-haspopup` relationship. These slots do
36
+ not accept roles or component-owned action, selection, disclosure, accessible
37
+ name, disabled, or icon fields. Extension objects are filtered at runtime,
38
+ including case-insensitive rejection of protected `data-*` names, and the
39
+ component writes its protected attributes after the accepted metadata. Typed,
40
+ structurally widened, and JavaScript callers therefore cannot replace its
41
+ contract.
42
+
43
+ `AppTab.closeIcon` and `ResizableRegion.handleIcon` replace dormant decorative
44
+ glyph content only. They must not contain controls or interactive roles. The
45
+ component continues to own the named close button or focusable separator, and
46
+ `wireTabBars()` / `wireResizableRegions()` continue to own transient behavior
47
+ and disposal.
48
+
49
+ `ListItem.trailing`, `ListActionRow.label`, `ListActionRow.icon`, and
50
+ `ListActionRow.trailingActionIcon` are dormant SafeHtml slots. They must not
51
+ contain controls. When a row needs an independently interactive trailing
52
+ region, `ListActionRow` owns the noninteractive root and the two sibling native
53
+ buttons; the application owns their delegated behavior and controlled state.
54
+ Both row components align a leading icon with the first label line when
55
+ `multiline` allows the label to wrap; additional lines extend below that fixed
56
+ visual anchor.
57
+
58
+ ### Public CSS anatomy
59
+
60
+ The `publicClasses` array on each entry in
61
+ [`component-catalog.json`](../ai/component-catalog.json) is the exact supported
62
+ anatomy boundary. A scoped selector may join documented public classes, such as
63
+ `.workspace .kui-toolbar .kui-toolbar__trailing`, when composition-specific
64
+ layout cannot be expressed by a prop or token. A class being public does not
65
+ make copied component markup an invocation or transfer state and accessibility
66
+ ownership to the application.
67
+
68
+ Do not select a component's descendant by element name, id, attribute alone, or
69
+ an unlisted implementation class. Selectors such as `.kui-state-banner span`,
70
+ `.kui-list-item [data-state]`, and `.kui-list-item .local-label` depend on
71
+ private structure. If no prop, token, or cataloged class expresses a recurring
72
+ need, request a supported hook instead of inferring one from rendered markup.
73
+
74
+ `DisclosureArrow` uses an 18px root-scaled visual by default. Override
75
+ `--kui-disclosure-arrow-size` on the component or its containing scope when a
76
+ consumer needs another size. Kerf `Select` uses a separate Web Awesome expand
77
+ glyph contract, `--kui-disclosure-icon-scale: .5`; changing one contract does
78
+ not implicitly change the other. Direction changes take the shortest rotation
79
+ path; a 180-degree closed-to-open tie uses counterclockwise rotation.
80
+
81
+ In `ListHeader` toggle mode, omitting `actionIcon` composes the production
82
+ `DisclosureArrow` and derives its visual direction from `expanded`. The app
83
+ must update that controlled state and reveal or hide real content. Passing a
84
+ custom `actionIcon` replaces the default rather than layering or rotating both.
85
+ The root fills the available inline width after its standard margins. A
86
+ separate action stays at the logical end in its 44px target while the visible
87
+ glyph defaults to 18px through `--kui-list-header-action-icon-size`.
12
88
 
13
89
  `StateBanner` exposes instance-level `--kui-state-banner-background`,
14
90
  `--kui-state-banner-border`, `--kui-state-banner-foreground`,
@@ -27,11 +103,47 @@ separator with a compact grip that appears on hover or keyboard focus. Override
27
103
  `--kui-resizable-region-handle-active-color` on a region when its containing
28
104
  surface needs a different contrast level.
29
105
 
106
+ `@kerfjs/ui/layout.css` defines one structural model for sidebars, main areas,
107
+ inspectors, and dialogs. `.kui-pane` is unpadded and contains an optional
108
+ toolbar, one scrolling `.kui-pane__content`, and an optional footer.
109
+ `.kui-content` gives major children 24px vertical separation.
110
+ `.kui-content-item` gives one child 8px inline margin, a real 1px border,
111
+ 8px padding, and 12px corners; border and background may be transparent without
112
+ changing geometry. `.kui-content-item--pill` selects the 22px radius.
113
+
114
+ `ToolbarControlGroup` is the unit of toolbar organization, even for dormant
115
+ text. Each group reserves `calc(2px + remify(42px))`, or 44px, with 8px between
116
+ groups and inside items. `ListHeader` similarly separates its dormant title and
117
+ optional count or badge from its optional 44px action. Use the mutually
118
+ exclusive `count`/`countLabel` pair for non-negative safe-integer section
119
+ quantities; reserve `badge` for non-count `SafeHtml`. Do not concatenate counts
120
+ into the section label. Do not add padding to pane shells,
121
+ double child-owned geometry with wrapper insets, or create competing scroll
122
+ owners. The [layout contract](./layout.md) lists the public roles and tokens.
123
+
124
+ `PanelHeader` is a plain top `Toolbar` used as a panel, dialog, or page heading;
125
+ it overrides no Toolbar styles. The leading zone holds an optional icon (a normal
126
+ bordered `ToolbarControlGroup` given a brand fill with a matching border) and the
127
+ title as extra-large `ToolbarText`, and the app's `actions` go straight into the
128
+ trailing zone (typically as a `ToolbarControlGroup`). The icon group is omitted
129
+ when no icon is passed. The optional summary is a separate row aligned below the
130
+ title, so it cannot pull the icon group out of vertical alignment with the
131
+ title and action row.
132
+
133
+ `ValueTable` composes typed `ValueTableRow` entries. A row owns its `dt`/`dd`
134
+ semantics and may receive a leading `SafeHtml` icon. Every row keeps 8px of
135
+ root-scaled padding above and below its content; the shared item-padding token
136
+ continues to own its inline inset. Separators follow the content they introduce:
137
+ 8px from either edge for an iconless row, or 40px from the left edge (8px
138
+ padding + 24px icon + 8px gap) and 8px from the right edge for an icon-bearing
139
+ row. Applications own the values, formatting, and whether an icon is decorative
140
+ or meaningfully labeled.
141
+
30
142
  ## Imports and side effects
31
143
 
32
144
  Every component has an explicit JS and CSS subpath. In CSS-aware browser builds,
33
145
  the JS component subpath resolves to a generated wrapper that imports the
34
- foundation, the component stylesheet, and styles for its reachable UI
146
+ foundation, the compiled component stylesheet from `dist/styles`, and styles for its reachable UI
35
147
  subcomponents. The source graph derives those transitive styles at build time;
36
148
  an application root never maintains that list. Unused component subpaths and
37
149
  their CSS remain unreachable. The root barrel and `@kerfjs/ui/unstyled` stay
@@ -41,23 +153,76 @@ overrides load later in the cascade or set scoped `--kui-*` variables. JavaScrip
41
153
  modules are pure except the browser style wrappers and `@kerfjs/ui/select/register`,
42
154
  which registers exactly the Web Awesome elements used by `Select`. Eventful
43
155
  helpers such as `wireResizableRegions` and `wireTabBars` attach listeners only
44
- when called and return disposers. CSS, the generated wrappers that make it
156
+ when called and return disposers. `wireTokenSearchFields` goes one step further:
157
+ by default it also owns the collapsible field's transient expand/collapse/focus
158
+ (activate to reveal and focus, Escape or empty blur to collapse), holding that
159
+ state in a signal it exposes on the returned handle. An app reads that signal in
160
+ render, hands in its own via `collapsible.signals`, drives it through
161
+ `handle.open`/`handle.close`, or disables any individual behavior — so transient
162
+ UI is consistent by default without every app reinventing it.
163
+
164
+ `wireTokenSearchFields` is a deliberate exception, not the rule for `wire…`
165
+ helpers. Its collapse behavior was *rich and error-prone* — reveal, focus
166
+ transfer, Escape, empty-blur collapse, focus return — the kind of transient chrome
167
+ apps kept reimplementing inconsistently, so the helper owns it. Everywhere else the
168
+ app's state is **domain or persisted, not transient chrome, and stays app-owned**: a
169
+ `NavStack`'s view stack is navigation history, a `TabBar`/`TabScaffold`'s selection
170
+ and tab order are data, a `ResizableRegion`'s committed size and a
171
+ `Workbench`/`SplitView` rail's `collapsed` flag are persisted layout preferences.
172
+ Each helper already owns only the *ephemeral mechanics* around that state —
173
+ `wireNavStack` the push/pop animation, `wireTabBars` the overflow autoscroll and
174
+ drag preview, `wireResizableRegions` the live drag preview — and reports committed
175
+ changes through callbacks. A `ListHeader` `toggle` disclosure's `expanded` is
176
+ likewise app-owned: it is a one-line boolean the app already tracks and must read to
177
+ render the section body, so a managed helper would remove no real complexity. Reach
178
+ for a managed default only when the transient behavior is substantial enough that
179
+ hand-rolling it produces genuine, inconsistent variation.
180
+
181
+ CSS, the generated wrappers that make it
45
182
  reachable, and the registration module are the package's only declared side
46
183
  effects.
47
184
 
185
+ A value-bearing component's `placeholder` prop is a first-class loading mode: the
186
+ component renders its own real chrome (labels, icon and action affordances,
187
+ container geometry) while replacing each **value** slot with a subtle, deliberately
188
+ unanimated `Skeleton` block and disabling its own interactive controls
189
+ (`aria-busy`, dropped `data-action`, disabled buttons). Sizes and shapes stay
190
+ identical to the populated component, so a parent composes a faithful loading view —
191
+ an inspector, a detail pane — from placeholder children without hand-rebuilding
192
+ markup. The application still owns the loading lifecycle (when to pass `placeholder`)
193
+ and which slots are unknown; the standalone `Skeleton` covers custom slots. It is a
194
+ placeholder, not progress: use `LoadingSpinner` for known busy activity, and never
195
+ animate the skeleton.
196
+
197
+ Package source styles express root-scaled geometry with `remify(<px>)`; the
198
+ build converts it against the 16px authoring baseline and exposes only ordinary
199
+ `rem` CSS. Literal pixels remain for intentional hairlines, and `em` remains
200
+ explicit when a value is relative to its component's current font size. The UX
201
+ catalog applies the same transform to source CSS under Vite so `npm run dev`
202
+ retains hot module replacement.
203
+
48
204
  `@kerfjs/ui/webawesome.css` is a separate, optional CSS boundary. It imports
49
205
  Web Awesome's public base stylesheet and overrides its semantic theme layer to
50
206
  match the Kerf/Hot Sheet 2 system. It never imports component JavaScript.
207
+ `import type {} from '@kerfjs/ui/webawesome'` is the matching side-effect-free
208
+ Kerf JSX declaration boundary for every catalog-supported `wa-*` tag.
51
209
  Consumers register only the individual Web Awesome modules they render and can
52
210
  override `--wa-*` values after the theme import. The shared `--wa-*` values feed
53
211
  both Web Awesome controls and Kerf's `--kui-*` foundation aliases, preventing a
54
212
  second application palette.
55
213
 
214
+ `Select` owns its custom-element reconciliation seam. It gives each slotted
215
+ option icon a stable key and leaves the upgraded Web Awesome-owned slot subtree
216
+ untouched on later Kerf renders. Its custom selected slot is keyed by the
217
+ controlled value so changed selections replace that content. Applications
218
+ should pass ordinary `choices` and `renderSelected` output rather than adding
219
+ their own morph-control attributes.
220
+
56
221
  `kerfjs` is a peer dependency and remains external in every build. Importing a toolbar must not bundle a second Kerf runtime, another UI component, Web Awesome registration, the UX catalog, or development tooling.
57
222
 
58
223
  ## Extracted versus application-specific
59
224
 
60
- The package set is intentionally domain-neutral: icon rendering, toolbar primitives, controlled segmented choices, menu rows/headers, resizable regions and wiring, controlled reorderable tab bars, headers, loading, select, banners, empty states, dialog headers, and value tables.
225
+ The package set is intentionally domain-neutral: icon rendering, toolbar primitives, controlled segmented choices, a controlled token-chip search field with DOM read/caret helpers, menu rows/headers, resizable regions and wiring, controlled reorderable tab bars, headers, loading, select, banners, empty states, dialog headers, and typed value-table rows.
61
226
 
62
227
  Keep product adapters outside the package: connection-state maps, ticket empty-state copy, project/terminal/chat tab actions, saved pane sizes, provider or repository models, and application-specific palettes. An adapter may compose these primitives, map product state into their props, and override semantic CSS variables.
63
228
 
@@ -0,0 +1,185 @@
1
+ # Component selection and composition
2
+
3
+ Start with the interface need, not an export name or a screenshot resemblance.
4
+ Use the shipped [`component-catalog.json`](../ai/component-catalog.json) when a
5
+ tool needs exhaustive structured facts; this page remains the concise human
6
+ decision procedure.
7
+
8
+ 1. Search the [Kerf UX catalog](./ux-demo.md) and the supported Web Awesome set.
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.
11
+ 4. Add a thin application adapter for product copy, domain mapping, actions, routing, persistence, permissions, and transport.
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
+
14
+ The application adapter is usually a plain function that maps domain state to
15
+ component props plus stable `data-action` values. It is not a fork of package
16
+ markup or CSS.
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
24
+ [`design-philosophy.md`](./design-philosophy.md) "Reach for the primitive, not for
25
+ CSS".
26
+
27
+ ## Production recipes
28
+
29
+ Use the [complete recipe guide](./recipes.md) when several primitives form one
30
+ application boundary:
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` |
44
+
45
+ Recipes use public production exports and show ownership boundaries; they are
46
+ copyable reference compositions, not new monolithic components.
47
+
48
+ ## Missing recurring concepts
49
+
50
+ Kerf UI does not export a command-palette component. Do not invent a package
51
+ command-palette import.
52
+ The typed [application adapter example](./examples/command-palette-adapter.tsx)
53
+ imports `@kerfjs/ui/layout.css`, assigns one `.kui-layout` root and one surface
54
+ inset, and groups its related footer commands with `.kui-control-cluster` while
55
+ the application owns ranking, history, shortcut policy, focus policy,
56
+ availability, actions, and copy. If that concept recurs across products, open
57
+ an upstream component or recipe request.
58
+
59
+ ## Problem-to-component matrix
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
+ | 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`. | Text, heading level, and responsive priority. | `@kerfjs/ui/toolbar-text` | [Toolbar composition](../README.md#component-subpaths) |
68
+ | 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) |
69
+ | 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) |
70
+ | 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) |
71
+ | 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) |
72
+ | 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) |
73
+ | 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) |
74
+ | 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) |
75
+ | 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) |
76
+ | 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) |
77
+ | 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) |
78
+ | 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) |
79
+ | 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) |
80
+ | 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) |
81
+ | 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) |
82
+ | 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) |
83
+ | 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) |
84
+ | 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) |
85
+
86
+ ## Ambiguous choices
87
+
88
+ - `Toolbar` is persistent app chrome; `PanelHeader` heads a panel, dialog, or page.
89
+ - `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `Select` handles a longer value list.
90
+ - `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.
91
+ - `ResizableRegion` is an interactive controlled pane. CSS grid is the right answer when columns do not need a user-operable separator.
92
+ - `TokenSearchField` is a structured editor. A native input or `wa-input` is the right answer for ordinary text.
93
+
94
+ ## Toolbar composition
95
+
96
+ A `Toolbar` has three zones — `leading`, `center`, and `trailing`. In almost
97
+ every case the only things that go **directly** in a zone are `ToolbarText`
98
+ (identity/title text) and `ToolbarControlGroup` (any control or cluster of
99
+ controls). Do not drop bare buttons, inputs, links, or arbitrary markup straight
100
+ into a zone; wrap controls in a `ToolbarControlGroup` so they get the shared
101
+ toolbar geometry, hover/pressed treatment, and grouping. `SegmentedControl`,
102
+ `Select`, a collapsible `TokenSearchField`, and Web Awesome controls all live
103
+ **inside** a `ToolbarControlGroup`, not loose in the zone. `PanelHeader` is the
104
+ one wrapper that composes these for you as a panel/dialog/page heading.
105
+
106
+ Common toolbar patterns:
107
+
108
+ | Want | Put in the zone | Notes |
109
+ | --- | --- | --- |
110
+ | 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 |
111
+ | One or more icon/text buttons | `<ToolbarControlGroup>{buttons}</ToolbarControlGroup>` | Use `buttonAppearance="push"` for toggle buttons with `aria-pressed`; `single` for a lone control |
112
+ | An exclusive view switch | `<ToolbarControlGroup><SegmentedControl … /></ToolbarControlGroup>` | Not `TabBar`, which switches tabpanels |
113
+ | A value list | `<ToolbarControlGroup><Select … /></ToolbarControlGroup>` | Register `@kerfjs/ui/select/register` once |
114
+ | A collapsible search box | `<ToolbarControlGroup single><TokenSearchField collapsible … /></ToolbarControlGroup>` | The group animates the iconic ↔ expanded states; `wireTokenSearchFields` manages expand/collapse/focus by default |
115
+
116
+ A **popup menu in a toolbar** is a `single` `ToolbarControlGroup` wrapping a Web
117
+ Awesome `wa-dropdown`: its `slot="trigger"` `wa-button` is the toolbar button and
118
+ the `wa-dropdown-item`s are the menu. Keep the dropdown's managed light-DOM
119
+ children under `data-morph-skip-children` so kerf does not reconcile Web Awesome's
120
+ own DOM.
121
+
122
+ ```tsx
123
+ <ToolbarControlGroup single>
124
+ <wa-dropdown placement="bottom-start" data-morph-skip-children>
125
+ <wa-button slot="trigger" appearance="plain" with-caret aria-label="Sort">
126
+ <LucideIcon icon={ArrowDownAZ} name="arrow-down-a-z" />
127
+ </wa-button>
128
+ <wa-dropdown-item data-action="sort-recent">Recently updated</wa-dropdown-item>
129
+ <wa-dropdown-item data-action="sort-priority">Priority</wa-dropdown-item>
130
+ </wa-dropdown>
131
+ </ToolbarControlGroup>
132
+ ```
133
+
134
+ ## Correct composition and duplicated-markup trap
135
+
136
+ Correct: let the pane stay unpadded while its children own the shared 8/1/8
137
+ geometry and 44px targets.
138
+
139
+ ```tsx
140
+ <aside class="kui-pane">
141
+ <nav class="kui-pane__content kui-content">
142
+ <section>
143
+ <ListHeader label="Workspace" />
144
+ <ListItem action="open" label="Inbox" icon={inboxIcon} />
145
+ <ListActionRow action="open-file" label="main.ts" trailingAction="file-actions" trailingActionLabel="Actions for main.ts" trailingActionIcon={moreIcon} />
146
+ </section>
147
+ <div class="kui-content-item">Workspace details</div>
148
+ </nav>
149
+ </aside>
150
+ ```
151
+
152
+ Incorrect: duplicating component-like rows and compensating for nested padding
153
+ forks the package anatomy and spacing contract.
154
+
155
+ ```tsx
156
+ <aside class="sidebar padded">
157
+ <h2 class="list-header-copy">Workspace</h2>
158
+ <button class="menu-row-copy padded">Inbox</button>
159
+ <div class="panel indented-with-negative-margin">Workspace details</div>
160
+ </aside>
161
+ ```
162
+
163
+ ## Web Awesome overlap policy
164
+
165
+ Web Awesome catalog coverage means supported and themed, not preferred. Import
166
+ `@kerfjs/ui/webawesome` for Kerf JSX types, individual component modules for
167
+ registration, and the CSS-only `@kerfjs/ui/webawesome.css` theme.
168
+
169
+ | Web Awesome choice | Kerf decision |
170
+ | --- | --- |
171
+ | `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. |
172
+ | `wa-button-group` | Use only for exceptional grouped actions; use `SegmentedControl` for one-of-many selection. |
173
+ | `wa-input`, `wa-tag` | Use for ordinary text and tags; use `TokenSearchField` only when text and ordered filter tokens form one editor. |
174
+ | `wa-select`, `wa-option` | Use `Select`, which owns Kerf spacing, controlled rendering, icon stability, and explicit registration. |
175
+ | `wa-tab-group`, `wa-tab`, `wa-tab-panel` | Use `TabBar`/`AppTab` for application tabs or `SegmentedControl` for compact local views. |
176
+ | `wa-icon` | Use `LucideIcon` in application UI. |
177
+ | `wa-split-panel` | Use `ResizableRegion` for Kerf application panes; retain Split Panel only when its distinct API is required. |
178
+ | `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. |
179
+ | `wa-callout`, `wa-toast`, `wa-toast-item` | Use `StateBanner` for persistent inline app status, `EmptyState` for absent content, and toasts only for transient feedback. |
180
+ | `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. |
181
+ | `wa-tree`, `wa-tree-item`, `wa-animated-image`, `wa-comparison` | Use only for the specialized behavior named by the component. |
182
+ | `wa-zoomable-frame` | Avoid for application UI; keep embedded-media behavior application-owned. |
183
+
184
+ All other entries in the [Web Awesome theme contract](./webawesome-theme.md#coverage)
185
+ remain supported when their native semantic contract matches the product need.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="cp0"><rect x="28" y="28" width="551.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="551.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="cp3"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="ct4"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="ct5"><rect x="587.7" y="34" width="64.3" height="32"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(34,43,48)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(34,43,48)" stroke-width="1"/><g clip-path="url(#cp2)"><g transform="translate(39 39)" color="rgb(138, 203, 255)"><svg data-lucide="folder" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" style="overflow:hidden" width="22" height="22"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.7-.9l-.8-1.2A2 2 0 0 0 7.9 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#cp3)"><g clip-path="url(#ct4)"><text x="88" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Package details</text></g></g></g></g><rect x="587.7" y="34" width="64.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="588.5" y="34.5" width="63" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="600.69" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(245,245,247)">Done</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="cp0"><rect x="28" y="28" width="551.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="551.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="cp3"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="ct4"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="ct5"><rect x="587.7" y="34" width="64.3" height="32"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(235,245,255)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(235,245,255)" stroke-width="1"/><g clip-path="url(#cp2)"><g transform="translate(39 39)" color="rgb(30, 110, 244)"><svg data-lucide="folder" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" style="overflow:hidden" width="22" height="22"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.7-.9l-.8-1.2A2 2 0 0 0 7.9 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#cp3)"><g clip-path="url(#ct4)"><text x="88" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">Package details</text></g></g></g></g><rect x="587.7" y="34" width="64.3" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="588.5" y="34.5" width="63" height="31" rx="15.5" fill="none" stroke="rgb(209,209,214)" stroke-width="1"/><text x="600.69" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(29,29,31)">Done</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="cp0"><rect x="28" y="28" width="550.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="550.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="cp3"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="ct4"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="ct5"><rect x="586.7" y="34" width="65.3" height="32"/></clipPath><clipPath id="cp6"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="ct7"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(34,43,48)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(34,43,48)" stroke-width="1"/><g clip-path="url(#cp2)"><g transform="translate(39 39)" color="rgb(138, 203, 255)"><svg data-lucide="file-text" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" style="overflow:hidden" width="22" height="22"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2 2 0 0 1 1.7.7l3.6 3.6A2 2 0 0 1 20 8v12a2 2 0 0 1-2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M14 2v5a1 1 0 0 0 1 1h5" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M10 9H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 13H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 17H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#cp3)"><g clip-path="url(#ct4)"><text x="88" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Northstar migration</text></g></g></g></g><rect x="586.7" y="34" width="65.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="587.5" y="34.5" width="64" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="599.66" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(245,245,247)">Open</text><g clip-path="url(#cp6)"><g clip-path="url(#ct7)"><text x="88" y="95" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="16" fill="rgb(174,174,178)">In review · updated today</text></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="cp0"><rect x="28" y="28" width="550.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="550.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="cp3"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="ct4"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="ct5"><rect x="586.7" y="34" width="65.3" height="32"/></clipPath><clipPath id="cp6"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="ct7"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(235,245,255)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(235,245,255)" stroke-width="1"/><g clip-path="url(#cp2)"><g transform="translate(39 39)" color="rgb(30, 110, 244)"><svg data-lucide="file-text" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" style="overflow:hidden" width="22" height="22"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2 2 0 0 1 1.7.7l3.6 3.6A2 2 0 0 1 20 8v12a2 2 0 0 1-2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M14 2v5a1 1 0 0 0 1 1h5" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M10 9H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 13H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 17H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(30, 110, 244)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#cp3)"><g clip-path="url(#ct4)"><text x="88" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">Northstar migration</text></g></g></g></g><rect x="586.7" y="34" width="65.3" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="587.5" y="34.5" width="64" height="31" rx="15.5" fill="none" stroke="rgb(209,209,214)" stroke-width="1"/><text x="599.66" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(29,29,31)">Open</text><g clip-path="url(#cp6)"><g clip-path="url(#ct7)"><text x="88" y="95" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="16" fill="rgb(110,110,115)">In review · updated today</text></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="cp0"><rect x="28" y="28" width="557.5" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="557.5" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="ct4"><rect x="593.5" y="34" width="58.5" height="32"/></clipPath><clipPath id="cp5"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="ct6"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Workspace settings</text></g></g></g></g><rect x="593.5" y="34" width="58.5" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="594.5" y="34.5" width="57" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="606.53" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(245,245,247)">New</text><g clip-path="url(#cp5)"><g clip-path="url(#ct6)"><text x="36" y="95" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="16" fill="rgb(174,174,178)">Manage members, billing, and integrations.</text></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="cp0"><rect x="28" y="28" width="557.5" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="557.5" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="ct4"><rect x="593.5" y="34" width="58.5" height="32"/></clipPath><clipPath id="cp5"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="ct6"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">Workspace settings</text></g></g></g></g><rect x="593.5" y="34" width="58.5" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="594.5" y="34.5" width="57" height="31" rx="15.5" fill="none" stroke="rgb(209,209,214)" stroke-width="1"/><text x="606.53" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(29,29,31)">New</text><g clip-path="url(#cp5)"><g clip-path="url(#ct6)"><text x="36" y="95" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="16" fill="rgb(110,110,115)">Manage members, billing, and integrations.</text></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="cp0"><rect x="28" y="28" width="500.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="500.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="ct4"><rect x="536.7" y="34" width="115.3" height="32"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">UI foundations</text></g></g></g></g><rect x="536.7" y="34" width="115.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="537.5" y="34.5" width="114" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="549.7" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(245,245,247)">New pattern</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="cp0"><rect x="28" y="28" width="500.7" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="500.7" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="ct4"><rect x="536.7" y="34" width="115.3" height="32"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">UI foundations</text></g></g></g></g><rect x="536.7" y="34" width="115.3" height="32" rx="16" fill="rgb(255,255,255)"/><rect x="537.5" y="34.5" width="114" height="31" rx="15.5" fill="none" stroke="rgb(209,209,214)" stroke-width="1"/><text x="549.7" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(29,29,31)">New pattern</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 104" width="720" height="104"><defs><clipPath id="cp0"><rect x="28" y="28" width="616" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="616" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="146.9" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="146.9" height="44"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Recent activity</text></g></g></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 104" width="720" height="104"><defs><clipPath id="cp0"><rect x="28" y="28" width="616" height="44"/></clipPath><clipPath id="ov1"><rect x="28" y="28" width="616" height="44" rx="0"/></clipPath><clipPath id="cp2"><rect x="28" y="28" width="146.9" height="44"/></clipPath><clipPath id="ct3"><rect x="28" y="28" width="146.9" height="44"/></clipPath></defs><g clip-path="url(#cp0)"><g clip-path="url(#ov1)"><g clip-path="url(#cp2)"><g clip-path="url(#ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(29,29,31)">Recent activity</text></g></g></g></g></svg>
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="752" height="806" viewBox="0 0 752 806">
2
+ <rect width="752" height="806" fill="#1c1c1e"/>
3
+ <text x="16" y="30" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">Icon, summary, and action</text>
4
+ <svg x="16" y="38" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="v0-cp0"><rect x="28" y="28" width="550.7" height="44"/></clipPath><clipPath id="v0-ov1"><rect x="28" y="28" width="550.7" height="44" rx="0"/></clipPath><clipPath id="v0-cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="v0-cp3"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="v0-ct4"><rect x="80" y="28" width="189.8" height="44"/></clipPath><clipPath id="v0-ct5"><rect x="586.7" y="34" width="65.3" height="32"/></clipPath><clipPath id="v0-cp6"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="v0-ct7"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#v0-cp0)"><g clip-path="url(#v0-ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(34,43,48)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(34,43,48)" stroke-width="1"/><g clip-path="url(#v0-cp2)"><g transform="translate(39 39)" color="rgb(138, 203, 255)"><svg data-lucide="file-text" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" style="overflow:hidden" width="22" height="22"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2 2 0 0 1 1.7.7l3.6 3.6A2 2 0 0 1 20 8v12a2 2 0 0 1-2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M14 2v5a1 1 0 0 0 1 1h5" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M10 9H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 13H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="M16 17H8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#v0-cp3)"><g clip-path="url(#v0-ct4)"><text x="88" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Northstar migration</text></g></g></g></g><rect x="586.7" y="34" width="65.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="587.5" y="34.5" width="64" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="599.66" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(245,245,247)">Open</text><g clip-path="url(#v0-cp6)"><g clip-path="url(#v0-ct7)"><text x="88" y="95" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="16" fill="rgb(174,174,178)">In review · updated today</text></g></g></svg>
5
+ <text x="16" y="204" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">Icon and action, no summary</text>
6
+ <svg x="16" y="212" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="v1-cp0"><rect x="28" y="28" width="551.7" height="44"/></clipPath><clipPath id="v1-ov1"><rect x="28" y="28" width="551.7" height="44" rx="0"/></clipPath><clipPath id="v1-cp2"><rect x="39" y="39" width="22" height="22"/></clipPath><clipPath id="v1-cp3"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="v1-ct4"><rect x="80" y="28" width="154.5" height="44"/></clipPath><clipPath id="v1-ct5"><rect x="587.7" y="34" width="64.3" height="32"/></clipPath></defs><g clip-path="url(#v1-cp0)"><g clip-path="url(#v1-ov1)"><rect x="28" y="28" width="44" height="44" rx="22" fill="rgb(34,43,48)"/><rect x="28.5" y="28.5" width="43" height="43" rx="21.5" fill="none" stroke="rgb(34,43,48)" stroke-width="1"/><g clip-path="url(#v1-cp2)"><g transform="translate(39 39)" color="rgb(138, 203, 255)"><svg data-lucide="folder" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" font-family="ui-sans-serif, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" style="overflow:hidden" width="22" height="22"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.7-.9l-.8-1.2A2 2 0 0 0 7.9 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(138, 203, 255)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></svg></g></g><g clip-path="url(#v1-cp3)"><g clip-path="url(#v1-ct4)"><text x="88" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Package details</text></g></g></g></g><rect x="587.7" y="34" width="64.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="588.5" y="34.5" width="63" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="600.69" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(245,245,247)">Done</text></svg>
7
+ <text x="16" y="354" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">No icon, summary and action</text>
8
+ <svg x="16" y="362" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 136" width="720" height="136"><defs><clipPath id="v2-cp0"><rect x="28" y="28" width="557.5" height="44"/></clipPath><clipPath id="v2-ov1"><rect x="28" y="28" width="557.5" height="44" rx="0"/></clipPath><clipPath id="v2-cp2"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="v2-ct3"><rect x="28" y="28" width="191.4" height="44"/></clipPath><clipPath id="v2-ct4"><rect x="593.5" y="34" width="58.5" height="32"/></clipPath><clipPath id="v2-cp5"><rect x="20" y="80" width="640" height="18"/></clipPath><clipPath id="v2-ct6"><rect x="20" y="80" width="640" height="18"/></clipPath></defs><g clip-path="url(#v2-cp0)"><g clip-path="url(#v2-ov1)"><g clip-path="url(#v2-cp2)"><g clip-path="url(#v2-ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Workspace settings</text></g></g></g></g><rect x="593.5" y="34" width="58.5" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="594.5" y="34.5" width="57" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="606.53" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(245,245,247)">New</text><g clip-path="url(#v2-cp5)"><g clip-path="url(#v2-ct6)"><text x="36" y="95" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="16" fill="rgb(174,174,178)">Manage members, billing, and integrations.</text></g></g></svg>
9
+ <text x="16" y="528" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">Title only</text>
10
+ <svg x="16" y="536" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 104" width="720" height="104"><defs><clipPath id="v3-cp0"><rect x="28" y="28" width="616" height="44"/></clipPath><clipPath id="v3-ov1"><rect x="28" y="28" width="616" height="44" rx="0"/></clipPath><clipPath id="v3-cp2"><rect x="28" y="28" width="146.9" height="44"/></clipPath><clipPath id="v3-ct3"><rect x="28" y="28" width="146.9" height="44"/></clipPath></defs><g clip-path="url(#v3-cp0)"><g clip-path="url(#v3-ov1)"><g clip-path="url(#v3-cp2)"><g clip-path="url(#v3-ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">Recent activity</text></g></g></g></g></svg>
11
+ <text x="16" y="670" font-family="system-ui, sans-serif" font-size="12" font-weight="600" fill="#aeaeb2">Page heading (h1) with action</text>
12
+ <svg x="16" y="678" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 112" width="720" height="112"><defs><clipPath id="v4-cp0"><rect x="28" y="28" width="500.7" height="44"/></clipPath><clipPath id="v4-ov1"><rect x="28" y="28" width="500.7" height="44" rx="0"/></clipPath><clipPath id="v4-cp2"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="v4-ct3"><rect x="28" y="28" width="144.6" height="44"/></clipPath><clipPath id="v4-ct4"><rect x="536.7" y="34" width="115.3" height="32"/></clipPath></defs><g clip-path="url(#v4-cp0)"><g clip-path="url(#v4-ov1)"><g clip-path="url(#v4-cp2)"><g clip-path="url(#v4-ct3)"><text x="36" y="57" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="20" font-weight="650" fill="rgb(245,245,247)">UI foundations</text></g></g></g></g><rect x="536.7" y="34" width="115.3" height="32" rx="16" fill="rgb(28,28,30)"/><rect x="537.5" y="34.5" width="114" height="31" rx="15.5" fill="none" stroke="rgb(72,72,74)" stroke-width="1"/><text x="549.7" y="56" font-family="&quot;ui-sans-serif&quot;, &quot;-apple-system&quot;, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif" font-size="16" fill="rgb(245,245,247)">New pattern</text></svg>
13
+ </svg>