@kerfjs/ui 5.0.0-beta.77 → 5.0.0-beta.79

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 (128) hide show
  1. package/README.md +9 -4
  2. package/ai/application-ui-diagnostic-ids-v1.json +4 -0
  3. package/ai/compile-time-contracts-v1.json +3 -3
  4. package/ai/component-catalog.json +152 -31
  5. package/ai/component-composition-rules.json +6 -14
  6. package/ai/component-composition.json +123 -29
  7. package/ai/components/README.md +1 -1
  8. package/ai/components/document-baseline.md +1 -1
  9. package/ai/components/nav-stack.md +6 -6
  10. package/ai/components/pane.md +3 -1
  11. package/ai/components/surface-scaffold.md +11 -2
  12. package/ai/components/{tab-scaffold.md → tab-navigator.md} +14 -14
  13. package/ai/components/token-search-field.md +1 -0
  14. package/ai/components/toolbar-control-group.md +2 -0
  15. package/ai/components/toolbar-text.md +2 -0
  16. package/ai/components/toolbar.md +6 -8
  17. package/ai/public-api-signatures-v1.md +68 -140
  18. package/ai/skill.md +15 -4
  19. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  20. package/dist/browser/nav-stack.js +2 -0
  21. package/dist/browser/split-view.js +2 -0
  22. package/dist/browser/tab-navigator.js +4 -0
  23. package/dist/catalog-resources.d.ts +3 -3
  24. package/dist/catalog.d.ts +3 -3
  25. package/dist/catalog.js +3 -3
  26. package/dist/{chunk-WJN3V2H5.js → chunk-2GOCOLQL.js} +18 -4
  27. package/dist/chunk-2GOCOLQL.js.map +1 -0
  28. package/dist/{chunk-QYXKMLHO.js → chunk-3CXRQAYT.js} +2 -2
  29. package/dist/chunk-3CXRQAYT.js.map +1 -0
  30. package/dist/{chunk-G4VKQP7B.js → chunk-3SXWV2IR.js} +5 -1
  31. package/dist/chunk-3SXWV2IR.js.map +1 -0
  32. package/dist/{chunk-VLPE6OPN.js → chunk-62VMNBV2.js} +6 -2
  33. package/dist/chunk-62VMNBV2.js.map +1 -0
  34. package/dist/{chunk-XIZRXI7N.js → chunk-A7DYEERU.js} +13 -1
  35. package/dist/chunk-A7DYEERU.js.map +1 -0
  36. package/dist/{chunk-6VQPLV3O.js → chunk-DSLRNR43.js} +9 -8
  37. package/dist/chunk-DSLRNR43.js.map +1 -0
  38. package/dist/{chunk-AOAW55JN.js → chunk-DXQZ3BEO.js} +4 -1
  39. package/dist/chunk-DXQZ3BEO.js.map +1 -0
  40. package/dist/chunk-IKCUPTR2.js +170 -0
  41. package/dist/chunk-IKCUPTR2.js.map +1 -0
  42. package/dist/{chunk-Z6PE5T7N.js → chunk-JL4BBZP4.js} +4 -3
  43. package/dist/chunk-JL4BBZP4.js.map +1 -0
  44. package/dist/collapsible-panel.js +2 -2
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.js +8 -8
  47. package/dist/nav-stack-B1Qc2e5q.d.ts +101 -0
  48. package/dist/nav-stack.d.ts +11 -93
  49. package/dist/nav-stack.js +5 -1
  50. package/dist/pane.d.ts +5 -2
  51. package/dist/pane.js +1 -1
  52. package/dist/split-view.d.ts +9 -4
  53. package/dist/split-view.js +7 -3
  54. package/dist/split-view.js.map +1 -1
  55. package/dist/styles/components/actions/toolbar/toolbar.css +7 -14
  56. package/dist/styles/components/actions/toolbar-control-group/toolbar-control-group.css +63 -13
  57. package/dist/styles/components/actions/toolbar-text/toolbar-text.css +8 -1
  58. package/dist/styles/components/forms/token-search-field/token-search-field.css +12 -1
  59. package/dist/styles/components/layout/pane/pane.css +18 -1
  60. package/dist/styles/components/navigation/nav-stack/nav-stack.css +6 -12
  61. package/dist/styles/components/navigation/tab-scaffold/tab-scaffold.css +18 -1
  62. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +55 -0
  63. package/dist/styles/nav-stack.css +6 -12
  64. package/dist/styles/pane.css +18 -1
  65. package/dist/styles/surface-scaffold.css +55 -0
  66. package/dist/styles/{tab-scaffold.css → tab-navigator.css} +18 -1
  67. package/dist/styles/token-search-field.css +12 -1
  68. package/dist/styles/toolbar-control-group.css +63 -13
  69. package/dist/styles/toolbar-text.css +8 -1
  70. package/dist/styles/toolbar.css +7 -14
  71. package/dist/surface-scaffold-register.d.ts +4 -0
  72. package/dist/surface-scaffold-register.js +59 -0
  73. package/dist/surface-scaffold-register.js.map +1 -0
  74. package/dist/surface-scaffold.d.ts +10 -2
  75. package/dist/surface-scaffold.js +1 -1
  76. package/dist/{tab-scaffold.d.ts → tab-navigator.d.ts} +12 -10
  77. package/dist/{tab-scaffold.js → tab-navigator.js} +4 -3
  78. package/dist/tab-navigator.js.map +1 -0
  79. package/dist/token-search-field.js +1 -1
  80. package/dist/wire-catalog.js +1 -1
  81. package/dist/wire-scroll-dividers.d.ts +3 -3
  82. package/dist/wire-scroll-dividers.js +1 -1
  83. package/dist/wire-tab-navigator.d.ts +11 -0
  84. package/dist/{wire-tab-scaffold.js → wire-tab-navigator.js} +3 -3
  85. package/dist/wire-tab-navigator.js.map +1 -0
  86. package/dist/wire-token-search-fields.js +1 -1
  87. package/dist/wire-toolbar-visibility.js +1 -1
  88. package/dist/workbench.d.ts +5 -13
  89. package/dist/workbench.js +1 -1
  90. package/docs/app-layouts.md +44 -11
  91. package/docs/component-contract.md +9 -2
  92. package/docs/component-selection.md +17 -15
  93. package/docs/design/templates/token-search-field/empty-dark.svg +1 -1
  94. package/docs/design/templates/token-search-field/empty.svg +1 -1
  95. package/docs/design/templates/token-search-field-dark.svg +1 -1
  96. package/docs/design/templates/token-search-field.svg +1 -1
  97. package/docs/design/templates.md +1 -1
  98. package/docs/examples/application-ui-profile.json +4 -4
  99. package/docs/layout.md +31 -8
  100. package/docs/nav-stack.md +30 -18
  101. package/docs/recipes.md +6 -2
  102. package/docs/split-view.md +7 -4
  103. package/docs/surface-scaffold.md +46 -0
  104. package/docs/{tab-scaffold.md → tab-navigator.md} +83 -11
  105. package/docs/type-contracts.md +3 -3
  106. package/docs/ui-analyzer.md +5 -0
  107. package/docs/ui-doctor.md +33 -0
  108. package/docs/workbench.md +3 -2
  109. package/doctor/component-style-checks.mjs +457 -0
  110. package/doctor/index.mjs +43 -13
  111. package/llms.txt +18 -6
  112. package/package.json +16 -11
  113. package/ux-demo/recipes/list-detail-dialog.tsx +4 -0
  114. package/ux-demo/recipes/navigation-stack.tsx +2 -2
  115. package/dist/browser/tab-scaffold.js +0 -4
  116. package/dist/chunk-6VQPLV3O.js.map +0 -1
  117. package/dist/chunk-AOAW55JN.js.map +0 -1
  118. package/dist/chunk-G4VKQP7B.js.map +0 -1
  119. package/dist/chunk-QYXKMLHO.js.map +0 -1
  120. package/dist/chunk-VLPE6OPN.js.map +0 -1
  121. package/dist/chunk-WJN3V2H5.js.map +0 -1
  122. package/dist/chunk-XIZRXI7N.js.map +0 -1
  123. package/dist/chunk-Z4EPMX25.js +0 -123
  124. package/dist/chunk-Z4EPMX25.js.map +0 -1
  125. package/dist/chunk-Z6PE5T7N.js.map +0 -1
  126. package/dist/tab-scaffold.js.map +0 -1
  127. package/dist/wire-tab-scaffold.d.ts +0 -11
  128. package/dist/wire-tab-scaffold.js.map +0 -1
