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

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 (79) hide show
  1. package/ai/component-catalog.json +19 -8
  2. package/ai/component-composition.json +34 -1
  3. package/ai/components/README.md +1 -1
  4. package/ai/components/content-item.md +1 -1
  5. package/ai/components/list-header.md +4 -3
  6. package/ai/components/pane.md +2 -1
  7. package/ai/components/sunken-panel.md +1 -1
  8. package/ai/components/tab-navigator.md +2 -1
  9. package/ai/components/workbench.md +2 -1
  10. package/ai/public-api-signatures-v1.md +45 -16
  11. package/ai/skill.md +2 -2
  12. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  13. package/dist/browser/tab-navigator.js +1 -1
  14. package/dist/catalog.js +2 -2
  15. package/dist/{chunk-HPREYDKV.js → chunk-3XPGJUUQ.js} +5 -1
  16. package/dist/chunk-3XPGJUUQ.js.map +1 -0
  17. package/dist/{chunk-VLQUXZKA.js → chunk-7TE6XDFI.js} +8 -3
  18. package/dist/chunk-7TE6XDFI.js.map +1 -0
  19. package/dist/{chunk-IKCUPTR2.js → chunk-CVKIADIS.js} +4 -2
  20. package/dist/chunk-CVKIADIS.js.map +1 -0
  21. package/dist/{chunk-DXQZ3BEO.js → chunk-FL6J644M.js} +6 -1
  22. package/dist/chunk-FL6J644M.js.map +1 -0
  23. package/dist/{chunk-JL4BBZP4.js → chunk-JZM3G6RT.js} +4 -2
  24. package/dist/chunk-JZM3G6RT.js.map +1 -0
  25. package/dist/{chunk-DSLRNR43.js → chunk-LQKY5S4F.js} +17 -4
  26. package/dist/chunk-LQKY5S4F.js.map +1 -0
  27. package/dist/{chunk-M6BWQMQY.js → chunk-VIGVNDXZ.js} +11 -6
  28. package/dist/chunk-VIGVNDXZ.js.map +1 -0
  29. package/dist/collapsible-panel.d.ts +5 -1
  30. package/dist/collapsible-panel.js +6 -2
  31. package/dist/collapsible-panel.js.map +1 -1
  32. package/dist/content-item.d.ts +8 -1
  33. package/dist/content-item.js +1 -1
  34. package/dist/index.js +4 -4
  35. package/dist/list-header.d.ts +2 -7
  36. package/dist/list-header.js +1 -1
  37. package/dist/nav-stack.js +1 -1
  38. package/dist/pane.d.ts +8 -3
  39. package/dist/pane.js +1 -1
  40. package/dist/split-view.js +1 -1
  41. package/dist/styles/collapsible-panel.css +5 -0
  42. package/dist/styles/components/layout/collapsible-panel/collapsible-panel.css +5 -0
  43. package/dist/styles/components/layout/pane/pane.css +18 -4
  44. package/dist/styles/components/layout/workbench/workbench.css +9 -1
  45. package/dist/styles/components/navigation/{tab-scaffold/tab-scaffold.css → tab-navigator/tab-navigator.css} +13 -0
  46. package/dist/styles/components/surfaces/content-item/content-item.css +1 -1
  47. package/dist/styles/components/surfaces/sunken-panel/sunken-panel.css +5 -0
  48. package/dist/styles/content-item.css +1 -1
  49. package/dist/styles/pane.css +18 -4
  50. package/dist/styles/sunken-panel.css +5 -0
  51. package/dist/styles/tab-navigator.css +13 -0
  52. package/dist/styles/workbench.css +9 -1
  53. package/dist/sunken-panel.d.ts +5 -1
  54. package/dist/sunken-panel.js +1 -1
  55. package/dist/tab-navigator.d.ts +5 -1
  56. package/dist/tab-navigator.js +4 -0
  57. package/dist/tab-navigator.js.map +1 -1
  58. package/dist/wire-tab-navigator.js +1 -1
  59. package/dist/wire-tab-navigator.js.map +1 -1
  60. package/dist/workbench.d.ts +11 -1
  61. package/dist/workbench.js +1 -1
  62. package/docs/accessibility.md +5 -1
  63. package/docs/collapsible-panel.md +8 -1
  64. package/docs/component-contract.md +7 -5
  65. package/docs/layout.md +22 -6
  66. package/docs/sunken-panel.md +6 -0
  67. package/docs/tab-navigator.md +10 -2
  68. package/docs/type-contracts.md +2 -1
  69. package/docs/ux-demo.md +2 -0
  70. package/docs/workbench.md +12 -0
  71. package/llms.txt +13 -5
  72. package/package.json +1 -1
  73. package/dist/chunk-DSLRNR43.js.map +0 -1
  74. package/dist/chunk-DXQZ3BEO.js.map +0 -1
  75. package/dist/chunk-HPREYDKV.js.map +0 -1
  76. package/dist/chunk-IKCUPTR2.js.map +0 -1
  77. package/dist/chunk-JL4BBZP4.js.map +0 -1
  78. package/dist/chunk-M6BWQMQY.js.map +0 -1
  79. package/dist/chunk-VLQUXZKA.js.map +0 -1
@@ -646,7 +646,8 @@
646
646
  "which sides may compensate for device safe areas in an app-owned arrangement (safeAreaEdges)",
647
647
  "responsive placement and visibility",
648
648
  "whether the chrome dividers follow the scroll, always show, or never show (chromeDividers)",
649
- "default or sunken scrolling work surface (appearance)"
649
+ "default or sunken scrolling work surface (appearance)",
650
+ "optional keyboard or programmatic focus (tabIndex) and persistent focus outline (outlined)"
650
651
  ],
651
652
  "variants": [
652
653
  "content only",
@@ -670,7 +671,8 @@
670
671
  "notes": [
671
672
  "The pane is unpadded; separators are independently opt-in on logical edges and default off.",
672
673
  "Safe areas: the root and separators paint through unsafe areas while header, content, and footer pad the sides the pane still reaches (routed by --kui-edge-inset-*); content block insets are scroll padding.",
673
- "The scrolling content exposes its resolved logical inline padding to descendants as --kui-pane-content-inset-inline-start/end, including safe areas and deepInset."
674
+ "The scrolling content exposes its resolved logical inline padding to descendants as --kui-pane-content-inset-inline-start/end, including safe areas and deepInset.",
675
+ "deepInset adds --kui-space-xs to every side of the scrolling content and its block scroll padding; header/footer chrome still owns safe-area padding at its edge."
674
676
  ]
675
677
  },
676
678
  "delivery": {
@@ -1056,7 +1058,8 @@
1056
1058
  "tab badge counts or dots and their localized badgeLabel phrases",
1057
1059
  "routing and persistence",
1058
1060
  "responsive replacement on larger device classes",
1059
- "whether the bar divider follows the scroll, always shows, or never shows (chromeDividers)"
1061
+ "whether the bar divider follows the scroll, always shows, or never shows (chromeDividers)",
1062
+ "optional active-scene focus (tabIndex) and persistent outline (outlined)"
1060
1063
  ],
