@kerfjs/ui 4.4.1 → 5.0.0-beta.3

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 (158) hide show
  1. package/README.md +231 -5
  2. package/ai/component-catalog.json +4467 -0
  3. package/ai/component-catalog.schema.json +79 -0
  4. package/ai/public-api-signatures-v1.md +619 -0
  5. package/ai/skill.md +64 -11
  6. package/ai/webawesome-jsx-signatures-v1.md +106 -0
  7. package/dist/app-tab.d.ts +12 -2
  8. package/dist/app-tab.js +2 -1
  9. package/dist/browser/app-tab.js +2 -2
  10. package/dist/browser/dialog-header.js +4 -2
  11. package/dist/browser/disclosure-arrow.js +4 -0
  12. package/dist/browser/empty-state.js +3 -3
  13. package/dist/browser/loading-spinner.js +2 -2
  14. package/dist/browser/lucide-icon.js +2 -2
  15. package/dist/browser/menu-action-row.js +3 -0
  16. package/dist/browser/menu-header.js +4 -2
  17. package/dist/browser/menu-item.js +2 -2
  18. package/dist/browser/page-header.js +2 -2
  19. package/dist/browser/resizable-region.js +2 -2
  20. package/dist/browser/segmented-control.js +2 -2
  21. package/dist/browser/select.js +3 -3
  22. package/dist/browser/state-banner.js +2 -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 +2 -2
  27. package/dist/browser/toolbar.js +2 -2
  28. package/dist/browser/value-table.js +2 -2
  29. package/dist/chunk-56RJXPHG.js +43 -0
  30. package/dist/chunk-56RJXPHG.js.map +1 -0
  31. package/dist/chunk-7BR3DO2A.js +31 -0
  32. package/dist/chunk-7BR3DO2A.js.map +1 -0
  33. package/dist/chunk-7LDCTU7D.js +134 -0
  34. package/dist/chunk-7LDCTU7D.js.map +1 -0
  35. package/dist/{chunk-4HHCYSGT.js → chunk-AQNKJRFH.js} +2 -2
  36. package/dist/{chunk-4HHCYSGT.js.map → chunk-AQNKJRFH.js.map} +1 -1
  37. package/dist/{chunk-NHXX4JI5.js → chunk-CTQKI2XL.js} +3 -3
  38. package/dist/chunk-CTQKI2XL.js.map +1 -0
  39. package/dist/chunk-EGHL5S4P.js +33 -0
  40. package/dist/chunk-EGHL5S4P.js.map +1 -0
  41. package/dist/chunk-H5AGGVU5.js +75 -0
  42. package/dist/chunk-H5AGGVU5.js.map +1 -0
  43. package/dist/chunk-K3G72I6D.js +24 -0
  44. package/dist/chunk-K3G72I6D.js.map +1 -0
  45. package/dist/{chunk-3O7ZEUW2.js → chunk-M5JRI6MN.js} +3 -3
  46. package/dist/chunk-M5JRI6MN.js.map +1 -0
  47. package/dist/chunk-NPPD2A6O.js +40 -0
  48. package/dist/chunk-NPPD2A6O.js.map +1 -0
  49. package/dist/chunk-R6JHHBKQ.js +19 -0
  50. package/dist/chunk-R6JHHBKQ.js.map +1 -0
  51. package/dist/chunk-S3TPM2LP.js +23 -0
  52. package/dist/chunk-S3TPM2LP.js.map +1 -0
  53. package/dist/{chunk-T5XNMVOI.js → chunk-XZXRLY5Q.js} +14 -5
  54. package/dist/chunk-XZXRLY5Q.js.map +1 -0
  55. package/dist/dialog-header.d.ts +3 -2
  56. package/dist/dialog-header.js +3 -1
  57. package/dist/disclosure-arrow.d.ts +14 -0
  58. package/dist/disclosure-arrow.js +4 -0
  59. package/dist/disclosure-arrow.js.map +1 -0
  60. package/dist/index.d.ts +5 -1
  61. package/dist/index.js +16 -11
  62. package/dist/menu-action-row.d.ts +50 -0
  63. package/dist/menu-action-row.js +4 -0
  64. package/dist/menu-action-row.js.map +1 -0
  65. package/dist/menu-header.d.ts +28 -2
  66. package/dist/menu-header.js +4 -1
  67. package/dist/menu-item.d.ts +10 -1
  68. package/dist/menu-item.js +2 -1
  69. package/dist/resizable-region.d.ts +3 -1
  70. package/dist/resizable-region.js +1 -1
  71. package/dist/select.js +1 -1
  72. package/dist/styles/app-tab.css +152 -0
  73. package/dist/styles/dialog-header.css +87 -0
  74. package/dist/styles/disclosure-arrow.css +23 -0
  75. package/dist/styles/empty-state.css +47 -0
  76. package/dist/styles/foundation.css +326 -0
  77. package/dist/styles/layout.css +73 -0
  78. package/dist/styles/loading-spinner.css +21 -0
  79. package/dist/styles/lucide-icon.css +6 -0
  80. package/dist/styles/menu-action-row.css +154 -0
  81. package/dist/styles/menu-header.css +143 -0
  82. package/dist/styles/menu-item.css +118 -0
  83. package/dist/styles/page-header.css +26 -0
  84. package/dist/styles/resizable-region.css +111 -0
  85. package/{src → dist/styles}/segmented-control.css +62 -31
  86. package/dist/styles/select.css +96 -0
  87. package/dist/styles/state-banner.css +173 -0
  88. package/dist/styles/styles.css +22 -0
  89. package/dist/styles/tab-bar.css +45 -0
  90. package/dist/styles/token-search-field.css +281 -0
  91. package/dist/styles/toolbar-control-group.css +161 -0
  92. package/dist/styles/toolbar-text.css +29 -0
  93. package/dist/styles/toolbar.css +52 -0
  94. package/dist/styles/value-table.css +75 -0
  95. package/{src → dist/styles}/webawesome.css +119 -29
  96. package/dist/token-search-field.d.ts +43 -0
  97. package/dist/token-search-field.js +4 -0
  98. package/dist/token-search-field.js.map +1 -0
  99. package/dist/value-table.d.ts +10 -2
  100. package/dist/value-table.js +1 -1
  101. package/dist/webawesome.d.ts +100 -0
  102. package/dist/webawesome.js +3 -0
  103. package/dist/webawesome.js.map +1 -0
  104. package/dist/wire-resizable-regions.js +1 -1
  105. package/dist/wire-token-search-fields.d.ts +11 -0
  106. package/dist/wire-token-search-fields.js +5 -0
  107. package/dist/wire-token-search-fields.js.map +1 -0
  108. package/docs/accessibility.md +60 -3
  109. package/docs/component-contract.md +128 -3
  110. package/docs/component-selection.md +133 -0
  111. package/docs/design-philosophy.md +38 -0
  112. package/docs/examples/command-palette-adapter.tsx +70 -0
  113. package/docs/layout.md +95 -0
  114. package/docs/recipes.md +116 -0
  115. package/docs/ux-demo.md +17 -5
  116. package/docs/webawesome-theme.md +15 -0
  117. package/llms.txt +25 -2
  118. package/package.json +70 -27
  119. package/ux-demo/recipes/app-shell.tsx +32 -0
  120. package/ux-demo/recipes/compact-toolbar.tsx +43 -0
  121. package/ux-demo/recipes/composer-form.tsx +62 -0
  122. package/ux-demo/recipes/list-workspace-states.tsx +26 -0
  123. package/ux-demo/recipes/loaders.ts +27 -0
  124. package/ux-demo/recipes/master-detail-dialog.tsx +45 -0
  125. package/ux-demo/recipes/mount-recipe.ts +38 -0
  126. package/ux-demo/recipes/navigation-sidebar.tsx +26 -0
  127. package/ux-demo/recipes/recipes.css +328 -0
  128. package/ux-demo/recipes/types.ts +11 -0
  129. package/ux-demo/recipes/workspace-header.tsx +12 -0
  130. package/dist/chunk-3O7ZEUW2.js.map +0 -1
  131. package/dist/chunk-723YNJBH.js +0 -17
  132. package/dist/chunk-723YNJBH.js.map +0 -1
  133. package/dist/chunk-NHXX4JI5.js.map +0 -1
  134. package/dist/chunk-OPCJRCYO.js +0 -19
  135. package/dist/chunk-OPCJRCYO.js.map +0 -1
  136. package/dist/chunk-T5XNMVOI.js.map +0 -1
  137. package/dist/chunk-VKH2JLDZ.js +0 -10
  138. package/dist/chunk-VKH2JLDZ.js.map +0 -1
  139. package/dist/chunk-WY4CAEPG.js +0 -14
  140. package/dist/chunk-WY4CAEPG.js.map +0 -1
  141. package/src/app-tab.css +0 -20
  142. package/src/dialog-header.css +0 -9
  143. package/src/empty-state.css +0 -6
  144. package/src/foundation.css +0 -106
  145. package/src/loading-spinner.css +0 -4
  146. package/src/lucide-icon.css +0 -1
  147. package/src/menu-header.css +0 -13
  148. package/src/menu-item.css +0 -12
  149. package/src/page-header.css +0 -3
  150. package/src/resizable-region.css +0 -19
  151. package/src/select.css +0 -18
  152. package/src/state-banner.css +0 -31
  153. package/src/styles.css +0 -18
  154. package/src/tab-bar.css +0 -4
  155. package/src/toolbar-control-group.css +0 -25
  156. package/src/toolbar-text.css +0 -4
  157. package/src/toolbar.css +0 -9
  158. package/src/value-table.css +0 -5