@@ -171,10 +171,10 @@ beside a `ContentItem` value, and connect its id to the preview group's
171
171
  | Inset bare text — `ListInsetText` | A plain string or inline content with no margin, border, or padding of its own must sit in a `.kui-content` list with its text edge aligned to the bordered items around it. | Do not use it to wrap a component that already owns content-item geometry (`ListItem`, `StateBanner`, a `.kui-content-item`) — that double-insets it. Do not hand-roll the 8px margin / 1px border / 8px padding. | Pass the text or inline `SafeHtml` as children; it supplies the 8px inline margin, 1px transparent border, and 8px padding so the text edge lands at the standard 17px inset. Pass `sides="rl"` to keep that horizontal inset but drop the vertical margin, border, and padding for tight text layout. | The text, copy, and localization. | `@kerfjs/ui/list-inset-text` | [Pane geometry](../README.md#component-subpaths) |
172
172
  | Self-contained content child — `ContentItem` | An ordinary surface-like child of a `Pane` or `.kui-content` stack needs the shared 8px margin / 1px border / 8px padding geometry; pass `frame="framed"` only when it marks a real distinction, `appearance="surface"` or a semantic appearance for coordinated surface colors, and `shape="pill"` for the 22px radius. | Do not wrap a component that already owns content-item geometry (`ListItem`, `ListHeader`, `StateBanner`, `Toolbar`, `ValueTable`) or pad around it — that double-insets. Do not frame an item only to make it look contained. Use `SunkenPanel` for a lowered inset stack and `ListInsetText` for bare text that only needs to align. | Pass the item content as children; choose `appearance` (`transparent` default, `surface`, or a semantic status), `frame`, and `shape` instead of writing CSS. Put safe `data-*` metadata in `rootAttributes`; `ariaLabel` names a distinct region and `focusTarget` makes it a programmatic focus target. | The content, surface or status appearance, and whether it marks a distinction worth a visible frame. | `@kerfjs/ui/content-item` | [Content items](./layout.md#content-items) |
173
173
  | Application column — `Pane` | A sidebar, main area, inspector, or dialog needs shared vertical header/content/footer organization, one scroll owner, and optional edge separators. | 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. | Import `Pane`, use `children` for its homogeneous primary content and the named `SafeHtml` prop slots `header`/`footer` for optional fixed chrome, then opt into only the needed logical-edge separators; add `ContentItem` children 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, responsive relocation, pane visibility state, slot semantics, labels, and separator placement. | `@kerfjs/ui/pane` | [Pane anatomy](./layout.md#anatomy) |
174
- | Scroll dividers — `wireScrollDividers` | Pinned chrome (a `Pane` header or footer, a `NavStack` top chrome or bottom toolbar, a `TabScaffold` bar, the sides of a `TabBar` strip, or app-owned chrome around a scroller) needs a divider only while content is scrolled away beneath it. | Do not keep a static toolbar divider to fake it or write a border onto another component; each component draws its own line from the wiring's state. | Call `wireScrollDividers` once at the app root and retain the disposer; it covers every `Pane`, `NavStack`, `TabScaffold`, and `TabBar` below it. Name app-owned arrangements in `targets` by id; a `Toolbar` or `List` named as chrome draws its facing side. Configure a pane with `chromeDividers` (`scroll` default, `always`, `none`). | Which panes follow the scroll, app-owned targets, and the disposer. | `@kerfjs/ui/wire-scroll-dividers` | [Scroll dividers](./layout.md#scroll-dividers) |
175
- | One-pane navigation — `NavStack` | A compact flow pushes into details while keeping earlier views mounted, or a single-pane screen needs the same stable layout contract. | Do not use it when list and detail should remain visible together; use `SplitView`. Do not use it alone for co-equal top-level destinations; use one stack per `TabScaffold` tab. | Keep the ordered view array controlled, put view-specific bottom chrome on each view (or use the component fallback), import its CSS explicitly, and call `wireNavStack` for back delegation, content-slide/chrome-cross-fade transitions, and focus move/restore. Mark a preferred initial target with `data-nav-focus` when DOM order is insufficient. | View keys/content/titles, push and pop actions, preferred initial focus targets, and per-view or persistent bottom toolbar content. | `@kerfjs/ui/nav-stack` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
174
+ | Scroll dividers — `wireScrollDividers` | Pinned chrome (a `Pane` header or footer, a `NavStack` top chrome or bottom toolbar, a `TabNavigator` bar, the sides of a `TabBar` strip, or app-owned chrome around a scroller) needs a divider only while content is scrolled away beneath it. | Do not keep a static toolbar divider to fake it or write a border onto another component; each component draws its own line from the wiring's state. | Call `wireScrollDividers` once at the app root and retain the disposer; it covers every `Pane`, `NavStack`, `TabNavigator`, and `TabBar` below it. Name app-owned arrangements in `targets` by id; a `Toolbar` or `List` named as chrome draws its facing side. Configure a pane with `chromeDividers` (`scroll` default, `always`, `none`). | Which panes follow the scroll, app-owned targets, and the disposer. | `@kerfjs/ui/wire-scroll-dividers` | [Scroll dividers](./layout.md#scroll-dividers) |
175
+ | One-pane navigation — `NavStack` | A compact flow pushes into details while keeping earlier views mounted, or a single-pane screen needs the same stable layout contract. | Do not use it when list and detail should remain visible together; use `SplitView`. Do not use it alone for co-equal top-level destinations; use one stack per `TabNavigator` tab. | Keep the ordered view array controlled, put view-specific bottom chrome on each view (or use the component fallback), import its CSS explicitly, and call `wireNavStack` for back delegation, content-slide/chrome-cross-fade transitions, and focus move/restore. Mark a preferred initial target with `data-nav-focus` when DOM order is insufficient. | View keys/content/titles, push and pop actions, preferred initial focus targets, and per-view or persistent bottom toolbar content. | `@kerfjs/ui/nav-stack` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
176
176
  | Responsive list-detail — `SplitView` | A primary list and related detail belong together on roomy devices but become a one-pane drill-down on compact devices. | Do not use it for a single linear flow or a multi-panel editor shell. | Provide controlled selection/detail state and device class; opt into resize wiring only when the separator is user adjustable. | Selection, detail content, compact back action, device policy, and optional persisted width. | `@kerfjs/ui/split-view` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
177
- | Compact top-level destinations — `TabScaffold` | A handset or portrait-tablet app has two to five co-equal destinations whose scenes and nested navigation must remain mounted across switches. | Do not use it for document tabs, reorderable work items, or a desktop navigation rail. | Keep the active id controlled, import its CSS explicitly, and call `wireTabScaffold`; commonly place one `NavStack` in each tab scene. | Destination definitions, active id, routing, persistence, and roomy-device replacement. | `@kerfjs/ui/tab-scaffold` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
177
+ | Compact top-level destinations — `TabNavigator` | A handset or portrait-tablet app has two to five co-equal destinations whose scenes and nested navigation must remain mounted across switches. | Do not use it for document tabs, reorderable work items, or a desktop navigation rail. | Keep the active id controlled, import its CSS explicitly, and call `wireTabNavigator`; commonly place one `NavStack` in each tab scene. | Destination definitions, active id, routing, persistence, and roomy-device replacement. | `@kerfjs/ui/tab-navigator` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
178
178
  | Desktop multi-panel workspace — `Workbench` | A desktop-class tool needs a central work area plus independently collapsible navigator, inspector, or console regions. | Do not shrink the full shell onto compact devices or use it for one standalone panel. | Render only the required rails/drawer, keep collapse state controlled, and replace peripheral regions with stacks or overlays below desktop sizes. Opt a panel into drag/keyboard resizing with `resizable` and call `wireWorkbench` with its size signal. | Panel presence/content, collapsed states, sizes, persistence, responsive replacement, and controls. | `@kerfjs/ui/workbench` | [Workbench](./workbench.md) |
179
179
  | Standalone rail or drawer — `CollapsiblePanel` | One left/right rail or bottom drawer needs controlled collapse without adopting the complete `Workbench` shell. | Do not use it for several coordinated workspace panels or retain an empty icon-only rail while collapsed. | Pair it with `CollapsiblePanelToggle`; call `wireSidebar` for focus transfer, Escape/backdrop dismissal, compact overlay trapping, and persistence integration. | Panel content/size, collapsed state, toggle placement, persistence, and compact policy. | `@kerfjs/ui/collapsible-panel` | [App-layout decision matrix](./app-layouts.md#decision-matrix) |
180
180
  | Lowered content surface — `SunkenPanel` | A main work area or nested content group needs one visually lowered surface with a compact inset and vertical stack. | Do not use it merely to add padding, as a replacement for `Pane` anatomy, or around children that already own the same outer surface. | Compose the application-owned content as children, choose the default `rounded` shape or `square` for a flush edge-to-edge area, and pass `ariaLabel` only when the surface is a distinct named region. | Region semantics, accessible name, shape, scroll ownership, child ordering, and content. | `@kerfjs/ui/sunken-panel` | [SunkenPanel](./sunken-panel.md) |
@@ -183,7 +183,7 @@ beside a `ContentItem` value, and connect its id to the preview group's
183
183
  | One application tab — `AppTab` | A controlled app tab needs selection, close, drag, leading/trailing anatomy, compact/segmented/icon-only presentation, label truncation, safe domain metadata, or a product-specific close glyph. | Do not render it alone or use it for a small settings choice; compose in `TabBar`, or use `SegmentedControl`. Keep `closeIcon` noninteractive. | Compose in `TabBar`; let `wireTabBars` manage interaction. Put only domain `data-*` values in `rootAttributes`; use typed appearance props instead of descendant CSS. | Tab identity, order, selection, close policy, content, domain metadata values, and outer placement. | `@kerfjs/ui/app-tab` | [Tabs contract](./accessibility.md#tabs) |
184
184
  | Application tab strip — `TabBar`, `wireTabBars`, `reorderTabs` | Tabs switch page regions and may overflow, close, reorder, fill a segmented inspector strip, sit beside a trailing action, or keep that adjacent action distinct from a far-edge end action. | Do not use it for a compact local view toggle; use `SegmentedControl`. Do not use it for a long choice list; use `Select`. Prefer it over Web Awesome `wa-tab-group`, `wa-tab`, and `wa-tab-panel` for Kerf app tabs. | Call `wireTabBars` once, retain the disposer, and apply `onReorder` synchronously; `reorderTabs` is the default array helper. Combine `trailingPlacement="adjacent"` with `end` for split actions; TabBar owns the shrinking/scrolling tab geometry and keeps both actions visible. Choose typed presentation/allocation props while the parent owns outer placement. | Ordered tabs, selection, panels, routing, closing, persistence, action content, and outer placement. | `@kerfjs/ui/tab-bar` plus `@kerfjs/ui/wire-tab-bars` | [Tabs contract](./accessibility.md#tabs) |
185
185
  | Panel, dialog, or page heading — `Toolbar` + `ToolbarText` | A panel, dialog, or page needs an extra-large title, optional icon, grouped trailing actions, and optional supporting copy. | Do not add a private heading wrapper or bespoke layout. The toolbar supplies structure, not modal behavior; use an application overlay or Web Awesome `wa-dialog` for that behavior. In a narrow peripheral rail or drawer, where xlarge would truncate at the panel's minimum size, use the default `ToolbarText` size as pane identity. | Connect title/supporting-copy ids to the host, group controls, and delegate actions. **For a page or view title set `headingLevel` (usually `1`)** on `ToolbarText`; omit it for a dialog title referenced by `aria-labelledby`. | Open state, focus lifecycle, dismissal, controls, ids, copy, and heading level. | `@kerfjs/ui/toolbar` + `@kerfjs/ui/toolbar-text` | [Toolbar headings](./accessibility.md#toolbar-headings) |
186
- | Dialog or popup geometry — `DialogSurface`, `PopupSurface` | Recurring Web Awesome dialogs need standard sizes/presentations/body/footer insets, or dropdown menus need list-compatible inset ownership. | Do not use these wrappers as generic layout or to replace native dialog/dropdown behavior. Avoid consumer `::part()` overrides and do not stack a dialog body inset around list-owned child geometry. | Wrap one `wa-dialog` or `wa-dropdown`; choose the typed geometry while retaining native labeling, open state, focus, dismissal, and menu semantics. Compose a dialog body as a `List` by default, use `bodyInset="none"` when its children own list geometry, and wrap bare prose in `ListInsetText`. | State, focus lifecycle, dismissal, labels, content, and outer placement. | `@kerfjs/ui/surface-scaffold` | [Surface scaffolds](./surface-scaffold.md) |
186
+ | Dialog or popup geometry — `DialogSurface`, `PopupSurface` | Recurring Web Awesome dialogs need standard sizes/presentations/body/footer insets and typed modal viewport bounds, or dropdown menus need list-compatible inset ownership. | Do not use these wrappers as generic layout or to replace native dialog/dropdown behavior. Avoid consumer `::part()` overrides and do not stack a dialog body inset around list-owned child geometry. | Wrap one `wa-dialog` or `wa-dropdown`; choose the typed geometry while retaining native labeling, open state, focus, dismissal, and menu semantics. Compose a dialog body as a `List` by default, use `bodyInset="none"` when its children own list geometry, and wrap bare prose in `ListInsetText`. | State, focus lifecycle, dismissal, labels, content, and outer placement. | `@kerfjs/ui/surface-scaffold` | [Surface scaffolds](./surface-scaffold.md) |
187
187
  | 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) |
188
188
  | 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. | Pass a label when the spinner conveys status; use size for a named icon step or positive pixel size. | Loading lifecycle and adjacent status copy. | `@kerfjs/ui/loading-spinner` | [Accessibility](./accessibility.md#shared-rules) |
189
189
  | 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`, `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) |
@@ -405,22 +405,24 @@ A `FloatingToolbar` accepts `ToolbarControlGroup` directly as its children in
405
405
  both floating and inline placement. Name the toolbar with `label`; group its
406
406
  controls as you would in a `Toolbar` zone.
407
407
 
408
- A `Toolbar` has three zones — `leading`, `center`, and `trailing`. The direct
409
- zone children are `ToolbarText` (identity/title text) and `ToolbarControlGroup`
410
- (a control or cluster of controls). The `trailing` zone also accepts one
411
- standalone `wa-button` for the application's primary action. It keeps its own
412
- Web Awesome brand, danger, size, and hover chrome, while Toolbar aligns it in
413
- the control band and wraps it with other trailing items when space is tight.
408
+ A `Toolbar` has three zones — `leading`, `center`, and `trailing`. Each accepts
409
+ `KerfUiContent` directly, including a status `Chip`, a native control, or
410
+ application markup. `ToolbarText` is the standard identity/title; use
411
+ `ToolbarControlGroup` when related controls should share its chrome, sizing,
412
+ hover treatment, or overflow behavior. A standalone primary `wa-button` keeps
413
+ its own Web Awesome chrome and fits naturally in the trailing zone. Bare
414
+ controls retain their own styling and accessibility responsibilities; the
415
+ toolbar only positions them within the zone.
414
416
  For a title that opens an editor, set `action` on a direct `ToolbarText`: it
415
417
  renders a native button that keeps its content width when possible and
416
418
  ellipsizes before an adjacent status chip when the leading zone narrows. The
417
419
  application handles the delegated action and replaces it with its own input
418
420
  while editing. Use a separate heading landmark if the view needs one.
419
- Do not drop other bare buttons, inputs, links, or arbitrary markup straight
420
- into a zone; wrap controls in a `ToolbarControlGroup` so they get the shared
421
- toolbar geometry, hover/pressed treatment, and grouping. `SegmentedControl`,
422
- `Select`, a collapsible `TokenSearchField`, and other Web Awesome controls live
423
- **inside** a `ToolbarControlGroup`. For a page, panel, or
421
+ For a search or cluster that needs shared toolbar geometry, place
422
+ `SegmentedControl`, `Select`, or `TokenSearchField` inside a
423
+ `ToolbarControlGroup`. The ToolbarText catalog's actionable-title example
424
+ places its status `Chip` directly beside the title while the search uses a
425
+ group. For a page, panel, or
424
426
  dialog heading, put an extra-large `ToolbarText` directly in the leading zone,
425
427
  optionally preceded by a grouped icon, and group trailing actions. Keep supporting
426
428
  copy below as app-owned content. Size the title to its track: a narrow
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 100" width="560" height="100"><defs><clipPath id="cp0"><rect x="29" y="34" width="16" height="16"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(28,28,30)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><g clip-path="url(#cp0)"><g color="rgb(174, 174, 178)"><clipPath id="svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 100" width="560" height="100"><defs><clipPath id="cp0"><rect x="29" y="34" width="16" height="16"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(28,28,30)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><g data-domotion-pseudo-source="blink-pseudo-fragment-v1" data-domotion-pseudo="::before" data-domotion-pseudo-owner="source-fragments" role="img" aria-label="Search tickets"><g/><g transform="translate(53 46.547)"><text x="0" y="0" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(174, 174, 178)">Search tickets</text></g></g><g clip-path="url(#cp0)"><g color="rgb(174, 174, 178)"><clipPath id="svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 100" width="560" height="100"><defs><clipPath id="cp0"><rect x="29" y="34" width="16" height="16"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(255,255,255)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><g clip-path="url(#cp0)"><g color="rgb(110, 110, 115)"><clipPath id="svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 560 100" width="560" height="100"><defs><clipPath id="cp0"><rect x="29" y="34" width="16" height="16"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(255,255,255)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><g data-domotion-pseudo-source="blink-pseudo-fragment-v1" data-domotion-pseudo="::before" data-domotion-pseudo-owner="source-fragments" role="img" aria-label="Search tickets"><g/><g transform="translate(53 46.547)"><text x="0" y="0" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(110, 110, 115)">Search tickets</text></g></g><g clip-path="url(#cp0)"><g color="rgb(110, 110, 115)"><clipPath id="svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 592 292" width="592" height="292"><defs><clipPath id="cp0"><rect x="16" y="38" width="560" height="100"/></clipPath><clipPath id="cp1"><rect x="16" y="176" width="560" height="100"/></clipPath><clipPath id="ct2"><rect x="16" y="16" width="560" height="22"/></clipPath><clipPath id="irc3"><rect x="16" y="38" width="560" height="100" rx="0"/></clipPath><clipPath id="ct5"><rect x="16" y="154" width="560" height="22"/></clipPath><clipPath id="irc6"><rect x="16" y="176" width="560" height="100" rx="0"/></clipPath></defs><rect x="0" y="0" width="592" height="292" rx="0" fill="rgb(28,28,30)"/><text x="16" y="28" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" font-weight="600" fill="rgb(174,174,178)">Text with atomic tokens</text><g clip-path="url(#cp0)"><g clip-path="url(#irc3)"><clipPath id="svgimg4vclip"><rect x="16" y="38" width="560" height="100"/></clipPath><g clip-path="url(#svgimg4vclip)"><g transform="translate(16 38)"><defs><clipPath id="svgimg4cp0"><rect x="29" y="34" width="16" height="16"/></clipPath><clipPath id="svgimg4ct2"><rect x="53" y="32.5" width="66.8" height="17"/></clipPath><clipPath id="svgimg4ct3"><rect x="129" y="33.9" width="41.1" height="18"/></clipPath><clipPath id="svgimg4cp4"><rect x="175" y="37" width="11.3" height="11.5"/></clipPath><clipPath id="svgimg4ct6"><rect x="191.7" y="32.5" width="50.7" height="17"/></clipPath><clipPath id="svgimg4cp7"><rect x="512" y="35" width="14" height="14"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(28,28,30)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><g clip-path="url(#svgimg4cp0)"><g color="rgb(174, 174, 178)"><clipPath id="svgimg4svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgimg4svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g><text x="53" y="46.55" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(245,245,247)">payments </text><rect x="121.8" y="33.9" width="67.9" height="18" rx="9" fill="rgb(40,57,66)"/><text x="129" y="46.86" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" fill="rgb(138,203,255)">is:open</text><g clip-path="url(#svgimg4cp4)"><g color="rgb(138, 203, 255)"><clipPath id="svgimg4svgic5"><rect x="175.1" y="37.3" width="11.2" height="11.2"/></clipPath><g clip-path="url(#svgimg4svgic5)"><g transform="translate(175.094 37.266)scale(.46615)"><path d="M18 6 6 18" 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="m6 6 12 12" 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"/></g></g></g></g><text x="191.69" y="46.55" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(245,245,247)">is:open </text><g clip-path="url(#svgimg4cp7)"><g color="rgb(174, 174, 178)"><clipPath id="svgimg4svgic8"><rect x="512.2" y="35.2" width="13.6" height="13.6"/></clipPath><g clip-path="url(#svgimg4svgic8)"><g transform="translate(512.203 35.203)scale(.5664)"><path d="M18 6 6 18" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="m6 6 12 12" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></g></g></g></g><text x="16" y="166" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" font-weight="600" fill="rgb(174,174,178)">Empty, expanded</text><g clip-path="url(#cp1)"><g clip-path="url(#irc6)"><clipPath id="svgimg7vclip"><rect x="16" y="176" width="560" height="100"/></clipPath><g clip-path="url(#svgimg7vclip)"><g transform="translate(16 176)"><defs><clipPath id="svgimg7cp0"><rect x="29" y="34" width="16" height="16"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(28,28,30)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><g clip-path="url(#svgimg7cp0)"><g color="rgb(174, 174, 178)"><clipPath id="svgimg7svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgimg7svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></g></g></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 592 292" width="592" height="292"><defs><clipPath id="cp0"><rect x="16" y="38" width="560" height="100"/></clipPath><clipPath id="cp1"><rect x="16" y="176" width="560" height="100"/></clipPath><clipPath id="ct2"><rect x="16" y="16" width="560" height="22"/></clipPath><clipPath id="irc3"><rect x="16" y="38" width="560" height="100" rx="0"/></clipPath><clipPath id="ct5"><rect x="16" y="154" width="560" height="22"/></clipPath><clipPath id="irc6"><rect x="16" y="176" width="560" height="100" rx="0"/></clipPath></defs><rect x="0" y="0" width="592" height="292" rx="0" fill="rgb(28,28,30)"/><text x="16" y="28" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" font-weight="600" fill="rgb(174,174,178)">Text with atomic tokens</text><g clip-path="url(#cp0)"><g clip-path="url(#irc3)"><clipPath id="svgimg4vclip"><rect x="16" y="38" width="560" height="100"/></clipPath><g clip-path="url(#svgimg4vclip)"><g transform="translate(16 38)"><defs><clipPath id="svgimg4cp0"><rect x="29" y="34" width="16" height="16"/></clipPath><clipPath id="svgimg4ct2"><rect x="53" y="32.5" width="66.8" height="17"/></clipPath><clipPath id="svgimg4ct3"><rect x="129" y="33.9" width="41.1" height="18"/></clipPath><clipPath id="svgimg4cp4"><rect x="175" y="37" width="11.3" height="11.5"/></clipPath><clipPath id="svgimg4ct6"><rect x="191.7" y="32.5" width="50.7" height="17"/></clipPath><clipPath id="svgimg4cp7"><rect x="512" y="35" width="14" height="14"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(28,28,30)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><g clip-path="url(#svgimg4cp0)"><g color="rgb(174, 174, 178)"><clipPath id="svgimg4svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgimg4svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g><text x="53" y="46.55" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(245,245,247)">payments </text><rect x="121.8" y="33.9" width="67.9" height="18" rx="9" fill="rgb(40,57,66)"/><text x="129" y="46.86" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" fill="rgb(138,203,255)">is:open</text><g clip-path="url(#svgimg4cp4)"><g color="rgb(138, 203, 255)"><clipPath id="svgimg4svgic5"><rect x="175.1" y="37.3" width="11.2" height="11.2"/></clipPath><g clip-path="url(#svgimg4svgic5)"><g transform="translate(175.094 37.266)scale(.46615)"><path d="M18 6 6 18" 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="m6 6 12 12" 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"/></g></g></g></g><text x="191.69" y="46.55" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(245,245,247)">is:open </text><g clip-path="url(#svgimg4cp7)"><g color="rgb(174, 174, 178)"><clipPath id="svgimg4svgic8"><rect x="512.2" y="35.2" width="13.6" height="13.6"/></clipPath><g clip-path="url(#svgimg4svgic8)"><g transform="translate(512.203 35.203)scale(.5664)"><path d="M18 6 6 18" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="m6 6 12 12" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></g></g></g></g><text x="16" y="166" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" font-weight="600" fill="rgb(174,174,178)">Empty, expanded</text><g clip-path="url(#cp1)"><g clip-path="url(#irc6)"><clipPath id="svgimg7vclip"><rect x="16" y="176" width="560" height="100"/></clipPath><g clip-path="url(#svgimg7vclip)"><g transform="translate(16 176)"><defs><clipPath id="svgimg7cp0"><rect x="29" y="34" width="16" height="16"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(28,28,30)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(253,253,255,0.196)" stroke-width="1"/><g data-domotion-pseudo-source="blink-pseudo-fragment-v1" data-domotion-pseudo="::before" data-domotion-pseudo-owner="source-fragments" role="img" aria-label="Search tickets"><g/><g transform="translate(53 46.547)"><text x="0" y="0" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(174, 174, 178)">Search tickets</text></g></g><g clip-path="url(#svgimg7cp0)"><g color="rgb(174, 174, 178)"><clipPath id="svgimg7svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgimg7svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(174, 174, 178)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></g></g></g></g></svg>
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 592 292" width="592" height="292"><defs><clipPath id="cp0"><rect x="16" y="38" width="560" height="100"/></clipPath><clipPath id="cp1"><rect x="16" y="176" width="560" height="100"/></clipPath><clipPath id="ct2"><rect x="16" y="16" width="560" height="22"/></clipPath><clipPath id="irc3"><rect x="16" y="38" width="560" height="100" rx="0"/></clipPath><clipPath id="ct5"><rect x="16" y="154" width="560" height="22"/></clipPath><clipPath id="irc6"><rect x="16" y="176" width="560" height="100" rx="0"/></clipPath></defs><rect x="0" y="0" width="592" height="292" rx="0" fill="rgb(255,255,255)"/><text x="16" y="28" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" font-weight="600" fill="rgb(110,110,115)">Text with atomic tokens</text><g clip-path="url(#cp0)"><g clip-path="url(#irc3)"><clipPath id="svgimg4vclip"><rect x="16" y="38" width="560" height="100"/></clipPath><g clip-path="url(#svgimg4vclip)"><g transform="translate(16 38)"><defs><clipPath id="svgimg4cp0"><rect x="29" y="34" width="16" height="16"/></clipPath><clipPath id="svgimg4ct2"><rect x="53" y="32.5" width="66.8" height="17"/></clipPath><clipPath id="svgimg4ct3"><rect x="129" y="33.9" width="41.1" height="18"/></clipPath><clipPath id="svgimg4cp4"><rect x="175" y="37" width="11.3" height="11.5"/></clipPath><clipPath id="svgimg4ct6"><rect x="191.7" y="32.5" width="50.7" height="17"/></clipPath><clipPath id="svgimg4cp7"><rect x="512" y="35" width="14" height="14"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(255,255,255)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><g clip-path="url(#svgimg4cp0)"><g color="rgb(110, 110, 115)"><clipPath id="svgimg4svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgimg4svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g><text x="53" y="46.55" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(29,29,31)">payments </text><rect x="121.8" y="33.9" width="67.9" height="18" rx="9" fill="rgb(214,233,249)"/><text x="129" y="46.86" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" fill="rgb(12,98,243)">is:open</text><g clip-path="url(#svgimg4cp4)"><g color="rgb(12, 98, 243)"><clipPath id="svgimg4svgic5"><rect x="175.1" y="37.3" width="11.2" height="11.2"/></clipPath><g clip-path="url(#svgimg4svgic5)"><g transform="translate(175.094 37.266)scale(.46615)"><path d="M18 6 6 18" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(12, 98, 243)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="m6 6 12 12" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(12, 98, 243)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g><text x="191.69" y="46.55" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(29,29,31)">is:open </text><g clip-path="url(#svgimg4cp7)"><g color="rgb(110, 110, 115)"><clipPath id="svgimg4svgic8"><rect x="512.2" y="35.2" width="13.6" height="13.6"/></clipPath><g clip-path="url(#svgimg4svgic8)"><g transform="translate(512.203 35.203)scale(.5664)"><path d="M18 6 6 18" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="m6 6 12 12" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></g></g></g></g><text x="16" y="166" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" font-weight="600" fill="rgb(110,110,115)">Empty, expanded</text><g clip-path="url(#cp1)"><g clip-path="url(#irc6)"><clipPath id="svgimg7vclip"><rect x="16" y="176" width="560" height="100"/></clipPath><g clip-path="url(#svgimg7vclip)"><g transform="translate(16 176)"><defs><clipPath id="svgimg7cp0"><rect x="29" y="34" width="16" height="16"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(255,255,255)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><g clip-path="url(#svgimg7cp0)"><g color="rgb(110, 110, 115)"><clipPath id="svgimg7svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgimg7svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></g></g></g></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 592 292" width="592" height="292"><defs><clipPath id="cp0"><rect x="16" y="38" width="560" height="100"/></clipPath><clipPath id="cp1"><rect x="16" y="176" width="560" height="100"/></clipPath><clipPath id="ct2"><rect x="16" y="16" width="560" height="22"/></clipPath><clipPath id="irc3"><rect x="16" y="38" width="560" height="100" rx="0"/></clipPath><clipPath id="ct5"><rect x="16" y="154" width="560" height="22"/></clipPath><clipPath id="irc6"><rect x="16" y="176" width="560" height="100" rx="0"/></clipPath></defs><rect x="0" y="0" width="592" height="292" rx="0" fill="rgb(255,255,255)"/><text x="16" y="28" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" font-weight="600" fill="rgb(110,110,115)">Text with atomic tokens</text><g clip-path="url(#cp0)"><g clip-path="url(#irc3)"><clipPath id="svgimg4vclip"><rect x="16" y="38" width="560" height="100"/></clipPath><g clip-path="url(#svgimg4vclip)"><g transform="translate(16 38)"><defs><clipPath id="svgimg4cp0"><rect x="29" y="34" width="16" height="16"/></clipPath><clipPath id="svgimg4ct2"><rect x="53" y="32.5" width="66.8" height="17"/></clipPath><clipPath id="svgimg4ct3"><rect x="129" y="33.9" width="41.1" height="18"/></clipPath><clipPath id="svgimg4cp4"><rect x="175" y="37" width="11.3" height="11.5"/></clipPath><clipPath id="svgimg4ct6"><rect x="191.7" y="32.5" width="50.7" height="17"/></clipPath><clipPath id="svgimg4cp7"><rect x="512" y="35" width="14" height="14"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(255,255,255)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><g clip-path="url(#svgimg4cp0)"><g color="rgb(110, 110, 115)"><clipPath id="svgimg4svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgimg4svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g><text x="53" y="46.55" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(29,29,31)">payments </text><rect x="121.8" y="33.9" width="67.9" height="18" rx="9" fill="rgb(214,233,249)"/><text x="129" y="46.86" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" fill="rgb(12,98,243)">is:open</text><g clip-path="url(#svgimg4cp4)"><g color="rgb(12, 98, 243)"><clipPath id="svgimg4svgic5"><rect x="175.1" y="37.3" width="11.2" height="11.2"/></clipPath><g clip-path="url(#svgimg4svgic5)"><g transform="translate(175.094 37.266)scale(.46615)"><path d="M18 6 6 18" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(12, 98, 243)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="m6 6 12 12" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(12, 98, 243)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g><text x="191.69" y="46.55" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(29,29,31)">is:open </text><g clip-path="url(#svgimg4cp7)"><g color="rgb(110, 110, 115)"><clipPath id="svgimg4svgic8"><rect x="512.2" y="35.2" width="13.6" height="13.6"/></clipPath><g clip-path="url(#svgimg4svgic8)"><g transform="translate(512.203 35.203)scale(.5664)"><path d="M18 6 6 18" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><path d="m6 6 12 12" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></g></g></g></g><text x="16" y="166" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="12" font-weight="600" fill="rgb(110,110,115)">Empty, expanded</text><g clip-path="url(#cp1)"><g clip-path="url(#irc6)"><clipPath id="svgimg7vclip"><rect x="16" y="176" width="560" height="100"/></clipPath><g clip-path="url(#svgimg7vclip)"><g transform="translate(16 176)"><defs><clipPath id="svgimg7cp0"><rect x="29" y="34" width="16" height="16"/></clipPath></defs><rect x="20" y="20" width="520" height="44" rx="22" fill="rgb(255,255,255)"/><rect x="20.5" y="20.5" width="519" height="43" rx="21.5" fill="none" stroke="rgba(0,0,28,0.18)" stroke-width="1"/><g data-domotion-pseudo-source="blink-pseudo-fragment-v1" data-domotion-pseudo="::before" data-domotion-pseudo-owner="source-fragments" role="img" aria-label="Search tickets"><g/><g transform="translate(53 46.547)"><text x="0" y="0" font-family="system-ui, &quot;-apple-system&quot;, sans-serif" font-size="14" fill="rgb(110, 110, 115)">Search tickets</text></g></g><g clip-path="url(#svgimg7cp0)"><g color="rgb(110, 110, 115)"><clipPath id="svgimg7svgic1"><rect x="29" y="34" width="16" height="16"/></clipPath><g clip-path="url(#svgimg7svgic1)"><g transform="translate(29 34)scale(.66667)"><path d="m21 21-4.3-4.3" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/><circle cx="11" cy="11" r="8" style="marker:none;vector-effect:none;color-interpolation:srgb" fill="none" stroke="rgb(110, 110, 115)" stroke-width="2px" stroke-dasharray="none" stroke-linecap="round" stroke-linejoin="round" stroke-opacity="1" fill-opacity="1" opacity="1"/></g></g></g></g></g></g></g></g></svg>
@@ -19,7 +19,7 @@ picture): `Select` renders a Web Awesome custom element that needs its runtime
19
19
  registered, so it has no faithful static capture; `LucideIcon` and
20
20
  `DisclosureArrow` are decorative primitives best seen inside the components that
21
21
  use them; `ResizableRegion` and the whole-screen layouts (`NavStack`,
22
- `SplitView`, `Workbench`, `TabScaffold`) and the `Catalog` shell are
22
+ `SplitView`, `Workbench`, `TabNavigator`) and the `Catalog` shell are
23
23
  interaction-/viewport-driven screens rather than component cards — the live UX
24
24
  catalog and the app-layouts docs cover those.
25
25
 
@@ -51,10 +51,10 @@
51
51
  },
52
52
  "exceptions": [
53
53
  {
54
- "id": "legacy-report-grid",
55
- "rules": ["KUI-C101"],
56
- "target": "src/features/legacy-report/report-grid.tsx",
57
- "rationale": "The embedded report engine owns this isolated toolbar-compatible control row."
54
+ "id": "legacy-list-header",
55
+ "rules": ["KUI-C103"],
56
+ "target": "src/features/legacy-report/report-list-header.tsx",
57
+ "rationale": "The legacy report adapter owns disclosure content and its expanded state."
58
58
  }
59
59
  ]
60
60
  }
package/docs/layout.md CHANGED
@@ -36,6 +36,26 @@ size) tall, and below that lets the whole pane scroll as one column, header,
36
36
  content, and footer together, so the content keeps its natural height. The
37
37
  default `fixed` always pins them.
38
38
 
39
+ ### Deep content inset
40
+
41
+ Set `deepInset` on a `Pane` to add one 8px inline gutter to its scrolling
42
+ content. A `ContentItem` or `ListItem` that normally sits 8px from the pane
43
+ edge then sits 16px from it. The default is `false`. This extra gutter adds
44
+ to any device safe-area inset, while the pane's header, footer, background,
45
+ and separators still reach their usual edges.
46
+
47
+ ```tsx
48
+ <Pane deepInset>
49
+ <ContentItem>Project Atlas</ContentItem>
50
+ </Pane>
51
+ ```
52
+
53
+ The pane leaves `--kui-layout-inline-margin` at its normal 8px value. Nested
54
+ `Row`, `List`, and `Grid` therefore keep their usual item spacing; they do
55
+ not inherit a doubled margin. `PaneConfig.deepInset` forwards through
56
+ Workbench `mainPane`, panel `pane`, CollapsiblePanel `pane`, and a
57
+ `NavStackView.pane`.
58
+
39
59
  ### Lowered work surfaces
40
60
 
41
61
  Set `appearance="sunken"` when the work area itself should use the lowered
@@ -60,7 +80,7 @@ on the Pane; it defaults to `--kui-color-surface`.
60
80
  `PaneConfig.appearance` forwards through Workbench `mainPane`, panel `pane`,
61
81
  and CollapsiblePanel `pane`. Workbench also applies that choice to its main or
62
82
  panel region when it renders no Pane chrome. Set `appearance: "sunken"` on a
63
- `NavStackView` or `TabScaffoldTab` when its view or scene owns scrolling.
83
+ `NavStackView` or `TabNavigatorTab` when its view or scene owns scrolling.
64
84
  For a `SplitView`, put a `Pane` in the list or detail region and set its
65
85
  appearance; SplitView only arranges the regions and does not own their scroll.
66
86
  Choose the innermost owner when composing layouts.
@@ -102,7 +122,7 @@ and the footer's top line, whatever the chrome holds — a `Toolbar`, a
102
122
  whose tabs are scrolled out of view). That includes the panes a `Workbench`,
103
123
  `CollapsiblePanel`, or `Catalog` renders; `wireCatalog` installs it for its
104
124
  own root. It also covers every `NavStack` (the top chrome's bottom line and the
105
- bottom toolbar's top line, around the active view) and every `TabScaffold`
125
+ bottom toolbar's top line, around the active view) and every `TabNavigator`
106
126
  (the bar's top line, over the active scene). The layout keys on whichever
107
127
  element actually scrolls there: the view or scene itself, or, through a sole
108
128
  child with no chrome of its own on that edge, a `Pane`'s content or a nested
@@ -120,7 +140,7 @@ for a root already has scroll dividers there and does not need another call.
120
140
  The wiring only reports state; every component draws its own line. It writes
121
141
  `data-scroll-overflow` (the edges with content hidden beyond them, in canonical
122
142
  `t`/`r`/`b`/`l` order) on each scroller and `data-scroll-divider` (the sides to
123
- draw) on each piece of chrome, and the Pane, NavStack, TabScaffold, TabBar,
143
+ draw) on each piece of chrome, and the Pane, NavStack, TabNavigator, TabBar,
124
144
  Toolbar, and List stylesheets draw from those. The line is an inset shadow or a pseudo-element
125
145
  inside the component, so no state ever moves the chrome, the content, or a tab.
126
146
  Never write a border onto a component to show one.
@@ -130,7 +150,7 @@ the scroll state and draws nothing until the wiring runs; `always` (the sunken
130
150
  pane default) keeps both lines; `none` drops them. Explicit values override
131
151
  either default. Layouts forward it through `PaneConfig`
132
152
  (`mainPane.chromeDividers`, a panel's `pane.chromeDividers`). A `NavStack` and a
133
- `TabScaffold` take the same `chromeDividers` for their own chrome (the stack's
153
+ `TabNavigator` take the same `chromeDividers` for their own chrome (the stack's
134
154
  top chrome and bottom toolbar, the scaffold's bar), and a `SplitView` forwards
135
155
  it to its compact stack through `compactStack`.
136
156
 
@@ -295,10 +315,13 @@ inset. Most interactive rows and toolbar groups are 44px tall.
295
315
  Toolbar groups reserve a real 1px outer border around a 42px inner area, even
296
316
  when their border and background are transparent.
297
317
  A `Toolbar` lays its zones out against one 44px control band at its top: an
298
- item no taller than the band is centered in it by its own size, and a taller
299
- item (a wrapped title, a second trailing row) starts at the band's top and
300
- grows down, so it never moves the other controls. A wrapped `ToolbarText`
301
- centers its first line in the band.
318
+ ordinary `ToolbarText`, `ToolbarControlGroup`, or standalone trailing
319
+ `wa-button` no taller than the band is centered in it by its own size, and a
320
+ taller one (a wrapped title, a second trailing row) starts at the band's top
321
+ and grows down, so it never moves the other controls. A wrapped `ToolbarText`
322
+ centers its first line in the band. Other direct zone content centers against
323
+ the zone's full height by default, including when a sibling makes that zone
324
+ taller than the control band.
302
325
  The leading zone clips a shrinking title but reserves 4px of paint overflow
303
326
  for the focus outline of a control at its edge. Focus rings in all three zones
304
327
  remain visible at narrow and wide widths.
package/docs/nav-stack.md CHANGED
@@ -15,7 +15,7 @@ import { wireNavStack } from "@kerfjs/ui/wire-nav-stack";
15
15
  In a browser bundler that honors the `browser` export condition (Vite, esbuild, and
16
16
  webpack do by default), the import above also
17
17
  loads NavStack's stylesheet and those of the components it renders internally
18
- (`Toolbar`, `ToolbarControlGroup`, `ToolbarText`, `LucideIcon`). Without that condition, import the manual stylesheets instead:
18
+ (`Toolbar`, `ToolbarControlGroup`, `ToolbarText`, `Pane`, `List`, `LucideIcon`). Without that condition, import the manual stylesheets instead:
19
19
  `@kerfjs/ui/nav-stack.css` plus those components' CSS, or `@kerfjs/ui/styles.css`.
20
20
 
21
21
  ## State lives in the app
@@ -28,9 +28,12 @@ stack as a `signal<NavStackView[]>`, `NavStack({ views })` renders it, and
28
28
  const views = signal<NavStackView[]>([
29
29
  {
30
30
  key: "inbox",
31
- title: "Inbox",
31
+ toolbar: { title: "Inbox" },
32
32
  content: <InboxView />,
33
- bottomToolbar: <InboxStatus />,
33
+ bottomToolbar: {
34
+ label: "Inbox status",
35
+ leading: <InboxStatus />,
36
+ },
34
37
  },
35
38
  ]);
36
39
 
@@ -47,35 +50,39 @@ const dispose = wireNavStack(root, {
47
50
  // push / pop by editing the signal:
48
51
  views.value = [
49
52
  ...views.value,
50
- { key: id, title: "Message", content: <MessageView id={id} /> },
53
+ { key: id, toolbar: { title: "Message" }, content: <MessageView id={id} /> },
51
54
  ];
52
55
  ```
53
56
 
54
57
  `NavStack` renders every entry stacked, the last one active and the rest kept
55
58
  mounted (so their DOM state survives) but hidden. Each entry carries a
56
- `key` (stable identity), `content`, an optional `title`, optional per-view
57
- `leading`, `center`, and trailing `toolbar` groups for the top toolbar, and an
58
- optional per-view `bottomToolbar`. The component-level
59
+ `key` (stable identity), `content`, an optional `toolbar` object with `title`,
60
+ `leading`, `center`, and `trailing` groups, and an optional structured
61
+ `bottomToolbar`. The component-level structured
59
62
  `bottomToolbar` remains a persistent fallback for views that do not provide one.
60
- Each view can also provide `header`, pinned below its top toolbar and above
61
- that view's single scroll area. A fixed heading, notice, or section selector
62
- can therefore change with the active screen without adding another scroller.
63
+ Each view can also provide `header`, `footer`, `headerList`, `footerList`, and
64
+ `pane` configuration, as a `WorkbenchStaticPanel` does. With any of these,
65
+ the view renders a `Pane`: its header and footer stay pinned around the view's
66
+ scrolling content, and slide with that view during navigation. The header and
67
+ footer list configuration forwards to their `List`s; `pane` forwards
68
+ `contentElement`, `contentLabel`, `separators`, `safeAreaEdges`,
69
+ `chromeDividers`, and `appearance` to the Pane. Without those fields, the view
70
+ keeps its simple direct scroll area. Use `pane: { appearance: "sunken" }` for a
71
+ lowered scrolling work surface.
63
72
  The back control appears automatically once the stack has more than one entry;
64
73
  `wireNavStack`'s `onBack` is where the app pops its own signal.
65
74
 
66
- Set a view's `appearance: "sunken"` to paint its scroll viewport with the
67
- shared lowered-surface color, including space after short content. When the
68
- view holds a `Pane` as its only child, set the appearance on the Pane instead
69
- if its content owns scrolling. See [Lowered work surfaces](layout.md#lowered-work-surfaces).
75
+ See [Lowered work surfaces](layout.md#lowered-work-surfaces) for nested Pane
76
+ and layout content.
70
77
 
71
78
  ## The top toolbar
72
79
 
73
80
  The top chrome is a real `Toolbar`, so it follows the same zone,
74
81
  group, and responsive rules as every other toolbar. Its leading zone holds the
75
82
  back control (a borderless `ToolbarControlGroup`, shown once the stack has
76
- depth), then the active view's `leading` groups, then the view's `title` as a
77
- `ToolbarText`. The view's `center` fills the center zone and its `toolbar`
78
- fills the trailing zone. Give each zone `ToolbarControlGroup`s or `ToolbarText`,
83
+ depth), then `toolbar.leading`, then `toolbar.title` as a `ToolbarText`.
84
+ `toolbar.center` fills the center zone and `toolbar.trailing` fills the
85
+ trailing zone. Give each zone `ToolbarControlGroup`s or `ToolbarText`,
79
86
  as in any toolbar.
80
87
 
81
88
  Configure the toolbar with `toolbarConfig` instead of styling it:
@@ -95,7 +102,12 @@ Configure the toolbar with `toolbarConfig` instead of styling it:
95
102
  />
96
103
  ```
97
104
 
98
- - `toolbarConfig` forwards the `Toolbar` configuration (`dividerSides`,
105
+ - Each view's `toolbar` can forward the `Toolbar` configuration (`dividerSides`,
106
+ `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`) and its `label`,
107
+ `titleSize`, and `headingLevel`. A view value overrides the corresponding
108
+ `toolbarConfig` default. Its `bottomToolbar` forwards the same `Toolbar`
109
+ configuration and has a required accessible `label`.
110
+ - `toolbarConfig` supplies stack-wide defaults (`dividerSides`,
99
111
  `centerAlign`, `responsive`, `responsiveAt`, `safeAreaEdges`) and adds
100
112
  `label` (the toolbar's accessible name), `titleSize` (the title's
101
113
  `ToolbarText` size, default `large`), and `headingLevel` (expose the title as
package/docs/recipes.md CHANGED
@@ -88,7 +88,11 @@ its label still names the modal — and the close button uses
88
88
  `data-dialog="close"`. Web Awesome keeps modal focus and Escape, and the thin
89
89
  recipe adapter restores the invoking control after the hide event.
90
90
  `DialogSurface` chooses the large modal with no body inset, because the panes
91
- own their geometry. On compact devices the dialog becomes a full-screen sheet
91
+ own their geometry. Its explicit 16px `viewportGutter` and `maxHeight="viewport"`
92
+ bound the roomy modal to the dynamic viewport; the compact fullscreen
93
+ presentation retains its existing geometry. Application render state chooses
94
+ the presentation and can likewise choose gutter/cap values for a compact modal.
95
+ On compact devices the dialog becomes a full-screen sheet
92
96
  with its own labeled header and footer actions, and `SplitView` becomes a
93
97
  `NavStack` drill-down whose back control the application pops. The
94
98
  application owns open state, selection, the pushed detail, dismissal policy,
@@ -146,7 +150,7 @@ pushes/pops it, `NavStack` renders it, and `wireNavStack` slides the content and
146
150
  settles the chrome (reduced motion collapses the slide to instant). See the
147
151
  layout guide
148
152
  [`app-layouts.md`](app-layouts.md) for choosing among `NavStack`, `SplitView`,
149
- `Workbench`, and `TabScaffold`.
153
+ `Workbench`, and `TabNavigator`.
150
154
 
151
155
  ## Loading inspector
152
156
 
@@ -86,15 +86,18 @@ resizable={{
86
86
 
87
87
  `compactStack` configures the compact `NavStack`: `toolbarConfig`, `backIcon`,
88
88
  `backText`, `hideToolbar`, the persistent `bottomToolbar`, and `chromeDividers`
89
- forward to the stack, and `list` / `detail` give each view its own top-toolbar groups
90
- (`leading`, `center`, trailing `toolbar`) and `bottomToolbar`:
89
+ forward to the stack, and `list` / `detail` give each view its own structured
90
+ top `toolbar` (`leading`, `center`, `trailing`) and `bottomToolbar`:
91
91
 
92
92
  ```tsx
93
93
  compactStack={{
94
94
  backText: "Threads",
95
95
  toolbarConfig: { headingLevel: 1 },
96
- list: { toolbar: <ComposeGroup /> },
97
- detail: { toolbar: <ReplyGroup />, bottomToolbar: <MessageActions /> },
96
+ list: { toolbar: { trailing: <ComposeGroup /> } },
97
+ detail: {
98
+ toolbar: { trailing: <ReplyGroup /> },
99
+ bottomToolbar: { label: "Message actions", leading: <MessageActions /> },
100
+ },
98
101
  }}
99
102
  ```
100
103
 
@@ -12,6 +12,50 @@ Wrap one `wa-dialog` with `DialogSurface`. Choose `size` (`small`, `medium`, or
12
12
  `bodyInset` / `footerInset` (`none`, `compact`, or `comfortable`). The defaults
13
13
  match Kerf's medium modal, 8px body, and 16px footer rhythm.
14
14
 
15
+ The modal size presets have preferred widths of 400px, 560px, and 800px,
16
+ respectively, before the viewport width cap. Set `preferredWidth` to a branded
17
+ `CssLength` when the desktop shell needs another width. It takes precedence over
18
+ `size`, while the viewport cap still applies. For example, 832px and 480px
19
+ desktop preferences can share the same responsive dialog without changing its
20
+ resource tracks or preview aspect sizing.
21
+
22
+ ### Modal viewport bounds
23
+
24
+ `viewportGutter` accepts a branded `CssLength` from `@kerfjs/ui/css-values`.
25
+ It sets the minimum horizontal clearance; `preferredWidth` overrides the `size`
26
+ preset when supplied. `maxHeight` accepts a branded length or `"viewport"`; explicit caps are
27
+ clamped to the dynamic viewport height minus twice the gutter (16px per edge
28
+ when the gutter is omitted). Negative gutters/caps clamp to zero. Omitting
29
+ both options preserves Web Awesome's existing width and phone height caps.
30
+ These options apply only to `presentation="modal"`; side sheets and fullscreen
31
+ surfaces keep their own geometry.
32
+
33
+ ```tsx
34
+ import '@kerfjs/ui/surface-scaffold/register';
35
+ import { remify } from '@kerfjs/ui/css-values';
36
+ import { DialogSurface } from '@kerfjs/ui/surface-scaffold';
37
+
38
+ <DialogSurface preferredWidth={remify(832)} viewportGutter={remify(8)} maxHeight="viewport" bodyInset="none">
39
+ <wa-dialog label="Close workspace">{/* Application-owned content and footer */}</wa-dialog>
40
+ </DialogSurface>;
41
+ ```
42
+
43
+ This gives a desktop modal an 832px preferred width and a phone modal an 8px edge gutter with a `calc(100dvh - 16px)` height
44
+ cap without application selectors on Kerf roots or Web Awesome parts. A cap
45
+ is not a fixed height: short content stays naturally sized, and taller content
46
+ scrolls in Web Awesome's body while its header/footer remain outside that
47
+ scroll owner. Choosing a larger cap cannot guarantee that arbitrary content
48
+ fits without scrolling; preview tracks, canvas aspect ratios and content sizing
49
+ remain application-owned.
50
+
51
+ Responsive policy is explicit application state: read `deviceClass()` during
52
+ the render to choose the gutter, cap or presentation. For example use
53
+ `viewportGutter={remify(device.value.handset ? 8 : 16)}` with `maxHeight="viewport"`.
54
+ The scaffold introduces no hidden breakpoint or automatic fullscreen switch.
55
+ An application can configure device-class breakpoints when its phone policy
56
+ differs from the default. Prefer a typed cap such as `maxHeight={remify(640)}`
57
+ when the modal should stop growing before the viewport bound.
58
+
15
59
  Treat a dialog body as a `List` by default. When its rows or sections own their
16
60
  standard list geometry, set `bodyInset="none"` so the dialog does not add a
17
61
  second inset. Bare prose is still a list child: wrap it in `ListInsetText` so its
@@ -26,3 +70,5 @@ padding for a child that already owns row insets, `compact` uses 4px, and
26
70
  preserve `data-morph-skip-children` when its upgraded light-DOM items must retain
27
71
  identity across Kerf rerenders. Import `@kerfjs/ui/popup-menu/register` once for
28
72
  the focus handoff fix when wrapping a raw `wa-dropdown`.
73
+
74
+ Import `@kerfjs/ui/surface-scaffold/register` once when using `DialogSurface`: it registers `wa-dialog` and repairs Web Awesome 3.12 native dialog names before the first modal opening. Headed dialogs reference the rendered title, including live slotted labels; headerless dialogs use the public `label`. Explicit host `aria-label` and pre-existing native names are preserved. Geometry-only markup stays CSS-free and does not register Web Awesome implicitly.