1061
1064
  "variants": [
1062
1065
  "text-only tabs",
@@ -1069,6 +1072,7 @@
1069
1072
  "accessibility": [
1070
1073
  "Give the tablist an accessible label and keep exactly the active tab in the sequential focus order.",
1071
1074
  "Wire selection through the controlled active id while leaving inactive scenes mounted and hidden.",
1075
+ "A focusable scene is a tabpanel named by its tab label; optional scene tabIndex applies only while active.",
1072
1076
  "A tab badge is aria-hidden; supply badgeLabel so the tab's accessible name becomes \"<label>, <badgeLabel>\" (it defaults to the badge text). The dot form, badge: true, has no text, so its badgeLabel is required."
1073
1077
  ],
1074
1078
  "geometry": {
@@ -1173,11 +1177,13 @@
1173
1177
  ],
1174
1178
  "appOwns": [
1175
1179
  "panel presence and content",
1180
+ "optional accessible name for the central region (mainLabel)",
1176
1181
  "collapsed state",
1177
1182
  "panel sizes",
1178
1183
  "navigation panel view stack and push/pop actions when navStack is supplied",
1179
1184
  "responsive replacement below desktop",
1180
- "collapse controls and focus policy"
1185
+ "collapse controls and focus policy",
1186
+ "optional main and panel focus stops and persistent outlines"
1181
1187
  ],
1182
1188
  "variants": [
1183
1189
  "main only",
@@ -1201,6 +1207,7 @@
1201
1207
  ],
1202
1208
  "accessibility": [
1203
1209
  "Give the Workbench and each meaningful rail or drawer an accessible label.",
1210
+ "Give the central region mainLabel when it needs its own named landmark.",
1204
1211
  "Keep collapse controls outside the disappearing region and move focus when a controlled rerender replaces a collapse control with its restore control.",
1205
1212
  "A resizable panel's separator is a focusable role=\"separator\" with its size in aria-valuenow and limits in aria-valuemin/aria-valuemax; arrow keys resize by 16px, Shift+arrow by 64px, and Home/End jump to the limits. It leaves the tab order while its panel is collapsed or not inline.",
1206
1213
  "Given a panel's collapsed signal, wireWorkbench closes an open overlay panel on Escape or an outside press and, however the panel closed, returns focus stranded inside it to the control that opened it, else to its restore control.",
@@ -1478,7 +1485,8 @@
1478
1485
  "region semantics and accessible name",
1479
1486
  "corner shape",
1480
1487
  "scroll ownership",
1481
- "child ordering and content"
1488
+ "child ordering and content",
1489
+ "optional keyboard or programmatic focus (tabIndex) and persistent outline (outlined)"
1482
1490
  ],
1483
1491
  "variants": [
1484
1492
  "rounded surface",
@@ -3210,15 +3218,15 @@
3210
3218
  "disclosure-arrow",
3211
3219
  "text"
3212
3220
  ],
3213
- "purpose": "Fill the available section width by default, or shrink-wrap without outer geometry when inline, while splitting a dormant title and mutually exclusive semantic count or badge from an optional logical-end action with an 18px visual; disclosure mode renders the title cluster as a button with a default production DisclosureArrow.",
3221
+ "purpose": "Fill the available section width by default, or shrink-wrap without outer geometry when inline, while splitting a dormant title and optional count or combined badge/status indicators from a logical-end action with an 18px visual; disclosure mode renders the title cluster as a button with a default production DisclosureArrow.",
3214
3222
  "publicExports": [
3215
3223
  "ListHeader"
3216
3224
  ],
3217
3225
  "useWhen": [
3218
- "Label a navigation section with an optional semantic count, non-count badge, action, or disclosure state; toggle mode supplies an 18px DisclosureArrow unless actionIcon replaces it."
3226
+ "Label a navigation section with an optional semantic count or simultaneous badge and status indicators, plus an action or disclosure state; toggle mode supplies an 18px DisclosureArrow unless actionIcon replaces it."
3219
3227
  ],
3220
3228
  "avoidWhen": [
3221
- "Do not concatenate a section count into label or pass numeric content through badge; use count with its required localized countLabel.",
3229
+ "Do not concatenate a section count into label or pass a bare number through badge; use count with its required localized countLabel for a sole section quantity.",
3222
3230
  "Do not use as a page or dialog title."
3223
3231
  ],
3224
3232
  "alternatives": [
@@ -3231,6 +3239,7 @@
3231
3239
  "section organization",
3232
3240
  "valid count and localized count label",
3233
3241
  "non-count badge content",
3242
+ "optional status content alongside badge, including conditional undefined values",
3234
3243
  "expanded state",
3235
3244
  "action policy",
3236
3245
  "domain data attributes",
@@ -3241,6 +3250,7 @@
3241
3250
  "label sizes xs, sm (default), m, l, xl",
3242
3251
  "count",
3243
3252
  "badge",
3253
+ "badge and status together",
3244
3254
  "action",
3245
3255
  "toggle",
3246
3256
  "placeholder (loading)",
@@ -3732,6 +3742,7 @@
3732
3742
  "corner shape",
3733
3743
  "region semantics and accessible name",
3734
3744
  "whether the item is a programmatic focus target",
3745
+ "optional static-item Tab stop and persistent focus outline",
3735
3746
  "the selection set and Shift/Cmd extension for multi-select rich cards"
3736
3747
  ],
3737
3748
  "variants": [
@@ -612,6 +612,11 @@
612
612
  "id": "default or sunken scrolling work surface (appearance)",
613
613
  "owner": "application",
614
614
  "required": false
615
+ },
616
+ {
617
+ "id": "optional keyboard or programmatic focus (tabIndex) and persistent focus outline (outlined)",
618
+ "owner": "application",
619
+ "required": false
615
620
  }
616
621
  ],
617
622
  "wiring": {
@@ -643,7 +648,8 @@
643
648
  "notes": [
644
649
  "The pane is unpadded; separators are independently opt-in on logical edges and default off.",
645
650
  "Safe areas: the root and separators paint through unsafe areas while header, content, and footer pad the sides the pane still reaches (routed by --kui-edge-inset-*); content block insets are scroll padding.",
646
- "The scrolling content exposes its resolved logical inline padding to descendants as --kui-pane-content-inset-inline-start/end, including safe areas and deepInset."
651
+ "The scrolling content exposes its resolved logical inline padding to descendants as --kui-pane-content-inset-inline-start/end, including safe areas and deepInset.",
652
+ "deepInset adds --kui-space-xs to every side of the scrolling content and its block scroll padding; header/footer chrome still owns safe-area padding at its edge."
647
653
  ]
648
654
  }
649
655
  },
@@ -1006,6 +1012,11 @@
1006
1012
  "id": "whether the bar divider follows the scroll, always shows, or never shows (chromeDividers)",
1007
1013
  "owner": "application",
1008
1014
  "required": false
1015
+ },
1016
+ {
1017
+ "id": "optional active-scene focus (tabIndex) and persistent outline (outlined)",
1018
+ "owner": "application",
1019
+ "required": false
1009
1020
  }
1010
1021
  ],