@@ -2,13 +2,86 @@
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
+ `MenuItem.rootAttributes`, `MenuActionRow.rootAttributes`,
30
+ `MenuHeader.rootAttributes`, and `AppTab.rootAttributes` accept
31
+ application-owned `data-*` metadata without
32
+ adding product fields to the shared API. A `MenuActionRow` trailing action uses
33
+ `trailingActionAttributes`, and a `MenuHeader` 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
+ `MenuItem.trailing`, `MenuActionRow.label`, `MenuActionRow.icon`, and
50
+ `MenuActionRow.trailingActionIcon` are dormant SafeHtml slots. They must not
51
+ contain controls. When a row needs an independently interactive trailing
52
+ region, `MenuActionRow` owns the noninteractive root and the two sibling native
53
+ buttons; the application owns their delegated behavior and controlled state.
54
+
55
+ ### Public CSS anatomy
56
+
57
+ The `publicClasses` array on each entry in
58
+ [`component-catalog.json`](../ai/component-catalog.json) is the exact supported
59
+ anatomy boundary. A scoped selector may join documented public classes, such as
60
+ `.workspace .kui-toolbar .kui-toolbar__trailing`, when composition-specific
61
+ layout cannot be expressed by a prop or token. A class being public does not
62
+ make copied component markup an invocation or transfer state and accessibility
63
+ ownership to the application.
64
+
65
+ Do not select a component's descendant by element name, id, attribute alone, or
66
+ an unlisted implementation class. Selectors such as `.kui-state-banner span`,
67
+ `.kui-menu-item [data-state]`, and `.kui-menu-item .local-label` depend on
68
+ private structure. If no prop, token, or cataloged class expresses a recurring
69
+ need, request a supported hook instead of inferring one from rendered markup.
70
+
71
+ `DisclosureArrow` uses an 18px root-scaled visual by default. Override
72
+ `--kui-disclosure-arrow-size` on the component or its containing scope when a
73
+ consumer needs another size. Kerf `Select` uses a separate Web Awesome expand
74
+ glyph contract, `--kui-disclosure-icon-scale: .5`; changing one contract does
75
+ not implicitly change the other. Direction changes take the shortest rotation
76
+ path; a 180-degree closed-to-open tie uses counterclockwise rotation.
77
+
78
+ In `MenuHeader` toggle mode, omitting `actionIcon` composes the production
79
+ `DisclosureArrow` and derives its visual direction from `expanded`. The app
80
+ must update that controlled state and reveal or hide real content. Passing a
81
+ custom `actionIcon` replaces the default rather than layering or rotating both.
82
+ The root fills the available inline width after its standard margins. A
83
+ separate action stays at the logical end in its 44px target while the visible
84
+ glyph defaults to 18px through `--kui-menu-header-action-icon-size`.
12
85
 
13
86
  `StateBanner` exposes instance-level `--kui-state-banner-background`,
14
87
  `--kui-state-banner-border`, `--kui-state-banner-foreground`,
@@ -27,11 +100,47 @@ separator with a compact grip that appears on hover or keyboard focus. Override
27
100
  `--kui-resizable-region-handle-active-color` on a region when its containing
28
101
  surface needs a different contrast level.
29
102
 
103
+ `@kerfjs/ui/layout.css` defines one structural model for sidebars, main areas,
104
+ inspectors, and dialogs. `.kui-pane` is unpadded and contains an optional
105
+ toolbar, one scrolling `.kui-pane__content`, and an optional footer.
106
+ `.kui-content` gives major children 24px vertical separation.
107
+ `.kui-content-item` gives one child 8px inline margin, a real 1px border,
108
+ 8px padding, and 12px corners; border and background may be transparent without
109
+ changing geometry. `.kui-content-item--pill` selects the 22px radius.
110
+
111
+ `ToolbarControlGroup` is the unit of toolbar organization, even for dormant
112
+ text. Each group reserves `calc(2px + remify(42px))`, or 44px, with 8px between
113
+ groups and inside items. `MenuHeader` similarly separates its dormant title and
114
+ optional count or badge from its optional 44px action. Use the mutually
115
+ exclusive `count`/`countLabel` pair for non-negative safe-integer section
116
+ quantities; reserve `badge` for non-count `SafeHtml`. Do not concatenate counts
117
+ into the section label. Do not add padding to pane shells,
118
+ double child-owned geometry with wrapper insets, or create competing scroll
119
+ owners. The [layout contract](./layout.md) lists the public roles and tokens.
120
+
121
+ `DialogHeader` composes its icon and first title line inside a borderless
122
+ `ToolbarControlGroup` in a real top `Toolbar`. Direct `actions` children are
123
+ automatically placed in one contained `ToolbarControlGroup`; use the optional
124
+ localized `actionsLabel` when that group needs an accessible name. Existing
125
+ pre-grouped action content remains layout-compatible, but new callers should
126
+ pass the action children rather than groups. The optional summary is a separate
127
+ row aligned below the title, so it cannot pull the 24px icon or 34px circular
128
+ icon background out of vertical alignment with the 44px action row.
129
+
130
+ `ValueTable` composes typed `ValueTableRow` entries. A row owns its `dt`/`dd`
131
+ semantics and may receive a leading `SafeHtml` icon. Every row keeps 8px of
132
+ root-scaled padding above and below its content; the shared item-padding token
133
+ continues to own its inline inset. Separators follow the content they introduce:
134
+ 8px from either edge for an iconless row, or 40px from the left edge (8px
135
+ padding + 24px icon + 8px gap) and 8px from the right edge for an icon-bearing
136
+ row. Applications own the values, formatting, and whether an icon is decorative
137
+ or meaningfully labeled.
138
+
30
139
  ## Imports and side effects