1011
1022
  "wiring": {
@@ -1044,6 +1055,7 @@
1044
1055
  "obligations": [
1045
1056
  "Give the tablist an accessible label and keep exactly the active tab in the sequential focus order.",
1046
1057
  "Wire selection through the controlled active id while leaving inactive scenes mounted and hidden.",
1058
+ "A focusable scene is a tabpanel named by its tab label; optional scene tabIndex applies only while active.",
1047
1059
  "A tab badge is aria-hidden; supply badgeLabel so the tab's accessible name becomes \"<label>, <badgeLabel>\" (it defaults to the badge text). The dot form, badge: true, has no text, so its badgeLabel is required."
1048
1060
  ]
1049
1061
  },
@@ -1111,6 +1123,11 @@
1111
1123
  "owner": "application",
1112
1124
  "required": false
1113
1125
  },
1126
+ {
1127
+ "id": "optional accessible name for the central region (mainLabel)",
1128
+ "owner": "application",
1129
+ "required": false
1130
+ },
1114
1131
  { "id": "collapsed state", "owner": "application", "required": false },
1115
1132
  { "id": "panel sizes", "owner": "application", "required": false },
1116
1133
  {
@@ -1127,6 +1144,11 @@
1127
1144
  "id": "collapse controls and focus policy",
1128
1145
  "owner": "application",
1129
1146
  "required": false
1147
+ },
1148
+ {
1149
+ "id": "optional main and panel focus stops and persistent outlines",
1150
+ "owner": "application",
1151
+ "required": false
1130
1152
  }
1131
1153
  ],