31
140
 
32
141
  Every component has an explicit JS and CSS subpath. In CSS-aware browser builds,
33
142
  the JS component subpath resolves to a generated wrapper that imports the
34
- foundation, the component stylesheet, and styles for its reachable UI
143
+ foundation, the compiled component stylesheet from `dist/styles`, and styles for its reachable UI
35
144
  subcomponents. The source graph derives those transitive styles at build time;
36
145
  an application root never maintains that list. Unused component subpaths and
37
146
  their CSS remain unreachable. The root barrel and `@kerfjs/ui/unstyled` stay
@@ -45,19 +154,35 @@ when called and return disposers. CSS, the generated wrappers that make it
45
154
  reachable, and the registration module are the package's only declared side
46
155
  effects.
47
156
 
157
+ Package source styles express root-scaled geometry with `remify(<px>)`; the
158
+ build converts it against the 16px authoring baseline and exposes only ordinary
159
+ `rem` CSS. Literal pixels remain for intentional hairlines, and `em` remains
160
+ explicit when a value is relative to its component's current font size. The UX
161
+ catalog applies the same transform to source CSS under Vite so `npm run dev`
162
+ retains hot module replacement.
163
+
48
164
  `@kerfjs/ui/webawesome.css` is a separate, optional CSS boundary. It imports
49
165
  Web Awesome's public base stylesheet and overrides its semantic theme layer to
50
166
  match the Kerf/Hot Sheet 2 system. It never imports component JavaScript.
167
+ `import type {} from '@kerfjs/ui/webawesome'` is the matching side-effect-free
168
+ Kerf JSX declaration boundary for every catalog-supported `wa-*` tag.
51
169
  Consumers register only the individual Web Awesome modules they render and can
52
170
  override `--wa-*` values after the theme import. The shared `--wa-*` values feed
53
171
  both Web Awesome controls and Kerf's `--kui-*` foundation aliases, preventing a
54
172
  second application palette.
55
173
 
174
+ `Select` owns its custom-element reconciliation seam. It gives each slotted
175
+ option icon a stable key and leaves the upgraded Web Awesome-owned slot subtree
176
+ untouched on later Kerf renders. Its custom selected slot is keyed by the
177
+ controlled value so changed selections replace that content. Applications
178
+ should pass ordinary `choices` and `renderSelected` output rather than adding
179
+ their own morph-control attributes.
180
+
56
181
  `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
182
 
58
183
  ## Extracted versus application-specific
59
184
 
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.
185
+ 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
186
 
62
187
  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
188
 
@@ -0,0 +1,133 @@
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
+ ## Production recipes
19
+
20
+ Use the [complete recipe guide](./recipes.md) when several primitives form one
21
+ application boundary:
22
+
23
+ | Task | Stable catalog route |
24
+ | --- | --- |
25
+ | Desktop application shell | [Catalog](../ux-demo/) · `?component=recipe-app-shell` |
26
+ | Navigation sidebar | [Catalog](../ux-demo/) · `?component=recipe-navigation-sidebar` |
27
+ | Workspace header | [Catalog](../ux-demo/) · `?component=recipe-workspace-header` |
28
+ | Master-detail dialog | [Catalog](../ux-demo/) · `?component=recipe-master-detail-dialog` |
29
+ | Composer form | [Catalog](../ux-demo/) · `?component=recipe-composer-form` |
30
+ | List workspace states | [Catalog](../ux-demo/) · `?component=recipe-list-workspace-states` |
31
+ | Compact toolbar choices and actions | [Catalog](../ux-demo/) · `?component=recipe-compact-toolbar` |
32
+
33
+ Recipes use public production exports and show ownership boundaries; they are
34
+ copyable reference compositions, not new monolithic components.
35
+
36
+ ## Missing recurring concepts
37
+
38
+ Kerf UI does not export a command-palette component. Do not invent a package
39
+ command-palette import.
40
+ The typed [application adapter example](./examples/command-palette-adapter.tsx)
41
+ imports `@kerfjs/ui/layout.css`, assigns one `.kui-layout` root and one surface
42
+ inset, and groups its related footer commands with `.kui-control-cluster` while
43
+ the application owns ranking, history, shortcut policy, focus policy,
44
+ availability, actions, and copy. If that concept recurs across products, open
45
+ an upstream component or recipe request.
46
+
47
+ ## Problem-to-component matrix
48
+
49
+ | Interface need | Use when | Do not use when; nearest alternative | Required wiring | Application owns | Import | Recipe |
50
+ | --- | --- | --- | --- | --- | --- | --- |
51
+ | 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) |
52
+ | 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) |
53
+ | Application toolbar — `Toolbar` | Leading identity, optional centered content, and trailing controls form one horizontal app bar. | Do not use it for a page title and one page action; use `PageHeader`. Do not use it as a dialog heading; use `DialogHeader`. | Compose `ToolbarText` and `ToolbarControlGroup` where their contracts fit. | Actions, command availability, responsive relocation, and state. | `@kerfjs/ui/toolbar` | [Toolbar composition](../README.md#component-subpaths) |
54
+ | 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) |
55
+ | Toolbar identity text — `ToolbarText` | A toolbar needs large, default, or compact textual identity. | Do not substitute it for document heading semantics; use `PageHeader` or native headings. | None. | Text and responsive priority. | `@kerfjs/ui/toolbar-text` | [Toolbar composition](../README.md#component-subpaths) |
56
+ | Navigation row — `MenuItem` | A pane or navigation area needs a selectable, disabled, dormant-trailing, or multiline action row. | Do not put a control in `trailing`; use `MenuActionRow` 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/menu-item` | [Pane geometry](../README.md#pane-and-content-geometry) |
57
+ | Navigation row with trailing action — `MenuActionRow` | A full-width row needs a selectable primary action and an independently focusable trailing action. | Use `MenuItem` 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/menu-action-row` | [Accessibility](./accessibility.md#menuactionrow) |
58
+ | Navigation section heading — `MenuHeader` | 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 or dialog title; use `PageHeader` or `DialogHeader`. | 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/menu-header` | [Pane geometry](../README.md#pane-and-content-geometry) |
59
+ | 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, or invent unrelated centered measures. | Use `.kui-pane` and one `.kui-pane__content`; add `.kui-content` and `.kui-content-item` as needed. | Layout hierarchy, reading width, scroll ownership, and responsive relocation. | `@kerfjs/ui/layout.css` | [Pane anatomy](./layout.md#anatomy) |
60
+ | 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 `MenuItem`, or use a chevron on a row that does not disclose content. Use ordinary links for a different navigation contract. | Compose `MenuHeader`, `MenuItem`, and `MenuActionRow` in `.kui-content`; use `MenuHeader` 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) |
61
+ | 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) |
62
+ | 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) |
63
+ | 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) |
64
+ | Page title and action — `PageHeader` | A page needs its primary heading and an optional trailing action. | Do not use it as persistent app chrome; use `Toolbar`. Do not use it inside a modal task; use `DialogHeader`. | Delegate an optional action normally. | Page title, action, and responsive placement. | `@kerfjs/ui/page-header` | [Header ownership](./component-contract.md#extracted-versus-application-specific) |
65
+ | Dialog title and summary — `DialogHeader` | A dialog needs a toolbar-aligned title, optional summary/id and icon, and grouped actions wired to the dialog's ARIA references. | Do not use it as the page's `h1`; use `PageHeader`. It supplies header structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. | Connect title and optional summary ids to the dialog host, pass action children directly, localize `actionsLabel` when the group needs a name, and delegate actions. | Open state, focus lifecycle, dismissal, actions, labels, and copy. | `@kerfjs/ui/dialog-header` | [Header ownership](./component-contract.md#extracted-versus-application-specific) |
66
+ | 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) |
67
+ | 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) |
68
+ | 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) |
69
+ | 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) |
70
+ | 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, control `expanded`, transfer focus after activation, and clear the transient state after focus leaves. | Parsing, suggestions, tokens, query execution, results, announcements, and the transient focus/open signal. | `@kerfjs/ui/token-search-field` | [TokenSearchField contract](./accessibility.md#tokensearchfield) |
71
+ | 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) |
72
+ | 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) |
73
+
74
+ ## Ambiguous choices
75
+
76
+ - `Toolbar` is persistent app chrome; `PageHeader` identifies a page; `DialogHeader` labels a focused modal task.
77
+ - `TabBar` changes tabpanels and supports overflow/reorder; `SegmentedControl` chooses among a few compact views; `Select` handles a longer value list.
78
+ - `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.
79
+ - `ResizableRegion` is an interactive controlled pane. CSS grid is the right answer when columns do not need a user-operable separator.
80
+ - `TokenSearchField` is a structured editor. A native input or `wa-input` is the right answer for ordinary text.
81
+
82
+ ## Correct composition and duplicated-markup trap
83
+
84
+ Correct: let the pane stay unpadded while its children own the shared 8/1/8
85
+ geometry and 44px targets.
86
+
87
+ ```tsx
88
+ <aside class="kui-pane">
89
+ <nav class="kui-pane__content kui-content">
90
+ <section>
91
+ <MenuHeader label="Workspace" />
92
+ <MenuItem action="open" label="Inbox" icon={inboxIcon} />
93
+ <MenuActionRow action="open-file" label="main.ts" trailingAction="file-actions" trailingActionLabel="Actions for main.ts" trailingActionIcon={moreIcon} />
94
+ </section>
95
+ <div class="kui-content-item">Workspace details</div>
96
+ </nav>
97
+ </aside>
98
+ ```
99
+
100
+ Incorrect: duplicating component-like rows and compensating for nested padding
101
+ forks the package anatomy and spacing contract.
102
+
103
+ ```tsx
104
+ <aside class="sidebar padded">
105
+ <h2 class="menu-header-copy">Workspace</h2>
106
+ <button class="menu-row-copy padded">Inbox</button>
107
+ <div class="panel indented-with-negative-margin">Workspace details</div>
108
+ </aside>
109
+ ```
110
+
111
+ ## Web Awesome overlap policy
112
+
113
+ Web Awesome catalog coverage means supported and themed, not preferred. Import
114
+ `@kerfjs/ui/webawesome` for Kerf JSX types, individual component modules for
115
+ registration, and the CSS-only `@kerfjs/ui/webawesome.css` theme.
116
+
117
+ | Web Awesome choice | Kerf decision |
118
+ | --- | --- |
119
+ | `wa-button`, `wa-dropdown`, `wa-dropdown-item` | Use buttons and command menus for actions. Use `MenuItem` for a navigation row and `Select` when the user chooses a value. |
120
+ | `wa-button-group` | Use only for exceptional grouped actions; use `SegmentedControl` for one-of-many selection. |
121
+ | `wa-input`, `wa-tag` | Use for ordinary text and tags; use `TokenSearchField` only when text and ordered filter tokens form one editor. |
122
+ | `wa-select`, `wa-option` | Use `Select`, which owns Kerf spacing, controlled rendering, icon stability, and explicit registration. |
123
+ | `wa-tab-group`, `wa-tab`, `wa-tab-panel` | Use `TabBar`/`AppTab` for application tabs or `SegmentedControl` for compact local views. |
124
+ | `wa-icon` | Use `LucideIcon` in application UI. |
125
+ | `wa-split-panel` | Use `ResizableRegion` for Kerf application panes; retain Split Panel only when its distinct API is required. |
126
+ | `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. |
127
+ | `wa-callout`, `wa-toast`, `wa-toast-item` | Use `StateBanner` for persistent inline app status, `EmptyState` for absent content, and toasts only for transient feedback. |
128
+ | `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. |
129
+ | `wa-tree`, `wa-tree-item`, `wa-animated-image`, `wa-comparison` | Use only for the specialized behavior named by the component. |
130
+ | `wa-zoomable-frame` | Avoid for application UI; keep embedded-media behavior application-owned. |
131
+
132
+ All other entries in the [Web Awesome theme contract](./webawesome-theme.md#coverage)
133
+ remain supported when their native semantic contract matches the product need.
@@ -24,6 +24,44 @@ Unrelated updates must not close controls, move focus, reset a draft, replace a
24
24
 
25
25
  Establish one reading order and one dominant action per decision point. Group first with alignment, spacing, typography, and shared surfaces. A border, fill, badge, or nested card must communicate a real distinction.
26
26
 
27
+ ### Panes share one child-owned geometry
28
+
29
+ Sidebars, main areas, inspectors, and dialogs use the same unpadded pane. Their
30
+ children own margin, border, background, padding, and radius, so a transparent
31
+ surface occupies exactly the same geometry as a visible one. Major content
32
+ groups use 24px vertical separation; the inside of an item and the gap between
33
+ toolbar groups use 8px. Rows and actions keep 44px targets.
34
+
35
+ Use the package composition so the geometry has one owner:
36
+
37
+ ```tsx
38
+ <aside class="kui-pane">
39
+ <nav class="kui-pane__content kui-content">
40
+ <section>
41
+ <MenuHeader label="Workspace" />
42
+ <MenuItem action="open" label="Inbox" icon={inboxIcon} />
43
+ <MenuItem action="open" label="Drafts" />
44
+ </section>
45
+ <div class="kui-content-item">Panel contents</div>
46
+ </nav>
47
+ </aside>
48
+ ```
49
+
50
+ Do not pad the pane and then pad every wrapper. That duplicates the geometry
51
+ and makes transparent borders behave differently from visible ones:
52
+
53
+ ```tsx
54
+ <aside class="pane padded">
55
+ <section class="padded">
56
+ <MenuHeader label="Workspace" />
57
+ <div class="panel padded-again">Panel contents</div>
58
+ </section>
59
+ </aside>
60
+ ```
61
+
62
+ Change the shared item tokens at a real composition boundary when a product
63
+ needs different geometry; do not compensate with one-off negative margins.
64
+
27
65
  ### Prefer directness
28
66
 
29
67
  Use the shortest understandable, recoverable interaction. Avoid modes, dialogs, confirmations, and explicit saves when direct manipulation can safely express the same result. Low ceremony still requires a discoverable affordance, visible focus, and honest feedback.
@@ -0,0 +1,70 @@
1
+ import '@kerfjs/ui/layout.css';
2
+
3
+ import { delegate, mount, signal } from 'kerfjs';
4
+ import { delegateActions } from 'kerfjs/actions';
5
+
6
+ export interface AppCommand {
7
+ id: string;
8
+ label: string;
9
+ keywords?: string[];
10
+ }
11
+
12
+ export interface CommandPaletteCallbacks {
13
+ onClose(): void;
14
+ onRun(command: AppCommand): void;
15
+ }
16
+
17
+ /**
18
+ * Application-local example, not an @kerfjs/ui export. The application owns
19
+ * ranking, history, shortcuts, focus policy, command availability, and copy.
20
+ */
21
+ export function mountCommandPaletteAdapter(
22
+ root: HTMLElement,
23
+ commands: AppCommand[],
24
+ { onClose, onRun }: CommandPaletteCallbacks,
25
+ ): () => void {
26
+ const query = signal('');
27
+ const render = () => {
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>;
40
+ };
41
+
42
+ const stopMount = mount(root, render);
43
+ const stopActions = delegateActions(root, 'click', {
44
+ 'run-command': (_event, element) => {
45
+ const id = (element as HTMLElement).dataset.commandId;
46
+ const command = commands.find((candidate) => candidate.id === id);
47
+ if (command) onRun(command);
48
+ },
49
+ 'clear-query': () => {
50
+ query.value = '';
51
+ root.querySelector<HTMLInputElement>('[data-command-query]')?.focus();
52
+ },
53
+ 'close-palette': onClose,
54
+ });
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
+ });
61
+ let disposed = false;
62
+ return () => {
63
+ if (disposed) return;
64
+ disposed = true;
65
+ stopEscape();
66
+ stopInput();
67
+ stopActions();
68
+ stopMount();
69
+ };
70
+ }
package/docs/layout.md ADDED
@@ -0,0 +1,95 @@
1
+ # Pane and content layout
2
+
3
+ Import `@kerfjs/ui/layout.css` for the shared geometry used by sidebars, main
4
+ areas, inspectors, and dialogs. The vocabulary deliberately describes structure
5
+ rather than location: the same pane can be placed anywhere.
6
+
7
+ ## Anatomy
8
+
9
+ ```tsx
10
+ <aside class="kui-pane">
11
+ <div class="kui-pane__toolbar"><Toolbar label="Workspace" ... /></div>
12
+ <div class="kui-pane__content kui-content">
13
+ <section>...</section>
14
+ <section>...</section>
15
+ </div>
16
+ <footer class="kui-pane__footer"><Toolbar label="Actions" ... /></footer>
17
+ </aside>
18
+ ```
19
+
20
+ `.kui-pane` has no padding. It reserves rows for an optional toolbar, one
21
+ scrolling content area, and an optional footer. A main area or dialog often
22
+ omits the footer; a navigation pane commonly uses all three. Fixed chrome stays
23
+ outside `.kui-pane__content`, which is the pane's scroll owner.
24
+
25
+ `.kui-content` is a vertical stack with a 24px gap between major children.
26
+ Sections may contain adjacent `MenuItem` rows without adding another major gap.
27
+ Ordinary surface-like children use `.kui-content-item` and own their complete
28
+ geometry:
29
+
30
+ - 8px inline margin from the pane edge
31
+ - 1px border, transparent by default
32
+ - 8px internal padding and 8px internal gap
33
+ - `calc(1px + remify(11px))`, or 12px, rounded corners
34
+
35
+ Use `.kui-content-item--pill` for the 22px pill radius expressed as
36
+ `calc(1px + remify(21px))`. Consumers can make a content item visible without
37
+ changing its geometry by setting `--kui-content-item-border` and
38
+ `--kui-content-item-background`.
39
+
40
+ A visible parent surface does not make every child another visible card. The
41
+ composer recipe keeps its form as the single surface and uses three transparent
42
+ content items for the introduction, fields, and footer. A conditional
43
+ `StateBanner` remains visibly distinct because it communicates semantic status.
44
+
45
+ ## Public roles and tokens
46
+
47
+ | Need | Class | Token / default |
48
+ | --- | --- | --- |
49
+ | Unpadded toolbar/content/footer structure | `.kui-pane` | — |
50
+ | Scrolling pane content | `.kui-pane__content` | — |
51
+ | Major vertical rhythm | `.kui-content` | `--kui-layout-content-gap: 24px` |
52
+ | Self-contained child geometry | `.kui-content-item` | 8px margin + 1px border + 8px padding |
53
+ | Pill child | `.kui-content-item--pill` | `--kui-layout-pill-radius: 22px` |
54
+ | Related controls | `.kui-control-cluster` | `--kui-layout-control-gap: 8px` |
55
+ | Inline metadata | `.kui-inline-metadata` | `--kui-layout-metadata-gap: 4px` |
56
+ | Explicit scroll owner outside a pane | `.kui-scroll-owner` | `overflow: auto` |
57
+
58
+ The component layer applies the same contract to `Toolbar`, `MenuHeader`,
59
+ `MenuItem`, `PageHeader`, `DialogHeader`, `StateBanner`, `ValueTable`,
60
+ `ValueTableRow`, tabs, and form controls. A value-table row separator starts at
61
+ the row's 8px content inset, or at 40px when the row contains its 24px leading
62
+ icon and 8px gap, and always ends 8px from the right edge. Each value-table row
63
+ also keeps 8px of root-scaled block padding independently of its semantic inline
64
+ inset. Most interactive rows and toolbar groups are 44px tall.
65
+ Toolbar groups reserve a real 1px outer border around a 42px inner area, even
66
+ when their border and background are transparent.
67
+ `DialogHeader` applies the same rule internally: its icon/title identity is a
68
+ borderless group in a top toolbar, its direct action children are wrapped in a
69
+ contained group, and its optional subtitle is a separate row aligned below the
70
+ title. The icon visual is 24px inside a 34px circular background.
71
+
72
+ ## Ownership rules
73
+
74
+ 1. Do not pad a sidebar, main area, dialog, or `.kui-pane` shell. Children own
75
+ their own margin, border, background, padding, and radius.
76
+ 2. Use 24px gaps for major vertical separation and 8px gaps inside an item or
77
+ between toolbar groups. Do not confuse the two relationships.
78
+ 3. Wrap toolbar content in `ToolbarControlGroup`, including dormant text. A
79
+ transparent group still reserves the same 44px geometry as a visible group.
80
+ 4. Keep one scrolling content owner per pane. Toolbar and footer siblings stay
81
+ fixed while the content scrolls.
82
+ 5. A split item keeps dormant and interactive regions separate. For example,
83
+ `MenuHeader` renders its title/count-or-badge cluster separately from its optional
84
+ logical-end 44px action. The header fills the available inline width and its
85
+ action glyph defaults to 18px; disclosure mode makes the title cluster itself
86
+ the button and supplies the production `DisclosureArrow` unless `actionIcon`
87
+ replaces it.
88
+ `MenuActionRow` uses a noninteractive row root around sibling 44px primary
89
+ and trailing buttons. `MenuItem.trailing` remains dormant content.
90
+ 6. Reading width, column placement, and responsive relocation remain application
91
+ decisions. The shared classes define local geometry, not the whole shell.
92
+
93
+ At narrow widths or 200% zoom, relocate or stack panes before shrinking targets.
94
+ The 8/1/8 item contract and 44px controls remain stable, so screenshots and
95
+ focus-order tests exercise the same model at every viewport.