1132
1154
  "wiring": {
@@ -1187,6 +1209,7 @@
1187
1209
  "accessibility": {
1188
1210
  "obligations": [
1189
1211
  "Give the Workbench and each meaningful rail or drawer an accessible label.",
1212
+ "Give the central region mainLabel when it needs its own named landmark.",
1190
1213
  "Keep collapse controls outside the disappearing region and move focus when a controlled rerender replaces a collapse control with its restore control.",
1191
1214
  "A resizable panel's separator is a focusable role=\"separator\" with its size in aria-valuenow and limits in aria-valuemin/aria-valuemax; arrow keys resize by 16px, Shift+arrow by 64px, and Home/End jump to the limits. It leaves the tab order while its panel is collapsed or not inline.",
1192
1215
  "Given a panel's collapsed signal, wireWorkbench closes an open overlay panel on Escape or an outside press and, however the panel closed, returns focus stranded inside it to the control that opened it, else to its restore control.",
@@ -1369,6 +1392,11 @@
1369
1392
  "id": "child ordering and content",
1370
1393
  "owner": "application",
1371
1394
  "required": false
1395
+ },
1396
+ {
1397
+ "id": "optional keyboard or programmatic focus (tabIndex) and persistent outline (outlined)",
1398
+ "owner": "application",
1399
+ "required": false
1372
1400
  }
1373
1401
  ],
1374
1402
  "wiring": {
@@ -3002,6 +3030,11 @@
3002
3030
  "owner": "application",
3003
3031
  "required": false
3004
3032
  },
3033
+ {
3034
+ "id": "optional static-item Tab stop and persistent focus outline",
3035
+ "owner": "application",
3036
+ "required": false
3037
+ },
3005
3038
  {
3006
3039
  "id": "the selection set and Shift/Cmd extension for multi-select rich cards",
3007
3040
  "owner": "application",
@@ -43,7 +43,7 @@ Exact prop and callback types live in [`public-api-signatures-v1.md`](../public-
43
43
  ## Kerf UI — Navigation
44
44
 
45
45
  - [List](./list.md) — Standardize vertical component layout, optional physical-side text/control insets, and scroll/divider ownership instead of repeating ad-hoc flex-column wrappers.
46
- - [ListHeader](./list-header.md) — Fill the available section width by default, or shrink-wrap without outer geometry when inline, while splitting a dormant title and mutually exclusive semantic count or badge from an optional logical-end action with an 18px visual; disclosure mode renders the title cluster as a button with a default production DisclosureArrow.
46
+ - [ListHeader](./list-header.md) — Fill the available section width by default, or shrink-wrap without outer geometry when inline, while splitting a dormant title and optional count or combined badge/status indicators from a logical-end action with an 18px visual; disclosure mode renders the title cluster as a button with a default production DisclosureArrow.
47
47
  - [ListActionRow](./list-action-row.md) — Keep a selectable primary row action and an independently named trailing action as sibling native controls inside one noninteractive visual row.
48
48
  - [ListItem](./list-item.md) — Selectable, disabled, trailing, and multiline action or link rows.
49
49
  - [Application tabs](./application-tabs.md) — Show AppTab and TabBar in a complete controlled composition.
@@ -58,7 +58,7 @@ Exact prop names and types: [`@kerfjs/ui/content-item`](../public-api-signatures
58
58
 
59
59
  ## State and wiring
60
60
 
61
- **The app owns:** the item content; whether the item marks a real distinction that warrants a visible frame; whether the item needs a surface or semantic appearance; corner shape; region semantics and accessible name; whether the item is a programmatic focus target; the selection set and Shift/Cmd extension for multi-select rich cards.
61
+ **The app owns:** the item content; whether the item marks a real distinction that warrants a visible frame; whether the item needs a surface or semantic appearance; corner shape; region semantics and accessible name; whether the item is a programmatic focus target; optional static-item Tab stop and persistent focus outline; the selection set and Shift/Cmd extension for multi-select rich cards.
62
62
 
63
63
  No wiring helper.
64
64
 
@@ -8,13 +8,13 @@ Configurable section headings with five independent label sizes, full-width or s
8
8
 
9
9
  ## When to use
10
10
 
11
- Fill the available section width by default, or shrink-wrap without outer geometry when inline, while splitting a dormant title and mutually exclusive semantic count or badge from an optional logical-end action with an 18px visual; disclosure mode renders the title cluster as a button with a default production DisclosureArrow.
11
+ Fill the available section width by default, or shrink-wrap without outer geometry when inline, while splitting a dormant title and optional count or combined badge/status indicators from a logical-end action with an 18px visual; disclosure mode renders the title cluster as a button with a default production DisclosureArrow.
12
12
 
13
- - Label a navigation section with an optional semantic count, non-count badge, action, or disclosure state; toggle mode supplies an 18px DisclosureArrow unless actionIcon replaces it.
13
+ - Label a navigation section with an optional semantic count or simultaneous badge and status indicators, plus an action or disclosure state; toggle mode supplies an 18px DisclosureArrow unless actionIcon replaces it.
14
14
 
15
15
  **Not when:**
16
16
 
17
- - Do not concatenate a section count into label or pass numeric content through badge; use count with its required localized countLabel.
17
+ - Do not concatenate a section count into label or pass a bare number through badge; use count with its required localized countLabel for a sole section quantity.
18
18
  - Do not use as a page or dialog title.
19
19
 
20
20
  **Alternatives:**
@@ -37,6 +37,7 @@ Exact prop names and types: [`@kerfjs/ui/list-header`](../public-api-signatures-
37
37
  - label sizes xs, sm (default), m, l, xl
38
38
  - count
39
39
  - badge
40
+ - badge and status together
40
41
  - action
41
42
  - toggle
42
43
  - placeholder (loading)
@@ -62,7 +62,7 @@ Zones (a zone is bound to JSX only through its listed prop; never assume the zon
62
62
 
63
63
  ## State and wiring
64
64
 
65
- **The app owns:** root and content semantics; accessible labels; header, content, and footer children; which logical boundaries show separators; which sides may compensate for device safe areas in an app-owned arrangement (safeAreaEdges); responsive placement and visibility; whether the chrome dividers follow the scroll, always show, or never show (chromeDividers); default or sunken scrolling work surface (appearance).
65
+ **The app owns:** root and content semantics; accessible labels; header, content, and footer children; which logical boundaries show separators; which sides may compensate for device safe areas in an app-owned arrangement (safeAreaEdges); responsive placement and visibility; whether the chrome dividers follow the scroll, always show, or never show (chromeDividers); default or sunken scrolling work surface (appearance); optional keyboard or programmatic focus (tabIndex) and persistent focus outline (outlined).
66
66
 
67
67
  **Wiring:** `wireScrollDividers` is optional.
68
68
 
@@ -78,6 +78,7 @@ Margin: child · border: conditional · padding: child (layout role: structure).
78
78
  - The pane is unpadded; separators are independently opt-in on logical edges and default off.
79
79
  - Safe areas: the root and separators paint through unsafe areas while header, content, and footer pad the sides the pane still reaches (routed by --kui-edge-inset-\*); content block insets are scroll padding.
80
80
  - The scrolling content exposes its resolved logical inline padding to descendants as --kui-pane-content-inset-inline-start/end, including safe areas and deepInset.
81
+ - deepInset adds --kui-space-xs to every side of the scrolling content and its block scroll padding; header/footer chrome still owns safe-area padding at its edge.
81
82
 
82
83
  ## Accessibility
83
84
 
@@ -46,7 +46,7 @@ Exact prop names and types: [`@kerfjs/ui/sunken-panel`](../public-api-signatures
46
46
 
47
47
  ## State and wiring
48
48
 
49
- **The app owns:** region semantics and accessible name; corner shape; scroll ownership; child ordering and content.
49
+ **The app owns:** region semantics and accessible name; corner shape; scroll ownership; child ordering and content; optional keyboard or programmatic focus (tabIndex) and persistent outline (outlined).
50
50
 
51
51
  No wiring helper.
52
52
 
@@ -53,7 +53,7 @@ Exact prop names and types: [`@kerfjs/ui/tab-navigator`](../public-api-signature
53
53
 
54
54
  ## State and wiring
55
55
 
56
- **The app owns:** controlled active tab id; tab labels, icons, and content; tab badge counts or dots and their localized badgeLabel phrases; routing and persistence; responsive replacement on larger device classes; whether the bar divider follows the scroll, always shows, or never shows (chromeDividers).
56
+ **The app owns:** controlled active tab id; tab labels, icons, and content; tab badge counts or dots and their localized badgeLabel phrases; routing and persistence; responsive replacement on larger device classes; whether the bar divider follows the scroll, always shows, or never shows (chromeDividers); optional active-scene focus (tabIndex) and persistent outline (outlined).
57
57
 
58
58
  **Wiring:** `wireTabNavigator`, `wireScrollDividers` is required.
59
59
 
@@ -75,6 +75,7 @@ Margin: none · border: conditional · padding: self (layout role: structure). `
75
75
 
76
76
  - Give the tablist an accessible label and keep exactly the active tab in the sequential focus order.
77
77
  - Wire selection through the controlled active id while leaving inactive scenes mounted and hidden.
78
+ - A focusable scene is a tabpanel named by its tab label; optional scene tabIndex applies only while active.
78
79
  - A tab badge is aria-hidden; supply badgeLabel so the tab's accessible name becomes "\<label>, \<badgeLabel>" (it defaults to the badge text). The dot form, badge: true, has no text, so its badgeLabel is required.
79
80
 
80
81
  ## Styling boundary
@@ -72,7 +72,7 @@ Zones (a zone is bound to JSX only through its listed prop; never assume the zon
72
72
 
73
73
  ## State and wiring
74
74
 
75
- **The app owns:** panel presence and content; collapsed state; panel sizes; navigation panel view stack and push/pop actions when navStack is supplied; responsive replacement below desktop; collapse controls and focus policy.
75
+ **The app owns:** panel presence and content; optional accessible name for the central region (mainLabel); collapsed state; panel sizes; navigation panel view stack and push/pop actions when navStack is supplied; responsive replacement below desktop; collapse controls and focus policy; optional main and panel focus stops and persistent outlines.
76
76
 
77
77
  **Wiring:** `wireWorkbench`, `wireScrollDividers` is optional.
78
78
 
@@ -94,6 +94,7 @@ Margin: none · border: self · padding: child (layout role: structure). `self`
94
94
  ## Accessibility
95
95
 
96
96
  - Give the Workbench and each meaningful rail or drawer an accessible label.
97
+ - Give the central region mainLabel when it needs its own named landmark.
97
98
  - Keep collapse controls outside the disappearing region and move focus when a controlled rerender replaces a collapse control with its restore control.
98
99
  - A resizable panel's separator is a focusable role="separator" with its size in aria-valuenow and limits in aria-valuemin/aria-valuemax; arrow keys resize by 16px, Shift+arrow by 64px, and Home/End jump to the limits. It leaves the tab order while its panel is collapsed or not inline.
99
100
  - Given a panel's collapsed signal, wireWorkbench closes an open overlay panel on Escape or an outside press and, however the panel closed, returns focus stranded inside it to the control that opened it, else to its restore control.
@@ -1,6 +1,6 @@
1
1
  # Public API signatures for the UI authoring corpus
2
2
 
3
- Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.79` and `kerfjs@5.0.0-beta.79`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
3
+ Generated from emitted declarations for `@kerfjs/ui@5.0.0-beta.80` and `kerfjs@5.0.0-beta.80`. This bounded reference covers only APIs used by the seven-task corpus. It is interface evidence, not an implementation or runtime guarantee.
4
4
 
5
5
  ## `@kerfjs/ui/css-values`
6
6
 
@@ -634,13 +634,8 @@ type ListHeaderIndicatorProps = {
634
634
  } | {
635
635
  count?: never;
636
636
  countLabel?: never;
637
- badge: SafeHtml;
638
- status?: never;
639
- } | {
640
- count?: never;
641
- countLabel?: never;
642
- badge?: never;
643
- status?: SafeHtml;
637
+ badge?: SafeHtml | undefined;
638
+ status?: SafeHtml | undefined;
644
639
  };
645
640
  type ListHeaderProps = ListHeaderBaseProps & ListHeaderIndicatorProps & ListHeaderModeProps;
646
641
  declare function ListHeader({ label, size, headingLevel, count, countLabel, badge, status, density, divider, inline, width, indicatorTone, action, actionLabel, actionIcon, actionDisabled, disabledReason, expanded, toggle, placeholder, rootAttributes, triggerAttributes, slot, }: ListHeaderProps): SafeHtml;
@@ -932,10 +927,12 @@ type ContentItemSelectionMode = 'none' | 'single' | 'multiple' | 'toggle';
932
927
  type ContentItemRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
933
928
  'data-component'?: never;
934
929
  'data-appearance'?: never;
930
+ 'data-outlined'?: never;
935
931
  }>;
936
932
  type ContentItemInteractiveRootAttributes = Readonly<Record<`data-${string}`, string | undefined> & {
937
933
  'data-component'?: never;
938
934
  'data-appearance'?: never;
935
+ 'data-outlined'?: never;
939
936
  'data-action'?: never;
940
937
  'data-item-id'?: never;
941
938
  'data-interactive'?: never;
@@ -976,6 +973,8 @@ interface ContentItemBaseProps {
976
973
  * combined with `data-nav-focus` in `rootAttributes`.
977
974
  */
978
975
  focusTarget?: boolean;
976
+ /** Keep the standard focus outline visible, such as for a drop target. */
977
+ outlined?: boolean;
979
978
  className?: string;
980
979
  /** Native named-slot assignment when composed inside a web component. */
981
980
  slot?: string;
@@ -987,6 +986,8 @@ type ContentItemProps = ContentItemBaseProps & ({
987
986
  selectionMode?: never;
988
987
  selected?: never;
989
988
  disabled?: never;
989
+ /** Opt into keyboard (`0`) or programmatic (`-1`) focus. */
990
+ tabIndex?: 0 | -1;
990
991
  /** Existing static `data-*` metadata remains available. */
991
992
  rootAttributes?: ContentItemRootAttributes;
992
993
  } | {
@@ -998,6 +999,7 @@ type ContentItemProps = ContentItemBaseProps & ({
998
999
  selectionMode?: ContentItemSelectionMode;
999
1000
  selected?: boolean;
1000
1001
  disabled?: boolean;
1002
+ tabIndex?: never;
1001
1003
  /** Safe app metadata; interaction attributes are component-owned. */
1002
1004
  rootAttributes?: ContentItemInteractiveRootAttributes;
1003
1005
  });
@@ -1006,7 +1008,7 @@ type ContentItemProps = ContentItemBaseProps & ({
1006
1008
  * border (transparent unless `framed`), 8px padding, and a rounded or pill
1007
1009
  * radius. It owns that whole geometry, so wrappers must not add more.
1008
1010
  */
1009
- declare function ContentItem({ appearance, children, frame, flush, shape, title, ariaLabel, focusTarget, interactive, action, itemId, selectionMode, selected, disabled, className, rootAttributes, slot, }: ContentItemProps): kerfjs.SafeHtml;
1011
+ declare function ContentItem({ appearance, children, frame, flush, shape, title, ariaLabel, focusTarget, outlined, tabIndex, interactive, action, itemId, selectionMode, selected, disabled, className, rootAttributes, slot, }: ContentItemProps): kerfjs.SafeHtml;
1010
1012
 
1011
1013
  export { ContentItem, type ContentItemAppearance, type ContentItemFrame, type ContentItemProps, type ContentItemSelectionMode, type ContentItemShape, SemanticTone };
1012
1014
  ```
@@ -1436,6 +1438,7 @@ type PaneRootAttributes = Readonly<Record<`data-${string}`, string | undefined>
1436
1438
  'data-chrome-dividers'?: never;
1437
1439
  'data-appearance'?: never;
1438
1440
  'data-deep-inset'?: never;
1441
+ 'data-outlined'?: never;
1439
1442
  }>;
1440
1443
  /** How a Pane's header and footer relate to its scrolling content. */
1441
1444
  type PaneChromePlacement = 'fixed' | 'auto';
@@ -1475,8 +1478,12 @@ interface PaneProps {
1475
1478
  chromeDividers?: PaneChromeDividers;
1476
1479
  /** Paint the scrolling work surface with the shared lowered-surface color. */
1477
1480
  appearance?: PaneAppearance;
1478
- /** Add an extra 8px inline gutter to scrolling content (default: false). */
1481
+ /** Add 8px to every side of scrolling content (default: false). */
1479
1482
  deepInset?: boolean;
1483
+ /** Opt into keyboard (`0`) or programmatic (`-1`) focus on the Pane root. */
1484
+ tabIndex?: 0 | -1;
1485
+ /** Keep the standard focus outline visible, such as for a drop target. */
1486
+ outlined?: boolean;
1480
1487
  /** Root semantics. Defaults to `div`. */
1481
1488
  element?: PaneElement;
1482
1489
  /** Scrolling content semantics. Defaults to `div`. */
@@ -1511,14 +1518,14 @@ interface PaneProps {
1511
1518
  * or a CollapsiblePanel) forwards, so the app configures that pane instead of
1512
1519
  * styling it. An omitted or `undefined` field keeps the composite's default.
1513
1520
  */
1514
- type PaneConfig = Pick<PaneProps, 'contentElement' | 'contentLabel' | 'separators' | 'safeAreaEdges' | 'chromeDividers' | 'appearance' | 'deepInset'>;
1521
+ type PaneConfig = Pick<PaneProps, 'contentElement' | 'contentLabel' | 'separators' | 'safeAreaEdges' | 'chromeDividers' | 'appearance' | 'deepInset' | 'tabIndex' | 'outlined'>;
1515
1522
  /**
1516
1523
  * An unpadded application column with optional fixed header/footer slots and one
1517
1524
  * scrolling vertical content owner. Separator lines are independently opt-in on
1518
1525
  * each logical edge, so the same component works as a sidebar, main area,
1519
1526
  * inspector, or dialog column.
1520
1527
  */
1521
- declare function Pane({ header, children, footer, chromePlacement, chromeDividers, appearance, deepInset, element, contentElement, separators, safeAreaEdges, id, label, contentLabel, className, headerClassName, contentClassName, footerClassName, rootAttributes, slot, }: PaneProps): kerfjs.SafeHtml;
1528
+ declare function Pane({ header, children, footer, chromePlacement, chromeDividers, appearance, deepInset, tabIndex, outlined, element, contentElement, separators, safeAreaEdges, id, label, contentLabel, className, headerClassName, contentClassName, footerClassName, rootAttributes, slot, }: PaneProps): kerfjs.SafeHtml;
1522
1529
 
1523
1530
  export { Pane, type PaneAppearance, type PaneChromeDividers, type PaneChromePlacement, type PaneConfig, type PaneContentElement, type PaneElement, type PaneProps, type PaneSeparatorSide };
1524
1531
  ```
@@ -1629,6 +1636,10 @@ interface WorkbenchPanelBase {
1629
1636
  resizable?: boolean | WorkbenchPanelResizable;
1630
1637
  /** Accessible name for the panel region. */
1631
1638
  label?: string;
1639
+ /** Opt into keyboard (`0`) or programmatic (`-1`) focus on the panel region. */
1640
+ tabIndex?: 0 | -1;
1641
+ /** Keep the standard focus outline visible while this panel is expanded. */
1642
+ outlined?: boolean;
1632
1643
  separator?: ResizableRegionSeparator;
1633
1644
  collapseMotion?: ResizableRegionCollapseMotion;
1634
1645
  contentOverflow?: ResizableRegionContentOverflow;
@@ -1733,6 +1744,12 @@ interface WorkbenchProps {
1733
1744
  * the `Pane` defaults.
1734
1745
  */
1735
1746
  mainPane?: PaneConfig;
1747
+ /** Accessible name for the central region when it needs its own landmark. */
1748
+ mainLabel?: string;
1749
+ /** Opt into keyboard (`0`) or programmatic (`-1`) focus on the central region. */
1750
+ mainTabIndex?: 0 | -1;
1751
+ /** Keep the standard focus outline visible on the central region. */
1752
+ mainOutlined?: boolean;
1736
1753
  /**
1737
1754
  * Configuration for the `List` that holds `mainHeader` (`gap`, `hAlign`,
1738
1755
  * `vAlign`, `dividerSides`, `textInsets`, `controlInsets`). Omitted or
@@ -1783,7 +1800,7 @@ interface WorkbenchProps {
1783
1800
  * lives outside it and stays reachable). A panel may opt in to drag and keyboard resizing with
1784
1801
  * `resizable`, which `wireWorkbench` drives. See `docs/23-app-layouts.md` §3.3.
1785
1802
  */
1786
- declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, mainToolbar, mainHeader, mainFooter, mainBottomToolbar, mainHeaderPlacement, mainFooterPlacement, mainPane, mainHeaderList, mainFooterList, mainMinSize, mainMinHeight, overlayBackdrop, className, slot, }: WorkbenchProps): SafeHtml;
1803
+ declare function Workbench({ id, label, main, leftRail, rightRail, bottomDrawer, mainToolbar, mainHeader, mainFooter, mainBottomToolbar, mainHeaderPlacement, mainFooterPlacement, mainPane, mainLabel, mainTabIndex, mainOutlined, mainHeaderList, mainFooterList, mainMinSize, mainMinHeight, overlayBackdrop, className, slot, }: WorkbenchProps): SafeHtml;
1787
1804
 
1788
1805
  export { Workbench, type WorkbenchChromePlacement, type WorkbenchCompactOverlay, type WorkbenchMainBottomToolbar, type WorkbenchMainToolbar, type WorkbenchNavigationPanel, type WorkbenchPanel, type WorkbenchPanelBottomToolbar, type WorkbenchPanelResizable, type WorkbenchPanelToggle, type WorkbenchPanelToolbar, type WorkbenchProps, type WorkbenchResponsiveOverlayAt, type WorkbenchStaticPanel };
1789
1806
  ```
@@ -1973,6 +1990,10 @@ interface CollapsiblePanelProps {
1973
1990
  size?: number;
1974
1991
  /** Accessible label for the panel region. */
1975
1992
  label?: string;
1993
+ /** Opt into keyboard (`0`) or programmatic (`-1`) focus on the panel region. */
1994
+ tabIndex?: 0 | -1;
1995
+ /** Keep the standard focus outline visible while the panel is expanded. */
1996
+ outlined?: boolean;
1976
1997
  /** Panel content. */
1977
1998
  children?: KerfUiContent;
1978
1999
  /**
@@ -2024,7 +2045,7 @@ interface CollapsiblePanelProps {
2024
2045
  * and persistence semantics, and with `CollapsiblePanelToggle` for the standard
2025
2046
  * affordance. See `ui/docs/collapsible-panel.md` and `docs/23-app-layouts.md`.
2026
2047
  */
2027
- declare function CollapsiblePanel({ id, side, collapsed, size, label, children, toolbar, header, headerList, headerPlacement, footer, footerList, bottomToolbar, footerPlacement, pane, separator, collapseMotion, contentOverflow, presentation, restoreControl, restorePosition, className, }: CollapsiblePanelProps): SafeHtml;
2048
+ declare function CollapsiblePanel({ id, side, collapsed, size, label, tabIndex, outlined, children, toolbar, header, headerList, headerPlacement, footer, footerList, bottomToolbar, footerPlacement, pane, separator, collapseMotion, contentOverflow, presentation, restoreControl, restorePosition, className, }: CollapsiblePanelProps): SafeHtml;
2028
2049
  interface CollapsiblePanelRelocatedProps {
2029
2050
  /** The panel's `id`. */
2030
2051
  panelId: string;
@@ -2131,8 +2152,12 @@ interface TabNavigatorTabBase<Id extends string> {
2131
2152
  content: KerfUiContent;
2132
2153
  /** Background of this scene's scrolling work surface. */
2133
2154
  appearance?: PaneAppearance;
2134
- /** Add an extra 8px inline gutter to a plain scene's scrolling content. */
2155
+ /** Add 8px to every side of a plain scene's scrolling content. */
2135
2156
  deepInset?: boolean;
2157
+ /** Opt into keyboard (`0`) or programmatic (`-1`) focus on this scene. */
2158
+ tabIndex?: 0 | -1;
2159
+ /** Keep the standard focus outline visible while this scene is active. */
2160
+ outlined?: boolean;
2136
2161
  }
2137
2162
  /** A tab with an optional count or short-status badge. */
2138
2163
  interface TabNavigatorTabCountBadge {
@@ -2905,6 +2930,10 @@ interface SunkenPanelProps {
2905
2930
  flex?: boolean | CssFlexKeyword | CssFlex;
2906
2931
  /** Fill a parent with a definite height; use flex inside a flex layout. */
2907
2932
  fill?: boolean;
2933
+ /** Opt into keyboard (`0`) or programmatic (`-1`) focus on the panel. */
2934
+ tabIndex?: 0 | -1;
2935
+ /** Keep the standard focus outline visible, such as for a drop target. */
2936
+ outlined?: boolean;
2908
2937
  className?: string;
2909
2938
  /** Native named-slot assignment when composed inside a web component. */
2910
2939
  slot?: string;
@@ -2914,7 +2943,7 @@ interface SunkenPanelProps {
2914
2943
  * stack. The panel owns its background and padding; children own their own
2915
2944
  * borders and internal geometry.
2916
2945
  */
2917
- declare function SunkenPanel({ children, ariaLabel, shape, flex, fill, className, slot, }: SunkenPanelProps): kerfjs.SafeHtml;
2946
+ declare function SunkenPanel({ children, ariaLabel, shape, flex, fill, tabIndex, outlined, className, slot, }: SunkenPanelProps): kerfjs.SafeHtml;
2918
2947
 
2919
2948
  export { SunkenPanel, type SunkenPanelProps, type SunkenPanelShape };
2920
2949
  ```
package/ai/skill.md CHANGED
@@ -254,7 +254,7 @@ Hard rules:
254
254
 
255
255
  16. Keep a visible collapsible pane's collapse control in its own toolbar. When hidden, move its restore control into the adjacent main toolbar on the corresponding logical edge: leading for an inline-start sidebar and trailing for an inline-end inspector. Collapse the pane completely; do not preserve an empty icon-only rail.
256
256
  16a. Dividers between pinned chrome and scrolling content are component chrome: a `Toolbar` draws none by default (`dividerSides` is for a permanent edge only). Call `wireScrollDividers(appRoot)` from `@kerfjs/ui/wire-scroll-dividers` once and retain its disposer; a plain `Pane` with `chromeDividers="scroll"` draws the line under its header only while content is scrolled beneath it and over its footer only while more content lies below (never when the content fits), every `NavStack`'s top chrome and bottom toolbar and every `TabNavigator` bar do the same around the active view or scene (keyed on whatever actually scrolls there, including a sole `Pane`'s content), and every `TabBar` marks each side of its strip with tabs scrolled out of view. Configure a pane with `chromeDividers` (`scroll` by default for plain panes, `always` by default for sunken panes, or explicit `scroll`, `always`, `none`, also via `PaneConfig`); `always` draws both lines even when content fits. A `NavStack` or `TabNavigator` has its own `chromeDividers` (same values: `always` draws the chrome lines without the wiring, `none` never, even when wired; a `SplitView` forwards it through `compactStack`); a `toolbarConfig.dividerSides` edge stays the Toolbar's own. Name app-owned chrome by id in `targets`. The wiring writes only `data-scroll-overflow` / `data-scroll-divider` state; never write a border onto a component to show a divider.
257
- 17. Each `Toolbar` zone accepts `KerfUiContent` directly, including status content, native controls, and application markup. Put identity/title text directly in the zone as `ToolbarText`; use `ToolbarControlGroup` when related controls need shared toolbar chrome, sizing, hover treatment, or overflow behavior. Bare controls keep their own styling and accessibility responsibilities. A standalone primary `wa-button` in trailing keeps its own Web Awesome chrome. `SegmentedControl`, `Select`, and `TokenSearchField` can live inside a group when they need that shared presentation. Configure zone behavior with `Toolbar.centerAlign` and its `responsive` / `responsiveAt` policy instead of selecting toolbar zone classes. No policy clips an action: trailing (and stacked center) zones wrap whole groups onto another row, and the center zone never compresses below its content (a leading title too long for the row truncates with an ellipsis instead), so never hide or shrink a toolbar control to make it fit; use `responsive="wrap"` for a heading whose title must stay whole, and move genuinely lower-priority commands into an app-owned "More" popup menu. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature. A popup menu in a toolbar is a `PopupMenu` (`@kerfjs/ui/popup-menu`, registered once via `@kerfjs/ui/popup-menu/register`) in a `single` group with `nestedDropdown` and a `menuInset`: give it typed `items` (commands with `action`s, `heading`s, `divider`s) and a trigger `icon` plus visible `text` or an accessible `label`; never hand-write `wa-dropdown` / `wa-dropdown-item` markup for a menu. Let the group size that trigger from its icon, localized text, and caret; do not force a width on the menu or button. By default a group stays on one row: each icon sits beside its label, and the toolbar's `responsive` policy relocates whole groups when needed. Set `overflow="wrap"` only for independent links that should move as whole actions to another row within the group; use `overflow="scroll"` for a bounded one-row action strip. A `ToolbarText` is one line ending in an ellipsis by default; opt into `wrap` (capped by `maxLines`) or `ellipsis={false}` deliberately. Inside a group, a `Select` or `PopupMenu` trigger drops its focus ring while its popup is open (the open popup and its current row show where focus is) and the ring returns when the popup closes; a ring follows the control's shape (a pill for an icon-and-caret trigger, a circle for a compact icon button) — set `focusRingOwner="group"` on a Select whose group paints the composed ring, and never add CSS to show, hide, or reshape a ring. For a compact mixed group, keep the standard item padding and raised selected chrome; the selected item paints over the outer border without an internal separator. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A non-interactive heading icon is an icon tile: put the `LucideIcon` directly in its `ToolbarControlGroup` (it takes an icon button's 40px slot and centers), never inside a `<button>` that does nothing. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Each zone item sits in the toolbar's top 44px control band, centered by its own size; a taller item (a wrapped title, a second trailing row) starts at the band's top and grows down without moving the others, so a wrapped `ToolbarText` keeps trailing controls beside its first line — do not add alignment CSS for this. Split dormant and interactive regions: `ListHeader` fills the available inline width by default; set `width="content"` to shrink-wrap while retaining its normal geometry, or `inline` when it belongs in surrounding text without outer margin, border, or padding. It keeps its label and mutually exclusive semantic count or non-count `badge` together, with an independent logical-end action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size` inside a fitted 36px hover square whose transparent hit layer keeps the pointer target at least 44px (44px wide by the 36px row in `density="compact"`); never shrink the target to match the visual, and never force the button to 44px. Pass every non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a number in `badge`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets. Dividers are chrome and earn their place: a `Toolbar` draws none by default, and the line between pinned chrome and scrolling content comes from scroll state (rule 16a), so never add a border, a permanent `dividerSides`, or a margin to fake or adjust one.
257
+ 17. Each `Toolbar` zone accepts `KerfUiContent` directly, including status content, native controls, and application markup. Put identity/title text directly in the zone as `ToolbarText`; use `ToolbarControlGroup` when related controls need shared toolbar chrome, sizing, hover treatment, or overflow behavior. Bare controls keep their own styling and accessibility responsibilities. A standalone primary `wa-button` in trailing keeps its own Web Awesome chrome. `SegmentedControl`, `Select`, and `TokenSearchField` can live inside a group when they need that shared presentation. Configure zone behavior with `Toolbar.centerAlign` and its `responsive` / `responsiveAt` policy instead of selecting toolbar zone classes. No policy clips an action: trailing (and stacked center) zones wrap whole groups onto another row, and the center zone never compresses below its content (a leading title too long for the row truncates with an ellipsis instead), so never hide or shrink a toolbar control to make it fit; use `responsive="wrap"` for a heading whose title must stay whole, and move genuinely lower-priority commands into an app-owned "More" popup menu. For an ordinary icon/action control inside a group, use a plain `<button>` (the group styles `> button` fully) — that is the default; reach for `wa-button` only when you need a Web Awesome feature. A popup menu in a toolbar is a `PopupMenu` (`@kerfjs/ui/popup-menu`, registered once via `@kerfjs/ui/popup-menu/register`) in a `single` group with `nestedDropdown` and a `menuInset`: give it typed `items` (commands with `action`s, `heading`s, `divider`s) and a trigger `icon` plus visible `text` or an accessible `label`; never hand-write `wa-dropdown` / `wa-dropdown-item` markup for a menu. Let the group size that trigger from its icon, localized text, and caret; do not force a width on the menu or button. By default a group stays on one row: each icon sits beside its label, and the toolbar's `responsive` policy relocates whole groups when needed. Set `overflow="wrap"` only for independent links that should move as whole actions to another row within the group; use `overflow="scroll"` for a bounded one-row action strip. A `ToolbarText` is one line ending in an ellipsis by default; opt into `wrap` (capped by `maxLines`) or `ellipsis={false}` deliberately. Inside a group, a `Select` or `PopupMenu` trigger drops its focus ring while its popup is open (the open popup and its current row show where focus is) and the ring returns when the popup closes; a ring follows the control's shape (a pill for an icon-and-caret trigger, a circle for a compact icon button) — set `focusRingOwner="group"` on a Select whose group paints the composed ring, and never add CSS to show, hide, or reshape a ring. For a compact mixed group, keep the standard item padding and raised selected chrome; the selected item paints over the outer border without an internal separator. For an avatar control, set `content="avatar"` and `avatarImage` on the group instead of inserting an `img`: a lone control paints the contained image on the outer group and a multi-button group paints it only on the pressed selection highlight. Compose panel/dialog/page headings with a direct extra-large `ToolbarText`, optional grouped icon, grouped trailing controls, and app-owned supporting copy below. A non-interactive heading icon is an icon tile: put the `LucideIcon` directly in its `ToolbarControlGroup` (it takes an icon button's 40px slot and centers), never inside a `<button>` that does nothing. A group remains 44px outside (`calc(2px + remify(42px))`) when its border/background are transparent; use 8px between groups and inside items. Each zone item sits in the toolbar's top 44px control band, centered by its own size; a taller item (a wrapped title, a second trailing row) starts at the band's top and grows down without moving the others, so a wrapped `ToolbarText` keeps trailing controls beside its first line — do not add alignment CSS for this. Split dormant and interactive regions: `ListHeader` fills the available inline width by default; set `width="content"` to shrink-wrap while retaining its normal geometry, or `inline` when it belongs in surrounding text without outer margin, border, or padding. It keeps its label and either a semantic count or independent `badge` and `status` indicators together, with an independent logical-end action unless disclosure mode makes the title cluster the button. Its action visual defaults to 18px through `--kui-list-header-action-icon-size` inside a fitted 36px hover square whose transparent hit layer keeps the pointer target at least 44px (44px wide by the 36px row in `density="compact"`); never shrink the target to match the visual, and never force the button to 44px. Pass a sole non-negative safe-integer section quantity through `count` with a localized full spoken `countLabel`; never concatenate it into `label` or put a bare number in `badge`. For a contextual phrase such as "3 lines unpriced", `badge` can appear beside `status`, and either may be `undefined`. `ListItem.trailing` is dormant; use `ListActionRow` when primary and trailing actions need sibling native buttons. Its `label`, `icon`, and `trailingActionIcon` slots are also dormant and cannot contain controls. Let panes relocate at narrow widths instead of shrinking targets. Dividers are chrome and earn their place: a `Toolbar` draws none by default, and the line between pinned chrome and scrolling content comes from scroll state (rule 16a), so never add a border, a permanent `dividerSides`, or a margin to fake or adjust one.
258
258
  For a toolbar heading that must remain at the 20px `--kui-font-l` step, use `ToolbarText size="xlarge-fixed"`; `xlarge` retains the responsive page-title clamp. Pass `headingLevel` when it is a section landmark.
259
259
 
260
260
  18. When a recurring concept has no matching export or production recipe, keep its semantics in a thin application adapter while reusing the public layout vocabulary. The composer recipe uses one visible form surface, toolbar title/supporting-copy ids, shared 8px field/action gutters, 24px major rhythm, and a conditional StateBanner as its only nested semantic surface; do not turn every section into a card or double-inset intrinsically bordered controls. For multi-line banner copy use `copyLayout="stacked"`; use `actionPlacement="below"` for an action on its own trailing row. The application-local `../docs/examples/command-palette-adapter.tsx` is reference source for one such missing concept, not an `@kerfjs/ui` runtime export or catalog recipe. The application owns its registration, ranking, history, permissions, availability, shortcut policy, focus policy, dispatch, and copy. If a missing concept recurs across products, open an upstream component or recipe request.
@@ -363,7 +363,7 @@ Navigation transition continuity: `wireNavStack` slides the incoming view over t
363
363
 
364
364
  For a tab scene with drill-down, place a `NavStack` directly in `TabNavigatorTab.content`. Give its views structured `toolbar` and `pane` configuration and omit both stack and view `bottomToolbar`; the navigator bar remains the only bottom chrome. Wire the navigator and stack on their respective roots. See `docs/tab-navigator.md` and the catalog's **NavStack inside a tab** example.
365
365
 
366
- Use `Pane.deepInset` (also in `PaneConfig`) or `TabNavigatorTab.deepInset` for a plain scene when its scrolling content needs a 16px outer gutter. The extra 8px is applied to the scroll surface, while nested Row/List/Grid item margins stay 8px. When a tab scene is a sole Pane or NavStack, configure deep inset on that child's Pane; the tab bar and top toolbar stay full width.
366
+ Use `Pane.deepInset` (also in `PaneConfig`) or `TabNavigatorTab.deepInset` for a plain scene when its scrolling content needs an extra 8px on all four sides, giving ordinary items a 16px inline gutter. The extra 8px is applied to the scroll surface, while nested Row/List/Grid item margins stay 8px. When a tab scene is a sole Pane or NavStack, configure deep inset on that child's Pane; the tab bar and top toolbar stay full width.
367
367
  An edge-to-edge table or similar Pane child can read `--kui-pane-content-inset-inline-start` and `--kui-pane-content-inset-inline-end`: negate them for the child's logical margins, then add them to the child's text padding. The inherited values include routed safe areas and `deepInset`; keep the child in the Pane's single scroll slot. See `docs/app-layouts.md` for the CSS recipe.
368
368
 
369
369
  Expanded search groups accept `expandedOverflow="visible"` to anchor app-owned suggestion/date/help surfaces below their relative root; collapsed/default groups retain existing clipping. Apps own surface placement/dismissal and use `data-token-search-keep-open` or `collapsible.keepOpenOn` for focus.
@@ -1,6 +1,6 @@
1
1
  # Web Awesome JSX signatures for the UI authoring corpus
2
2
 
3
- Generated from the emitted `@kerfjs/ui@5.0.0-beta.79` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
3
+ Generated from the emitted `@kerfjs/ui@5.0.0-beta.80` declaration boundary. Import `@kerfjs/ui/webawesome` for type effects when authoring direct `wa-*` JSX. The module emits no runtime behavior and does not register custom elements.
4
4
 
5
5
  ```ts
6
6
  import { KerfCustomElement } from 'kerfjs/jsx-runtime';
@@ -1,4 +1,4 @@
1
1
  import '../styles/foundation.css';
2
2
  import '../styles/badge.css';
3
- import '../styles/components/navigation/tab-scaffold/tab-scaffold.css';
3
+ import '../styles/tab-navigator.css';
4
4
  export * from '../tab-navigator.js';
package/dist/catalog.js CHANGED
@@ -1,10 +1,10 @@
1
- import { Workbench } from './chunk-DSLRNR43.js';
1
+ import { Workbench } from './chunk-LQKY5S4F.js';
2
2
 
3
3
 
4
4
  import { PopupMenu } from './chunk-NJZMR7HM.js';
5
5
 
6
6
  import { Row } from './chunk-5D3WDMIK.js';
7
- import { ListHeader } from './chunk-M6BWQMQY.js';
7
+ import { ListHeader } from './chunk-VIGVNDXZ.js';
8
8
  import { Text } from './chunk-4Y4L5YEI.js';
9
9
  import { ListInsetText } from './chunk-NMWDHZVM.js';
10
10
  import { ListItem } from './chunk-HFQDSSED.js';
@@ -7,6 +7,8 @@ function SunkenPanel({
7
7
  shape = "rounded",
8
8
  flex = false,
9
9
  fill = false,
10
+ tabIndex,
11
+ outlined = false,
10
12
  className = "",
11
13
  slot
12
14
  }) {
@@ -19,9 +21,11 @@ function SunkenPanel({
19
21
  "data-shape": shape,
20
22
  "data-flex": String(Boolean(flex)),
21
23
  "data-fill": fill ? "true" : void 0,
24
+ "data-outlined": outlined ? "true" : void 0,
22
25
  style: flexValue ? `--_kui-sunken-panel-flex:${flexValue}` : void 0,
23
26
  role: ariaLabel ? "region" : void 0,
24
27
  "aria-label": ariaLabel,
28
+ tabindex: tabIndex,
25
29
  slot,
26
30
  children
27
31
  }
@@ -30,4 +34,4 @@ function SunkenPanel({
30
34
 
31
35
  export { SunkenPanel };
32
36
 
33
- //# sourceMappingURL=chunk-HPREYDKV.js.map
37
+ //# sourceMappingURL=chunk-3XPGJUUQ.js.map