@kerfjs/ui 5.0.0-beta.105 → 5.0.0-beta.107

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 (227) hide show
  1. package/README.md +2 -1
  2. package/ai/application-ui-diagnostic-ids-v1.json +1 -0
  3. package/ai/application-ui-profile.defaults.json +6 -1
  4. package/ai/component-catalog.json +160 -27
  5. package/ai/component-composition-rules.json +20 -2
  6. package/ai/component-composition.d.ts +11 -0
  7. package/ai/component-composition.json +118 -19
  8. package/ai/component-composition.schema.json +32 -0
  9. package/ai/components/README.md +3 -2
  10. package/ai/components/button.md +4 -1
  11. package/ai/components/checkbox-group.md +89 -0
  12. package/ai/components/checkbox.md +3 -2
  13. package/ai/components/color-picker.md +10 -7
  14. package/ai/components/disclosure.md +6 -1
  15. package/ai/components/foundation.md +1 -1
  16. package/ai/components/list-detail-dialog.md +1 -1
  17. package/ai/components/message-composer.md +2 -1
  18. package/ai/components/radio-group.md +4 -3
  19. package/ai/components/split-button.md +5 -2
  20. package/ai/components/surface-scaffold.md +2 -1
  21. package/ai/components/switch.md +1 -1
  22. package/ai/components/text.md +2 -0
  23. package/ai/components/wa-checkbox-group.md +5 -5
  24. package/ai/components/workbench-dialog.md +1 -1
  25. package/ai/public-api-signatures-v1.md +198 -26
  26. package/ai/skill.md +73 -62
  27. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  28. package/dist/browser/checkbox-group.js +5 -0
  29. package/dist/button.d.ts +15 -2
  30. package/dist/button.js +1 -1
  31. package/dist/catalog.js +9 -8
  32. package/dist/catalog.js.map +1 -1
  33. package/dist/checkbox-group.d.ts +70 -0
  34. package/dist/checkbox-group.js +7 -0
  35. package/dist/checkbox-group.js.map +1 -0
  36. package/dist/{chunk-SQ2Q3OQI.js → chunk-2LM3PYHA.js} +2 -2
  37. package/dist/{chunk-SQ2Q3OQI.js.map → chunk-2LM3PYHA.js.map} +1 -1
  38. package/dist/{chunk-HYORKKYP.js → chunk-3VZ6SFD4.js} +1 -1
  39. package/dist/chunk-3VZ6SFD4.js.map +1 -0
  40. package/dist/{chunk-EKBMUXFV.js → chunk-3Z7N2NAZ.js} +141 -54
  41. package/dist/chunk-3Z7N2NAZ.js.map +1 -0
  42. package/dist/{chunk-FKKJHGN5.js → chunk-AZ3WT77F.js} +2 -2
  43. package/dist/{chunk-FKKJHGN5.js.map → chunk-AZ3WT77F.js.map} +1 -1
  44. package/dist/{chunk-FG6U7KQQ.js → chunk-DNWWF6UN.js} +2 -2
  45. package/dist/{chunk-FG6U7KQQ.js.map → chunk-DNWWF6UN.js.map} +1 -1
  46. package/dist/{chunk-C3RLKKFQ.js → chunk-EYDXFOSH.js} +13 -8
  47. package/dist/chunk-EYDXFOSH.js.map +1 -0
  48. package/dist/{chunk-TAHR457X.js → chunk-G4DRH4A5.js} +3 -1
  49. package/dist/chunk-G4DRH4A5.js.map +1 -0
  50. package/dist/{chunk-PE2KPZDW.js → chunk-HDXUHLTQ.js} +7 -2
  51. package/dist/chunk-HDXUHLTQ.js.map +1 -0
  52. package/dist/chunk-HU5IHXCI.js +73 -0
  53. package/dist/chunk-HU5IHXCI.js.map +1 -0
  54. package/dist/{chunk-DF2YKGIX.js → chunk-JCCN3U6X.js} +2 -2
  55. package/dist/{chunk-DF2YKGIX.js.map → chunk-JCCN3U6X.js.map} +1 -1
  56. package/dist/{chunk-GCAK23PU.js → chunk-MVKHSAKE.js} +2 -2
  57. package/dist/{chunk-GCAK23PU.js.map → chunk-MVKHSAKE.js.map} +1 -1
  58. package/dist/{chunk-DDSCVEO5.js → chunk-NLMEDZRM.js} +5 -5
  59. package/dist/{chunk-DDSCVEO5.js.map → chunk-NLMEDZRM.js.map} +1 -1
  60. package/dist/{chunk-ECFCAKWE.js → chunk-NR6O74ST.js} +5 -1
  61. package/dist/chunk-NR6O74ST.js.map +1 -0
  62. package/dist/{chunk-7W3GKMN4.js → chunk-OQP3PFKQ.js} +7 -4
  63. package/dist/chunk-OQP3PFKQ.js.map +1 -0
  64. package/dist/{chunk-WJKHLOP5.js → chunk-TI2GV36S.js} +8 -2
  65. package/dist/chunk-TI2GV36S.js.map +1 -0
  66. package/dist/{chunk-UTCAOOXU.js → chunk-TXRKCGT3.js} +8 -2
  67. package/dist/chunk-TXRKCGT3.js.map +1 -0
  68. package/dist/{chunk-ACUKTVS3.js → chunk-XWNXGFX3.js} +52 -42
  69. package/dist/chunk-XWNXGFX3.js.map +1 -0
  70. package/dist/{chunk-ZXZAECHZ.js → chunk-Z4DS77U2.js} +2 -2
  71. package/dist/{chunk-ZXZAECHZ.js.map → chunk-Z4DS77U2.js.map} +1 -1
  72. package/dist/collapsible-panel.js +2 -2
  73. package/dist/color-picker.d.ts +30 -5
  74. package/dist/color-picker.js +1 -1
  75. package/dist/disclosure.d.ts +13 -4
  76. package/dist/disclosure.js +1 -1
  77. package/dist/disclosure.js.map +1 -1
  78. package/dist/help-tags-register.js +1 -1
  79. package/dist/index.d.ts +2 -1
  80. package/dist/index.js +20 -19
  81. package/dist/list-action-row.js +1 -1
  82. package/dist/list-detail-dialog.d.ts +7 -1
  83. package/dist/list-detail-dialog.js +6 -4
  84. package/dist/list-detail-dialog.js.map +1 -1
  85. package/dist/list-header.d.ts +8 -1
  86. package/dist/list-header.js +1 -1
  87. package/dist/nav-stack.js +1 -1
  88. package/dist/popup-menu-register.js +2 -2
  89. package/dist/popup-menu.d.ts +15 -5
  90. package/dist/popup-menu.js +1 -1
  91. package/dist/select-register.js +3 -3
  92. package/dist/split-button.d.ts +14 -2
  93. package/dist/split-button.js +1 -1
  94. package/dist/split-view.js +1 -1
  95. package/dist/styles/anchored-surface.css +3 -0
  96. package/dist/styles/button.css +29 -0
  97. package/dist/styles/catalog/components/catalog-example.css +55 -18
  98. package/dist/styles/checkbox-group.css +63 -0
  99. package/dist/styles/checkbox.css +7 -6
  100. package/dist/styles/color-picker.css +69 -34
  101. package/dist/styles/components/actions/button/button.css +29 -0
  102. package/dist/styles/components/actions/split-button/split-button.css +51 -5
  103. package/dist/styles/components/collections/framed-list/framed-list.css +4 -0
  104. package/dist/styles/components/collections/list/list.css +6 -0
  105. package/dist/styles/components/collections/list-header/list-header.css +7 -0
  106. package/dist/styles/components/feedback/anchored-surface/anchored-surface.css +3 -0
  107. package/dist/styles/components/forms/checkbox/checkbox.css +7 -6
  108. package/dist/styles/components/forms/checkbox-group/checkbox-group.css +63 -0
  109. package/dist/styles/components/forms/color-picker/color-picker.css +69 -34
  110. package/dist/styles/components/forms/radio-group/radio-group.css +7 -5
  111. package/dist/styles/components/forms/select/select.css +6 -2
  112. package/dist/styles/components/forms/switch/switch.css +3 -3
  113. package/dist/styles/components/layout/pane/pane.css +9 -0
  114. package/dist/styles/components/layout/workbench/workbench.css +4 -0
  115. package/dist/styles/components/navigation/nav-stack/nav-stack.css +64 -1
  116. package/dist/styles/components/navigation/tab-navigator/tab-navigator.css +1 -0
  117. package/dist/styles/components/surfaces/content-item/content-item.css +30 -0
  118. package/dist/styles/components/surfaces/disclosure/disclosure.css +38 -0
  119. package/dist/styles/components/surfaces/sunken-panel/sunken-panel.css +3 -0
  120. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +6 -0
  121. package/dist/styles/components/typography/text/text.css +10 -0
  122. package/dist/styles/content-item.css +30 -0
  123. package/dist/styles/disclosure.css +38 -0
  124. package/dist/styles/foundation.css +21 -0
  125. package/dist/styles/framed-list.css +4 -0
  126. package/dist/styles/list-header.css +7 -0
  127. package/dist/styles/list.css +6 -0
  128. package/dist/styles/nav-stack.css +64 -1
  129. package/dist/styles/pane.css +9 -0
  130. package/dist/styles/radio-group.css +7 -5
  131. package/dist/styles/select.css +6 -2
  132. package/dist/styles/split-button.css +51 -5
  133. package/dist/styles/styles.css +1 -0
  134. package/dist/styles/sunken-panel.css +3 -0
  135. package/dist/styles/surface-scaffold.css +6 -0
  136. package/dist/styles/switch.css +3 -3
  137. package/dist/styles/tab-navigator.css +1 -0
  138. package/dist/styles/text.css +10 -0
  139. package/dist/styles/webawesome.css +45 -9
  140. package/dist/styles/workbench.css +4 -0
  141. package/dist/surface-scaffold.d.ts +7 -1
  142. package/dist/surface-scaffold.js +1 -1
  143. package/dist/wire-color-fields.js +2 -2
  144. package/dist/wire-color-pickers.d.ts +4 -3
  145. package/dist/wire-color-pickers.js +1 -1
  146. package/dist/wire-comboboxes.js +1 -1
  147. package/dist/workbench-dialog.d.ts +11 -2
  148. package/dist/workbench-dialog.js +5 -2
  149. package/dist/workbench-dialog.js.map +1 -1
  150. package/dist/workbench.js +1 -1
  151. package/docs/accessibility.md +40 -1
  152. package/docs/anchored-surface.md +6 -1
  153. package/docs/catalog.md +26 -1
  154. package/docs/color-picker.md +39 -8
  155. package/docs/component-contract.md +33 -2
  156. package/docs/component-selection.md +20 -9
  157. package/docs/design/templates/button/full-width-dark.svg +1 -0
  158. package/docs/design/templates/button/full-width.svg +1 -0
  159. package/docs/design/templates/button-dark.svg +1 -1
  160. package/docs/design/templates/button.svg +1 -1
  161. package/docs/design/templates/checkbox/error-dark.svg +1 -1
  162. package/docs/design/templates/checkbox/error.svg +1 -1
  163. package/docs/design/templates/checkbox/states-dark.svg +1 -1
  164. package/docs/design/templates/checkbox/states.svg +1 -1
  165. package/docs/design/templates/checkbox-dark.svg +1 -1
  166. package/docs/design/templates/checkbox-group/horizontal-dark.svg +1 -0
  167. package/docs/design/templates/checkbox-group/horizontal.svg +1 -0
  168. package/docs/design/templates/checkbox-group/vertical-dark.svg +1 -0
  169. package/docs/design/templates/checkbox-group/vertical.svg +1 -0
  170. package/docs/design/templates/checkbox-group-dark.svg +1 -0
  171. package/docs/design/templates/checkbox-group.svg +1 -0
  172. package/docs/design/templates/checkbox.svg +1 -1
  173. package/docs/design/templates/color-picker/gray-dark.svg +1 -1
  174. package/docs/design/templates/color-picker/gray.svg +1 -1
  175. package/docs/design/templates/color-picker/hsb-dark.svg +1 -0
  176. package/docs/design/templates/color-picker/hsb.svg +1 -0
  177. package/docs/design/templates/color-picker/rgb-dark.svg +1 -1
  178. package/docs/design/templates/color-picker/rgb.svg +1 -1
  179. package/docs/design/templates/color-picker-dark.svg +1 -1
  180. package/docs/design/templates/color-picker.svg +1 -1
  181. package/docs/design/templates/disclosure/embedded-dark.svg +1 -0
  182. package/docs/design/templates/disclosure/embedded.svg +1 -0
  183. package/docs/design/templates/disclosure-dark.svg +1 -1
  184. package/docs/design/templates/disclosure.svg +1 -1
  185. package/docs/design/templates/radio-group/horizontal-dark.svg +1 -1
  186. package/docs/design/templates/radio-group/horizontal.svg +1 -1
  187. package/docs/design/templates/radio-group/vertical-dark.svg +1 -1
  188. package/docs/design/templates/radio-group/vertical.svg +1 -1
  189. package/docs/design/templates/radio-group-dark.svg +1 -1
  190. package/docs/design/templates/radio-group.svg +1 -1
  191. package/docs/design/templates/split-button/full-width-dark.svg +1 -0
  192. package/docs/design/templates/split-button/full-width.svg +1 -0
  193. package/docs/design/templates/split-button/pill-dark.svg +1 -0
  194. package/docs/design/templates/split-button/pill.svg +1 -0
  195. package/docs/design/templates/split-button/states-dark.svg +1 -0
  196. package/docs/design/templates/split-button/states.svg +1 -0
  197. package/docs/design/templates/split-button/tones-dark.svg +1 -0
  198. package/docs/design/templates/split-button/tones.svg +1 -0
  199. package/docs/design/templates/split-button-dark.svg +1 -0
  200. package/docs/design/templates/split-button.svg +1 -0
  201. package/docs/design/templates/switch/states-dark.svg +1 -1
  202. package/docs/design/templates/switch/states.svg +1 -1
  203. package/docs/design/templates/switch-dark.svg +1 -1
  204. package/docs/design/templates/switch.svg +1 -1
  205. package/docs/design/templates.md +10 -3
  206. package/docs/disclosure.md +34 -0
  207. package/docs/list-detail-dialog.md +4 -0
  208. package/docs/nav-stack.md +6 -2
  209. package/docs/surface-scaffold.md +6 -2
  210. package/docs/ui-doctor.md +16 -4
  211. package/docs/webawesome-migration.md +1 -1
  212. package/docs/webawesome-theme.md +48 -16
  213. package/docs/workbench-dialog.md +4 -1
  214. package/docs/writing.md +3 -3
  215. package/doctor/copy-capitalization-checks.mjs +10 -7
  216. package/llms.txt +50 -20
  217. package/package.json +7 -1
  218. package/dist/chunk-7W3GKMN4.js.map +0 -1
  219. package/dist/chunk-ACUKTVS3.js.map +0 -1
  220. package/dist/chunk-C3RLKKFQ.js.map +0 -1
  221. package/dist/chunk-ECFCAKWE.js.map +0 -1
  222. package/dist/chunk-EKBMUXFV.js.map +0 -1
  223. package/dist/chunk-HYORKKYP.js.map +0 -1
  224. package/dist/chunk-PE2KPZDW.js.map +0 -1
  225. package/dist/chunk-TAHR457X.js.map +0 -1
  226. package/dist/chunk-UTCAOOXU.js.map +0 -1
  227. package/dist/chunk-WJKHLOP5.js.map +0 -1
@@ -344,6 +344,9 @@
344
344
  "--kui-color-surface-lowered",
345
345
  "--kui-sunken-texture",
346
346
  "--kui-sunken-surface",
347
+ "--kui-color-surface-current",
348
+ "--kui-color-surface-current-region",
349
+ "--kui-color-surface-current-tint",
347
350
  "--kui-chrome-material",
348
351
  "--kui-chrome-material-raised",
349
352
  "--kui-chrome-material-filter",
@@ -2055,7 +2058,7 @@
2055
2058
  "obligations": [
2056
2059
  "DialogSurface is a native <dialog> opened modally (showModal): it is in the top layer, the rest of the page is inert, focus stays inside, and it returns to the opener on close. Focus starts on an autofocus element, otherwise on the panel.",
2057
2060
  "Name it with label (also its title text) or labelledBy (the id of a heading, such as a ToolbarText). The close button is named Close (closeLabel) and Escape asks to close; the cancelable kui-request-close event reports source close-button, escape, or backdrop.",
2058
- "Name a PopoverDialogSurface with label or labelledBy, and describe it with describedBy (rendered as aria-describedby), usually a title ToolbarText descriptionId.",
2061
+ "Name a PopoverDialogSurface with label or labelledBy. Describe either surface with describedBy (rendered as aria-describedby on the dialog root), usually the heading ToolbarText descriptionId or app-owned summary copy in the body; rootAttributes accepts only data-* attributes, so it never sets aria-describedby.",
2059
2062
  "A floating header's title uses the normal text color, which keeps WCAG AA over the 80% raised chrome material on any content; the material turns opaque under prefers-reduced-transparency, prefers-contrast: more, forced colors, or without backdrop-filter.",
2060
2063
  "A resizable bottom sheet's grabber is a focusable horizontal separator (\"Resize sheet\") with aria-valuemin, aria-valuenow, and aria-valuemax in visible px; the arrow keys, Page Up/Down, Home, and End resize it, and its slide motion is skipped under prefers-reduced-motion.",
2061
2064
  "Use popup surfaces only for real dropdown menus with named triggers."
@@ -2290,7 +2293,7 @@
2290
2293
  "accessibility": {
2291
2294
  "obligations": [
2292
2295
  "It is a native modal <dialog> named by label: the page is inert, focus stays inside, Escape asks to close, and focus returns to the opener on close.",
2293
- "On roomy devices the detail title is a level-2 heading; every presentation keeps a close control named Close (closeLabel), and the compact back control is named by backLabel."
2296
+ "Pass describedBy, the id of app-owned summary copy, for the dialog's accessible description; the detail description describes the selection and is not wired. On roomy devices the detail title is a level-2 heading; every presentation keeps a close control named Close (closeLabel), and the compact back control is named by backLabel."
2294
2297
  ]
2295
2298
  },
2296
2299
  "cssValueProps": [],
@@ -2389,7 +2392,7 @@
2389
2392
  "accessibility": {
2390
2393
  "obligations": [
2391
2394
  "It is a native modal <dialog> named by label: the page is inert, focus stays inside, Escape asks to close, and focus returns to the opener on close.",
2392
- "The title is a level-2 heading; the close control is named Close (closeLabel) and stays the outermost trailing control. An open overlay rail takes focus, keeps Tab inside it, and closes on Escape before the dialog does."
2395
+ "The title is a level-2 heading, and its description (id <id>-description) is the dialog's accessible description unless describedBy names other content; the close control is named Close (closeLabel) and stays the outermost trailing control. An open overlay rail takes focus, keeps Tab inside it, and closes on Escape before the dialog does."
2393
2396
  ]
2394
2397
  },
2395
2398
  "cssValueProps": [],
@@ -2653,8 +2656,12 @@
2653
2656
  "diagnostics": [],
2654
2657
  "provenance": {
2655
2658
  "selection": "ai/component-catalog.json",
2656
- "composition": "generated-permissive-default"
2657
- }
2659
+ "composition": "docs/disclosure.md#embedded-in-a-content-item"
2660
+ },
2661
+ "insetFreeWhen": [
2662
+ { "prop": "flush", "equals": true },
2663
+ { "prop": "variant", "equals": "span" }
2664
+ ]
2658
2665
  },
2659
2666
  {
2660
2667
  "key": "@kerfjs/ui:headers",
@@ -4148,7 +4155,8 @@
4148
4155
  "border": "self",
4149
4156
  "padding": "self",
4150
4157
  "notes": [
4151
- "The root takes the content-item geometry (8px inline margin, a 1px border, 8px padding); the content starts on the title's text column."
4158
+ "The root takes the content-item geometry (8px inline margin, a 1px border, 8px padding); the content starts on the title's text column.",
4159
+ "appearance embedded drops that geometry inside a ContentItem, which already owns it: the arrow sits on the item's content inset, the summary row is at least 24px tall, and the content (a flush Text or other content without an inset) starts on the title's text column 8px below the summary. Do not use embedded outside owner chrome."
4152
4160
  ]
4153
4161
  }
4154
4162
  },
@@ -4173,8 +4181,9 @@
4173
4181
  "diagnostics": [],
4174
4182
  "provenance": {
4175
4183
  "selection": "ai/component-catalog.json",
4176
- "composition": "generated-permissive-default"
4177
- }
4184
+ "composition": "docs/disclosure.md#embedded-in-a-content-item"
4185
+ },
4186
+ "insetFreeChildren": [{ "prop": "appearance", "equals": "embedded" }]
4178
4187
  },
4179
4188
  {
4180
4189
  "key": "@kerfjs/ui:application-tabs",
@@ -4747,14 +4756,17 @@
4747
4756
  "border": "self",
4748
4757
  "padding": "self",
4749
4758
  "notes": [
4750
- "The panel takes the shared block-content inset and a 320px maximum width; its rows are 12px apart."
4759
+ "The panel takes the shared block-content inset and a 320px maximum width; its rows are 12px apart and its sliders form one group 8px apart."
4751
4760
  ]
4752
4761
  }
4753
4762
  },
4754
4763
  "accessibility": {
4755
4764
  "obligations": [
4756
4765
  "The area is a slider named Saturation and Brightness with a spoken value; the arrow keys move it (Shift for 10% steps), Page Up and Down change brightness, Home and End saturation.",
4757
- "Hue and opacity are native range inputs; every field is named (Red, Hue, Hex Color, Opacity); the picker is a named group."
4766
+ "Every channel slider and the opacity slider is a native range input named for its channel (Red, Hue, Lightness, Gray Level, Opacity) with a spoken value in the channel's unit (210°, 40%, or a 0–255 byte); every field is named (Red, Hue, Hex Color, Opacity); the picker is a named group.",
4767
+ "The saturation and brightness area and every slider track carry a 1px inner edge halfway to the text color, so an area or track as dark or as light as the surface around it keeps at least 3:1 non-text contrast against it; increased contrast draws the edge in the full text color and forced colors in CanvasText.",
4768
+ "Every thumb draws a two-tone ring, a white border inside a dark outline, so it keeps at least 3:1 non-text contrast against light and dark area and track colors alike.",
4769
+ "Every slider track runs in the writing direction, as the native range does: in right-to-left text its gradient starts at the right, under the thumb at the channel's minimum."
4758
4770
  ]
4759
4771
  },
4760
4772
  "cssValueProps": [],
@@ -4765,7 +4777,8 @@
4765
4777
  "kui-color-picker__surface",
4766
4778
  "kui-color-picker__area",
4767
4779
  "kui-color-picker__area-thumb",
4768
- "kui-color-picker__hue",
4780
+ "kui-color-picker__sliders",
4781
+ "kui-color-picker__slider",
4769
4782
  "kui-color-picker__opacity",
4770
4783
  "kui-color-picker__controls",
4771
4784
  "kui-color-picker__mode-wrap",
@@ -4884,8 +4897,15 @@
4884
4897
  "contexts": ["chat-composer", "dialog-floating-footer"],
4885
4898
  "zones": [
4886
4899
  {
4887
- "id": "actions",
4888
- "jsx": { "prop": "actions" },
4900
+ "id": "leading",
4901
+ "jsx": { "prop": "leading" },
4902
+ "accepts": ["KerfUiContent"],
4903
+ "cardinality": { "min": 0, "max": "unbounded" },
4904
+ "exclusiveWith": []
4905
+ },
4906
+ {
4907
+ "id": "trailing",
4908
+ "jsx": { "prop": "trailing" },
4889
4909
  "accepts": ["KerfUiContent"],
4890
4910
  "cardinality": { "min": 1, "max": "unbounded" },
4891
4911
  "exclusiveWith": []
@@ -5547,7 +5567,7 @@
5547
5567
  "border": "self",
5548
5568
  "padding": "none",
5549
5569
  "notes": [
5550
- "A 20px box (18px compact) 8px (7px) before its label; hint and error align with the label text."
5570
+ "A 20px box (18px compact) 8px (7px) before its label; hint and error sit directly under the label, on its text column, like a grouped choice hint. Stack standalone Checkboxes in a List gap=\"xs\" (8px), the same 28px pitch as RadioGroup choices."
5551
5571
  ]
5552
5572
  }
5553
5573
  },
@@ -5592,6 +5612,82 @@
5592
5612
  "composition": "generated-permissive-default"
5593
5613
  }
5594
5614
  },
5615
+ {
5616
+ "key": "@kerfjs/ui:checkbox-group",
5617
+ "package": "@kerfjs/ui",
5618
+ "id": "checkbox-group",
5619
+ "name": "CheckboxGroup",
5620
+ "kind": "component",
5621
+ "source": "kerf",
5622
+ "parents": { "mode": "any", "entries": [] },
5623
+ "contexts": ["application-ui"],
5624
+ "zones": [],
5625
+ "children": { "mode": "any", "concepts": [], "requiredConcepts": [] },
5626
+ "state": [
5627
+ { "id": "checked values", "owner": "application", "required": false },
5628
+ {
5629
+ "id": "choices and their copy",
5630
+ "owner": "application",
5631
+ "required": false
5632
+ },
5633
+ {
5634
+ "id": "validation and error copy",
5635
+ "owner": "application",
5636
+ "required": false
5637
+ }
5638
+ ],
5639
+ "wiring": {
5640
+ "required": false,
5641
+ "helpers": [],
5642
+ "obligations": [],
5643
+ "stateAttributes": []
5644
+ },
5645
+ "responsive": { "owner": "not-applicable", "behaviors": [] },
5646
+ "layout": {
5647
+ "roles": ["controls"],
5648
+ "geometry": {
5649
+ "margin": "none",
5650
+ "border": "none",
5651
+ "padding": "self",
5652
+ "notes": [
5653
+ "Laid out exactly like a RadioGroup: the label and hint sit on the text column (1px + 8px); each choice's 20px box (18px compact) sits on the group's inline-start edge 8px (7px) before its label; choices stack 8px apart (--kui-space-xs), the same 28px pitch as RadioGroup choices, wrap with a 24px column gap when horizontal, and a choice hint sits directly under its label text."
5654
+ ]
5655
+ }
5656
+ },
5657
+ "accessibility": {
5658
+ "obligations": [
5659
+ "A native <fieldset> (a group) named by its legend (label), ariaLabel, or labelledBy; each choice is a Kerf Checkbox, a native checkbox inside its <label>, invisibly over the drawn box. Every checkbox is its own Tab stop and Space toggles it; each checked value submits under name.",
5660
+ "The group's hint and error form its aria-description; an error sets aria-invalid on the group and the danger border on each box. A choice's hint is that checkbox's aria-description. disabled disables the fieldset, and so every checkbox, natively."
5661
+ ]
5662
+ },
5663
+ "cssValueProps": [],
5664
+ "boundaries": {
5665
+ "rootClass": "kui-checkbox-group",
5666
+ "publicClasses": [
5667
+ "kui-checkbox-group",
5668
+ "kui-checkbox-group__label",
5669
+ "kui-checkbox-group__choices",
5670
+ "kui-checkbox-group__hint",
5671
+ "kui-checkbox-group__error"
5672
+ ],
5673
+ "rootElement": "fieldset",
5674
+ "publicTokens": [
5675
+ "--kui-color-neutral-on-quiet",
5676
+ "--kui-color-text-quiet",
5677
+ "--kui-color-danger-on-quiet",
5678
+ "--kui-font-m",
5679
+ "--kui-font-s",
5680
+ "--kui-font-xs",
5681
+ "--kui-layout-item-padding",
5682
+ "--kui-space-xs"
5683
+ ]
5684
+ },
5685
+ "diagnostics": [],
5686
+ "provenance": {
5687
+ "selection": "ai/component-catalog.json",
5688
+ "composition": "generated-permissive-default"
5689
+ }
5690
+ },
5595
5691
  {
5596
5692
  "key": "@kerfjs/ui:radio-group",
5597
5693
  "package": "@kerfjs/ui",
@@ -5630,7 +5726,7 @@
5630
5726
  "border": "none",
5631
5727
  "padding": "self",
5632
5728
  "notes": [
5633
- "The label and hint sit on the text column (1px + 8px); the choices sit on the 8px control column, 12px apart."
5729
+ "The label and hint sit on the text column (1px + 8px); each choice's 20px circle (18px compact) sits on the group's inline-start edge, where a Checkbox draws its box, 8px (7px) before its label; choices stack 8px apart (--kui-space-xs), the same 28px pitch as Checkboxes in a List gap=\"xs\", and a choice hint aligns with its label text."
5634
5730
  ]
5635
5731
  }
5636
5732
  },
@@ -5671,7 +5767,8 @@
5671
5767
  "--kui-font-s",
5672
5768
  "--kui-font-xs",
5673
5769
  "--kui-color-neutral-on-quiet",
5674
- "--kui-layout-item-padding"
5770
+ "--kui-layout-item-padding",
5771
+ "--kui-space-xs"
5675
5772
  ]
5676
5773
  },
5677
5774
  "diagnostics": [],
@@ -5713,7 +5810,7 @@
5713
5810
  "border": "self",
5714
5811
  "padding": "none",
5715
5812
  "notes": [
5716
- "A 35×20 track (31.5×18 compact) 8px (7px) before its label; the hint aligns with the label text."
5813
+ "A 35×20 track (31.5×18 compact) 8px (7px) before its label; the hint sits directly under the label, on its text column, like a Checkbox hint."
5717
5814
  ]
5718
5815
  }
5719
5816
  },
@@ -5995,7 +6092,7 @@
5995
6092
  },
5996
6093
  "accessibility": {
5997
6094
  "obligations": [
5998
- "A native <button> (type=\"button\" unless set) or, with href, an <a>; its label names it, or ariaLabel when the label is not enough. Focus shows the standard focus ring.",
6095
+ "A native <button> (type=\"button\" unless set) or, with href, an <a>; its label names it, or ariaLabel when the label is not enough. Focus shows the standard focus ring 1px outside the box; with focusRingInset it is drawn inside the box, two-tone with a surface-colored line inside the ring color, so it keeps 3:1 contrast on a loud fill of its own hue.",
5999
6096
  "Loading shows a spinner in place of the label, which still names the button, and sets aria-busy=\"true\". A disabled link drops its href and reports aria-disabled=\"true\".",
6000
6097
  "With pressed, it is a toggle: aria-pressed reports the state, and the pressed look adds a doubled outline (secondary and plain) or a darkened fill with an inset ring (primary), so the state never relies on hue alone; forced-colors mode paints it Highlight. An icon-only Button is named by ariaLabel, and help tags show that name on hover and keyboard focus. A title gives native hover text that says more than the name and replaces the help tag."
6001
6098
  ]
@@ -6079,7 +6176,8 @@
6079
6176
  },
6080
6177
  "accessibility": {
6081
6178
  "obligations": [
6082
- "Both halves are native buttons in a group; the options trigger exposes aria-haspopup, aria-expanded, and optional aria-controls, and needs optionsLabel. busy sets aria-busy on the primary."
6179
+ "Both halves are native buttons in a group; the options trigger exposes aria-haspopup, aria-expanded, and optional aria-controls, and needs optionsLabel. busy sets aria-busy on the primary.",
6180
+ "Each half draws the focus ring 1px outside its box; with focusRingInset it is drawn inside, two-tone with a surface-colored line inside the ring color, so it keeps 3:1 contrast on every loud fill."
6083
6181
  ]
6084
6182
  },
6085
6183
  "cssValueProps": [],
@@ -6105,6 +6203,7 @@
6105
6203
  "--kui-color-danger-on-loud",
6106
6204
  "--kui-focus-ring",
6107
6205
  "--kui-font-weight-semibold",
6206
+ "--kui-layout-pill-radius",
6108
6207
  "--kui-layout-rounded-radius",
6109
6208
  "--kui-space-s",
6110
6209
  "--kui-space-xs"
@@ -169,6 +169,18 @@
169
169
  "minItems": 1,
170
170
  "uniqueItems": true
171
171
  },
172
+ "insetFreeChildren": {
173
+ "description": "Prop values under which the entry's direct JSX children must bring no content-item inset. While any condition holds, a child whose layout.geometry owns its margin, border, and padding (self on all three) double-insets the entry's own geometry unless one of the child's insetFreeWhen conditions holds; eslint-plugin-kerfjs reports it as KUI-L204.",
174
+ "type": "array",
175
+ "items": { "$ref": "#/$defs/propCondition" },
176
+ "minItems": 1
177
+ },
178
+ "insetFreeWhen": {
179
+ "description": "Prop values that render this inset-bearing entry with no margin, border, or padding of its own (Text flush). Any one condition makes the element inset-free.",
180
+ "type": "array",
181
+ "items": { "$ref": "#/$defs/propCondition" },
182
+ "minItems": 1
183
+ },
172
184
  "rendersAs": {
173
185
  "description": "For a wrapper component: the cataloged roots it renders (package-qualified keys), any one of them or nothing. Composition checks treat an element of this entry as each declared root: a zone accepts it only if it accepts every root, and each root's parent contract applies to where it is placed.",
174
186
  "type": "array",
@@ -369,6 +381,26 @@
369
381
  },
370
382
  "additionalProperties": false
371
383
  },
384
+ "propCondition": {
385
+ "type": "object",
386
+ "required": ["prop", "equals"],
387
+ "properties": {
388
+ "prop": {
389
+ "type": "string",
390
+ "pattern": "^[A-Za-z][A-Za-z0-9]*$",
391
+ "description": "A JSX prop of the element."
392
+ },
393
+ "equals": {
394
+ "anyOf": [
395
+ { "type": "string" },
396
+ { "type": "number" },
397
+ { "type": "boolean" }
398
+ ],
399
+ "description": "The statically written value that satisfies the condition. A bare boolean prop (<Text flush />) equals true."
400
+ }
401
+ },
402
+ "additionalProperties": false
403
+ },
372
404
  "cssValueProp": {
373
405
  "type": "object",
374
406
  "required": [
@@ -73,12 +73,13 @@ Exact prop and callback types live in [`public-api-signatures-v1.md`](../public-
73
73
  - [Combobox](./combobox.md) — Let people pick an existing value or create a new one by typing (tags, labels, recipients), with the themed field chrome, the ARIA combobox pattern, and a touch-safe font size, without a native datalist or app CSS.
74
74
  - [TextField](./text-field.md) — Collect free text (names, addresses, notes, comments) with Kerf's field chrome, a visible or accessible label, supporting and validation text, and native input semantics.
75
75
  - [Checkbox](./checkbox.md) — Turn one independent option on or off in a form or settings list, including a parent whose children are partly checked, without Web Awesome.
76
+ - [CheckboxGroup](./checkbox-group.md) — Choose any number of a few related options that should stay visible under one group label, with native keyboard behavior and form participation.
76
77
  - [RadioGroup](./radio-group.md) — Choose exactly one of a few options that should stay visible with room for a label and hint each, with native keyboard behavior and form participation.
77
78
  - [Switch](./switch.md) — Turn a setting on or off the moment it changes, such as live updates or a preference, without Web Awesome.
78
79
  - [Slider](./slider.md) — Let a person pick a value from a range where the relative position matters more than an exact number (volume, zoom, opacity, a threshold).
79
80
  - [PopupMenu](./popup-menu.md) — A typed action menu with nested choices, selected state, and optional pointer-anchored context opening, drawn by Kerf as a native menu in the top layer.
80
81
  - [Button](./button.md) — Trigger an action from a dialog footer, a form, or content outside any Toolbar, with Kerf's button chrome and native button semantics.
81
- - [SplitButton](./split-button.md) — Render a prominent primary action plus an options trigger (provider, model, target) as one control with a semantic tone, full-width sizing, a busy presentation, and native aria-expanded on the trigger, outside any Toolbar.
82
+ - [SplitButton](./split-button.md) — Render a prominent primary action plus an options trigger (provider, model, target) as one control with a semantic tone, full-width sizing, a rounded or pill shape, a busy presentation, and native aria-expanded on the trigger, outside any Toolbar.
82
83
  - [FilePicker](./file-picker.md) — Let people choose or drop files (attachments, evidence, imports) without app CSS: a link-toned browse trigger and a dashed drop target that highlights while dragging, both around visually hidden native file inputs.
83
84
 
84
85
  ## Kerf UI — Feedback
@@ -107,7 +108,7 @@ Exact prop and callback types live in [`public-api-signatures-v1.md`](../public-
107
108
  ## Web Awesome — Forms
108
109
 
109
110
  - [Checkbox](./wa-checkbox.md) (avoid) — Ecosystem checkbox; use Kerf Checkbox.
110
- - [Checkbox Group](./wa-checkbox-group.md) (avoid) — Ecosystem checkbox group; use Kerf Checkbox rows or ChoiceChips.
111
+ - [Checkbox Group](./wa-checkbox-group.md) (avoid) — Ecosystem checkbox group; use Kerf CheckboxGroup or ChoiceChips.
111
112
  - [Color Picker](./wa-color-picker.md) (avoid) — Ecosystem color picker; use the Kerf ColorField or ColorPicker.
112
113
  - [Input](./wa-input.md) (avoid) — Ecosystem text input; use Kerf TextField.
113
114
  - [Known Date](./wa-known-date.md) — Experimental segmented field for partially or fully known dates.
@@ -13,6 +13,7 @@ Trigger an action from a dialog footer, a form, or content outside any Toolbar,
13
13
  - A dialog footer, a form, or content needs an action button: tone="brand" for the main action, tone="danger" for a destructive one, and emphasis="secondary" or "plain" for the quieter actions beside it.
14
14
  - A form submits or resets: type="submit" or "reset" (the default type never submits), with name, value, and form when needed.
15
15
  - An action navigates: href renders a link with the same chrome.
16
+ - A single primary action spans its form, sheet, or narrow column: fill makes it span the parent's available inline width, label centered, as SplitButton's fill does.
16
17
 
17
18
  **Not when:**
18
19
 
@@ -41,6 +42,7 @@ Exact prop names and types: [`@kerfjs/ui/button`](../public-api-signatures-v1.md
41
42
  - primary, secondary, or plain emphasis
42
43
  - default or compact size
43
44
  - rounded (default) or pill shape
45
+ - content width (default) or fill: spans the parent's available inline width, label centered and ellipsized when it no longer fits
44
46
  - leading icon
45
47
  - icon-only square (icon and ariaLabel, no label)
46
48
  - pressed toggle (aria-pressed) on any tone and emphasis
@@ -48,6 +50,7 @@ Exact prop names and types: [`@kerfjs/ui/button`](../public-api-signatures-v1.md
48
50
  - disabled
49
51
  - link (href)
50
52
  - badge (a count, short text, or true for a dot) over the corner, named through badgeLabel, with badgeTone
53
+ - focusRingInset: the focus ring drawn inside the box for an ancestor that clips overflow
51
54
 
52
55
  ## Composition
53
56
 
@@ -68,7 +71,7 @@ Margin: none · border: self · padding: self (layout role: controls). `self` me
68
71
 
69
72
  ## Accessibility
70
73
 
71
- - A native \<button> (type="button" unless set) or, with href, an \<a>; its label names it, or ariaLabel when the label is not enough. Focus shows the standard focus ring.
74
+ - A native \<button> (type="button" unless set) or, with href, an \<a>; its label names it, or ariaLabel when the label is not enough. Focus shows the standard focus ring 1px outside the box; with focusRingInset it is drawn inside the box, two-tone with a surface-colored line inside the ring color, so it keeps 3:1 contrast on a loud fill of its own hue.
72
75
  - Loading shows a spinner in place of the label, which still names the button, and sets aria-busy="true". A disabled link drops its href and reports aria-disabled="true".
73
76
  - With pressed, it is a toggle: aria-pressed reports the state, and the pressed look adds a doubled outline (secondary and plain) or a darkened fill with an inset ring (primary), so the state never relies on hue alone; forced-colors mode paints it Highlight. An icon-only Button is named by ariaLabel, and help tags show that name on hover and keyboard focus. A title gives native hover text that says more than the name and replaces the help tag.
74
77
 
@@ -0,0 +1,89 @@
1
+ # CheckboxGroup
2
+
3
+ <!-- Generated by scripts/sync-component-docs.mjs from ai/component-catalog.json and ai/component-composition.json. Do not edit; run `npm run catalog:sync`. -->
4
+
5
+ `@kerfjs/ui:checkbox-group` · Kerf UI · component · Controls
6
+
7
+ A native fieldset of Kerf Checkboxes with the group label above, per-choice hints, and hint and error text below, stacked or wrapping like a RadioGroup.
8
+
9
+ ## When to use
10
+
11
+ Choose any number of a few related options that should stay visible under one group label, with native keyboard behavior and form participation.
12
+
13
+ - A form asks for any of two to about eight related options under one label, each with a readable label and an optional hint, and every checked value submits under one name.
14
+
15
+ **Not when:**
16
+
17
+ - Do not use for one choice among several; use RadioGroup.
18
+ - Do not use for a single independent option or a parent with a mixed state; use Checkbox.
19
+ - Do not use for settings that apply the moment they change; use Switch.
20
+ - Do not use for many short tag-like or wrapping pill choices; use ChoiceChips multiple.
21
+
22
+ **Alternatives:**
23
+
24
+ - [Checkbox Group](./wa-checkbox-group.md) (prefer-over)
25
+ - [Checkbox](./checkbox.md) (nearest) — Use Checkbox for one independent option, or a mixed parent above its children.
26
+ - [RadioGroup](./radio-group.md) (nearest) — Use RadioGroup when exactly one option may be chosen.
27
+ - [ChoiceChips](./choice-chips.md) (nearest) — Use ChoiceChips multiple for wrapping pill choices.
28
+
29
+ ## Imports
30
+
31
+ - JavaScript: `@kerfjs/ui/checkbox-group` — exports `CheckboxGroup`.
32
+ - CSS: the browser build of `@kerfjs/ui/checkbox-group` pulls its CSS automatically; import `@kerfjs/ui/checkbox-group.css` manually only without the `browser` export condition.
33
+ - Declared side effects: browser-condition-css.
34
+
35
+ ## Props
36
+
37
+ Exact prop names and types: [`@kerfjs/ui/checkbox-group`](../public-api-signatures-v1.md#kerfjsuicheckbox-group) in `public-api-signatures-v1.md`. Do not infer props from examples.
38
+
39
+ **Variants (configure, do not restyle):**
40
+
41
+ - vertical or horizontal
42
+ - per-choice hint
43
+ - disabled choice
44
+ - whole group disabled
45
+ - hint
46
+ - error
47
+ - default or compact size
48
+
49
+ ## Composition
50
+
51
+ - Parents: any.
52
+ - Contexts: `application-ui`.
53
+ - Children: unrestricted.
54
+ - Built from: [Checkbox](./checkbox.md).
55
+
56
+ ## State and wiring
57
+
58
+ **The app owns:** checked values; choices and their copy; validation and error copy.
59
+
60
+ No wiring helper.
61
+
62
+ ## Geometry
63
+
64
+ Margin: none · border: none · padding: self (layout role: controls). `self` means the component already owns it — do not add wrapper padding, margin, or borders around it.
65
+
66
+ - Laid out exactly like a RadioGroup: the label and hint sit on the text column (1px + 8px); each choice's 20px box (18px compact) sits on the group's inline-start edge 8px (7px) before its label; choices stack 8px apart (--kui-space-xs), the same 28px pitch as RadioGroup choices, wrap with a 24px column gap when horizontal, and a choice hint sits directly under its label text.
67
+
68
+ ## Accessibility
69
+
70
+ - A native \<fieldset> (a group) named by its legend (label), ariaLabel, or labelledBy; each choice is a Kerf Checkbox, a native checkbox inside its \<label>, invisibly over the drawn box. Every checkbox is its own Tab stop and Space toggles it; each checked value submits under name.
71
+ - The group's hint and error form its aria-description; an error sets aria-invalid on the group and the danger border on each box. A choice's hint is that checkbox's aria-description. disabled disables the fieldset, and so every checkbox, natively.
72
+
73
+ ## Styling boundary
74
+
75
+ The component owns its own styles. Configure it through its props and variants; do not override its internals.
76
+
77
+ Public class hooks (select for layout placement only, never to change the component's look): `kui-checkbox-group`, `kui-checkbox-group__label`, `kui-checkbox-group__choices`, `kui-checkbox-group__hint`, `kui-checkbox-group__error`.
78
+
79
+ Never put `kui-checkbox-group`, `kui-checkbox-group__label`, `kui-checkbox-group__choices`, `kui-checkbox-group__hint`, `kui-checkbox-group__error` on an element you write; render `CheckboxGroup` instead (`KUI-L103`).
80
+
81
+ Public tokens it reads: `--kui-color-neutral-on-quiet`, `--kui-color-text-quiet`, `--kui-color-danger-on-quiet`, `--kui-font-m`, `--kui-font-s`, `--kui-font-xs`, `--kui-layout-item-padding`, `--kui-space-xs`. Set a token only where its public contract allows; prefer a prop.
82
+
83
+ Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-*` selectors (`KUI-L001`, `KUI-L010`), unknown or private tokens (`KUI-L002`, `KUI-L012`), and any application rule that restyles the component, forced dimensions included (`KUI-L019`).
84
+
85
+ ## Related
86
+
87
+ - Components: [Checkbox](./checkbox.md), [Checkbox Group](./wa-checkbox-group.md), [RadioGroup](./radio-group.md), [ChoiceChips](./choice-chips.md).
88
+ - Docs: [`docs/component-selection.md`](../../docs/component-selection.md), [`docs/component-selection.md#problem-to-component-matrix`](../../docs/component-selection.md#problem-to-component-matrix).
89
+ - UX catalog route: `?component=checkbox-group`.
@@ -22,6 +22,7 @@ Turn one independent option on or off in a form or settings list, including a pa
22
22
  **Alternatives:**
23
23
 
24
24
  - [Checkbox](./wa-checkbox.md) (prefer-over)
25
+ - [CheckboxGroup](./checkbox-group.md) (nearest) — Use CheckboxGroup for a labeled set of related options.
25
26
  - [Switch](./switch.md) (nearest) — Use Switch for a setting that applies immediately.
26
27
  - [ChoiceChips](./choice-chips.md) (nearest) — Use ChoiceChips multiple for several visible, wrapping choices.
27
28
 
@@ -63,7 +64,7 @@ Exact prop names and types: [`@kerfjs/ui/checkbox`](../public-api-signatures-v1.
63
64
 
64
65
  Margin: none · border: self · padding: none (layout role: controls). `self` means the component already owns it — do not add wrapper padding, margin, or borders around it.
65
66
 
66
- - A 20px box (18px compact) 8px (7px) before its label; hint and error align with the label text.
67
+ - A 20px box (18px compact) 8px (7px) before its label; hint and error sit directly under the label, on its text column, like a grouped choice hint. Stack standalone Checkboxes in a List gap="xs" (8px), the same 28px pitch as RadioGroup choices.
67
68
 
68
69
  ## Accessibility
69
70
 
@@ -84,6 +85,6 @@ Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-
84
85
 
85
86
  ## Related
86
87
 
87
- - Components: [LucideIcon](./lucide-icon.md), [Checkbox](./wa-checkbox.md), [Switch](./switch.md), [ChoiceChips](./choice-chips.md).
88
+ - Components: [LucideIcon](./lucide-icon.md), [Checkbox](./wa-checkbox.md), [CheckboxGroup](./checkbox-group.md), [Switch](./switch.md), [ChoiceChips](./choice-chips.md).
88
89
  - Docs: [`docs/component-selection.md`](../../docs/component-selection.md), [`docs/component-selection.md#problem-to-component-matrix`](../../docs/component-selection.md#problem-to-component-matrix).
89
90
  - UX catalog route: `?component=checkbox`.
@@ -4,7 +4,7 @@
4
4
 
5
5
  `@kerfjs/ui:color-picker` · Kerf UI · component · Controls
6
6
 
7
- A full inline color picker: a saturation and brightness area, hue and opacity sliders, an RGB/HSL/HSB/Grayscale model menu with channel fields, a hex field (8 digits below full opacity) and opacity field, a screen sampler where the EyeDropper API exists, and optional preset and sample swatches.
7
+ A full inline color picker: a saturation and brightness area, a slider per channel of the color model (red, green, and blue; hue, saturation, and lightness or brightness; or one gray level) whose tracks paint the colors each channel produces while the others hold (the hue track a fixed full-saturation rainbow), an opacity slider, an RGB/HSL/HSB/Grayscale model menu that switches the sliders and channel fields together, a hex field (8 digits below full opacity) and opacity field, a screen sampler where the EyeDropper API exists, and optional preset and sample swatches.
8
8
 
9
9
  ## When to use
10
10
 
@@ -25,7 +25,7 @@ Let a person choose any color precisely, by dragging, typing channel or hex valu
25
25
 
26
26
  ## Imports
27
27
 
28
- - JavaScript: `@kerfjs/ui/color-picker` — exports `ColorPicker`, `ColorPickerProps`, `ColorPickerMode`, `ColorPickerSamples`, `ColorPickerRootAttributes`, `colorPickerChannels`, `colorPickerAreaText`, `colorPickerStyle`.
28
+ - JavaScript: `@kerfjs/ui/color-picker` — exports `ColorPicker`, `ColorPickerProps`, `ColorPickerMode`, `ColorPickerSamples`, `ColorPickerRootAttributes`, `colorPickerChannels`, `colorPickerSlider`, `ColorPickerSliderState`, `colorPickerAreaText`, `colorPickerStyle`.
29
29
  - CSS: the browser build of `@kerfjs/ui/color-picker` pulls its CSS automatically; import `@kerfjs/ui/color-picker.css` manually only without the `browser` export condition.
30
30
  - Wiring: `wireColorPickers` from `@kerfjs/ui/wire-color-pickers` (required).
31
31
  - Declared side effects: browser-condition-css.
@@ -37,7 +37,7 @@ Exact prop names and types: [`@kerfjs/ui/color-picker`](../public-api-signatures
37
37
  **Variants (configure, do not restyle):**
38
38
 
39
39
  - with or without opacity
40
- - RGB, HSL, HSB, or Grayscale channel fields
40
+ - RGB, HSL, HSB, or Grayscale channel sliders and fields
41
41
  - presets and titled sample groups with an add button
42
42
  - screen sampler where the EyeDropper API exists
43
43
  - disabled
@@ -66,20 +66,23 @@ Exact prop names and types: [`@kerfjs/ui/color-picker`](../public-api-signatures
66
66
 
67
67
  Margin: none · border: self · padding: self (layout role: controls). `self` means the component already owns it — do not add wrapper padding, margin, or borders around it.
68
68
 
69
- - The panel takes the shared block-content inset and a 320px maximum width; its rows are 12px apart.
69
+ - The panel takes the shared block-content inset and a 320px maximum width; its rows are 12px apart and its sliders form one group 8px apart.
70
70
 
71
71
  ## Accessibility
72
72
 
73
73
  - The area is a slider named Saturation and Brightness with a spoken value; the arrow keys move it (Shift for 10% steps), Page Up and Down change brightness, Home and End saturation.
74
- - Hue and opacity are native range inputs; every field is named (Red, Hue, Hex Color, Opacity); the picker is a named group.
74
+ - Every channel slider and the opacity slider is a native range input named for its channel (Red, Hue, Lightness, Gray Level, Opacity) with a spoken value in the channel's unit (210°, 40%, or a 0–255 byte); every field is named (Red, Hue, Hex Color, Opacity); the picker is a named group.
75
+ - The saturation and brightness area and every slider track carry a 1px inner edge halfway to the text color, so an area or track as dark or as light as the surface around it keeps at least 3:1 non-text contrast against it; increased contrast draws the edge in the full text color and forced colors in CanvasText.
76
+ - Every thumb draws a two-tone ring, a white border inside a dark outline, so it keeps at least 3:1 non-text contrast against light and dark area and track colors alike.
77
+ - Every slider track runs in the writing direction, as the native range does: in right-to-left text its gradient starts at the right, under the thumb at the channel's minimum.
75
78
 
76
79
  ## Styling boundary
77
80
 
78
81
  The component owns its own styles. Configure it through its props and variants; do not override its internals.
79
82
 
80
- Public class hooks (select for layout placement only, never to change the component's look): `kui-color-picker`, `kui-color-picker__surface`, `kui-color-picker__area`, `kui-color-picker__area-thumb`, `kui-color-picker__hue`, `kui-color-picker__opacity`, `kui-color-picker__controls`, `kui-color-picker__mode-wrap`, `kui-color-picker__mode-icon`, `kui-color-picker__mode`, `kui-color-picker__tool`, `kui-color-picker__channels`, `kui-color-picker__hex-row`, `kui-color-picker__field`, `kui-color-picker__field-label`, `kui-color-picker__field--hex`.
83
+ Public class hooks (select for layout placement only, never to change the component's look): `kui-color-picker`, `kui-color-picker__surface`, `kui-color-picker__area`, `kui-color-picker__area-thumb`, `kui-color-picker__sliders`, `kui-color-picker__slider`, `kui-color-picker__opacity`, `kui-color-picker__controls`, `kui-color-picker__mode-wrap`, `kui-color-picker__mode-icon`, `kui-color-picker__mode`, `kui-color-picker__tool`, `kui-color-picker__channels`, `kui-color-picker__hex-row`, `kui-color-picker__field`, `kui-color-picker__field-label`, `kui-color-picker__field--hex`.
81
84
 
82
- Never put `kui-color-picker`, `kui-color-picker__surface`, `kui-color-picker__area`, `kui-color-picker__area-thumb`, `kui-color-picker__hue`, `kui-color-picker__opacity`, `kui-color-picker__controls`, `kui-color-picker__mode-wrap`, `kui-color-picker__mode-icon`, `kui-color-picker__mode`, `kui-color-picker__tool`, `kui-color-picker__channels`, `kui-color-picker__hex-row`, `kui-color-picker__field`, `kui-color-picker__field-label`, `kui-color-picker__field--hex` on an element you write; render `ColorPicker` instead (`KUI-L103`).
85
+ Never put `kui-color-picker`, `kui-color-picker__surface`, `kui-color-picker__area`, `kui-color-picker__area-thumb`, `kui-color-picker__sliders`, `kui-color-picker__slider`, `kui-color-picker__opacity`, `kui-color-picker__controls`, `kui-color-picker__mode-wrap`, `kui-color-picker__mode-icon`, `kui-color-picker__mode`, `kui-color-picker__tool`, `kui-color-picker__channels`, `kui-color-picker__hex-row`, `kui-color-picker__field`, `kui-color-picker__field-label`, `kui-color-picker__field--hex` on an element you write; render `ColorPicker` instead (`KUI-L103`).
83
86
 
84
87
  Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-*` selectors (`KUI-L001`, `KUI-L010`), unknown or private tokens (`KUI-L002`, `KUI-L012`), and any application rule that restyles the component, forced dimensions included (`KUI-L019`).
85
88
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  `@kerfjs/ui:disclosure` · Kerf UI · component · Structure
6
6
 
7
- Content a person shows and hides under a summary row: a native details element with a DisclosureArrow, a title, and an optional quiet detail, on content-item geometry, with plain, framed, and sunken appearances and native exclusive (accordion) groups.
7
+ Content a person shows and hides under a summary row: a native details element with a DisclosureArrow, a title, and an optional quiet detail, on content-item geometry, with plain, framed, and sunken appearances, an embedded appearance for a compact panel inside a ContentItem, and native exclusive (accordion) groups.
8
8
 
9
9
  ## When to use
10
10
 
@@ -12,6 +12,7 @@ Keep secondary or optional content one press away under a summary, or let a pers
12
12
 
13
13
  - Optional or advanced content should stay out of the way until a person asks for it (advanced settings, a build log, release notes).
14
14
  - A list of questions or sections should open one at a time: give the Disclosures the same name for a native exclusive accordion.
15
+ - A compact details panel belongs inside a ContentItem (a note card's AI feedback): use appearance embedded so the item's own inset frames it once.
15
16
 
16
17
  **Not when:**
17
18
 
@@ -36,6 +37,7 @@ Exact prop names and types: [`@kerfjs/ui/disclosure`](../public-api-signatures-v
36
37
  **Variants (configure, do not restyle):**
37
38
 
38
39
  - plain, framed, or sunken appearance
40
+ - embedded appearance inside a ContentItem or other owner chrome: no margin, border, or padding of its own and a 24px minimum summary row
39
41
  - open or closed initially
40
42
  - a quiet detail after the title
41
43
  - an exclusive accordion group sharing a name
@@ -58,6 +60,9 @@ No wiring helper.
58
60
  Margin: self · border: self · padding: self (layout role: structure). `self` means the component already owns it — do not add wrapper padding, margin, or borders around it.
59
61
 
60
62
  - The root takes the content-item geometry (8px inline margin, a 1px border, 8px padding); the content starts on the title's text column.
63
+ - appearance embedded drops that geometry inside a ContentItem, which already owns it: the arrow sits on the item's content inset, the summary row is at least 24px tall, and the content (a flush Text or other content without an inset) starts on the title's text column 8px below the summary. Do not use embedded outside owner chrome.
64
+
65
+ While `appearance="embedded"` is set, direct children must bring no content-item inset: a child that owns its margin, border, and padding double-insets this one. Use an inset-free form such as `Text flush`, or inline content (`KUI-L204`).
61
66
 
62
67
  ## Accessibility
63
68
 
@@ -55,7 +55,7 @@ Margin: none · border: none · padding: none (layout role: foundation). `self`
55
55
 
56
56
  The component owns its own styles. Configure it through its props and variants; do not override its internals.
57
57
 
58
- Public tokens it reads: `--kui-font-sans`, `--kui-font-mono`, `--kui-font-2xs`, `--kui-font-xs`, `--kui-font-s`, `--kui-font-m`, `--kui-font-l`, `--kui-font-xl`, `--kui-font-2xl`, `--kui-font-weight-semibold`, `--kui-space-none`, `--kui-space-2xs`, `--kui-space-xs`, `--kui-space-s`, `--kui-space-m`, `--kui-space-l`, `--kui-space-xl`, `--kui-bp-mobile`, `--kui-bp-tablet`, `--kui-bp-desktop`, `--kui-bp-xl-desktop`, `--kui-layout-inline-margin`, `--kui-layout-item-border-width`, `--kui-layout-item-padding`, `--kui-layout-item-gap`, `--kui-layout-content-gap`, `--kui-layout-control-gap`, `--kui-layout-metadata-gap`, `--kui-layout-highlight-inset`, `--kui-layout-rounded-radius`, `--kui-layout-pill-radius`, `--kui-toolbar-group-inner-size`, `--kui-toolbar-group-size`, `--kui-toolbar-gap`, `--kui-safe-area-block-start`, `--kui-safe-area-block-end`, `--kui-safe-area-inline-start`, `--kui-safe-area-inline-end`, `--kui-radius-s`, `--kui-radius-m`, `--kui-radius-l`, `--kui-radius-circle`, `--kui-radius-pill`, `--kui-disclosure-icon-scale`, `--kui-color-surface`, `--kui-color-surface-raised`, `--kui-color-surface-lowered`, `--kui-sunken-texture`, `--kui-sunken-surface`, `--kui-chrome-material`, `--kui-chrome-material-raised`, `--kui-chrome-material-filter`, `--kui-color-text`, `--kui-color-text-quiet`, `--kui-color-text-link`, `--kui-color-neutral-fill-quiet`, `--kui-color-neutral-fill-normal`, `--kui-color-neutral-fill-loud`, `--kui-color-neutral-border-quiet`, `--kui-color-neutral-border-normal`, `--kui-color-neutral-border-loud`, `--kui-color-neutral-on-quiet`, `--kui-color-neutral-on-normal`, `--kui-color-neutral-on-loud`, `--kui-color-brand-fill-loud`, `--kui-color-brand-fill-quiet`, `--kui-color-brand-fill-normal`, `--kui-color-brand-border-quiet`, `--kui-color-brand-border-normal`, `--kui-color-brand-border-loud`, `--kui-color-brand-on-quiet`, `--kui-color-brand-on-normal`, `--kui-color-brand-on-loud`, `--kui-color-brand-on-fill`, `--kui-color-pop-fill-loud`, `--kui-color-pop-fill-quiet`, `--kui-color-pop-fill-normal`, `--kui-color-pop-border-quiet`, `--kui-color-pop-border-normal`, `--kui-color-pop-border-loud`, `--kui-color-pop-on-quiet`, `--kui-color-pop-on-normal`, `--kui-color-pop-on-loud`, `--kui-color-pop-on-fill`, `--kui-color-success-fill-loud`, `--kui-color-success-fill-quiet`, `--kui-color-success-fill-normal`, `--kui-color-success-border-quiet`, `--kui-color-success-border-normal`, `--kui-color-success-border-loud`, `--kui-color-success-on-quiet`, `--kui-color-success-on-normal`, `--kui-color-success-on-loud`, `--kui-color-success-on-fill`, `--kui-color-warning-fill-loud`, `--kui-color-warning-fill-quiet`, `--kui-color-warning-fill-normal`, `--kui-color-warning-border-quiet`, `--kui-color-warning-border-normal`, `--kui-color-warning-border-loud`, `--kui-color-warning-on-quiet`, `--kui-color-warning-on-normal`, `--kui-color-warning-on-loud`, `--kui-color-warning-on-fill`, `--kui-color-danger-fill-loud`, `--kui-color-danger-fill-quiet`, `--kui-color-danger-fill-normal`, `--kui-color-danger-border-quiet`, `--kui-color-danger-border-normal`, `--kui-color-danger-border-loud`, `--kui-color-danger-on-quiet`, `--kui-color-danger-on-normal`, `--kui-color-danger-on-loud`, `--kui-color-danger-on-fill`, `--kui-focus-ring`, `--kui-shadow-s`, `--kui-shadow-m`, `--kui-shadow-l`. Set a token only where its public contract allows; prefer a prop.
58
+ Public tokens it reads: `--kui-font-sans`, `--kui-font-mono`, `--kui-font-2xs`, `--kui-font-xs`, `--kui-font-s`, `--kui-font-m`, `--kui-font-l`, `--kui-font-xl`, `--kui-font-2xl`, `--kui-font-weight-semibold`, `--kui-space-none`, `--kui-space-2xs`, `--kui-space-xs`, `--kui-space-s`, `--kui-space-m`, `--kui-space-l`, `--kui-space-xl`, `--kui-bp-mobile`, `--kui-bp-tablet`, `--kui-bp-desktop`, `--kui-bp-xl-desktop`, `--kui-layout-inline-margin`, `--kui-layout-item-border-width`, `--kui-layout-item-padding`, `--kui-layout-item-gap`, `--kui-layout-content-gap`, `--kui-layout-control-gap`, `--kui-layout-metadata-gap`, `--kui-layout-highlight-inset`, `--kui-layout-rounded-radius`, `--kui-layout-pill-radius`, `--kui-toolbar-group-inner-size`, `--kui-toolbar-group-size`, `--kui-toolbar-gap`, `--kui-safe-area-block-start`, `--kui-safe-area-block-end`, `--kui-safe-area-inline-start`, `--kui-safe-area-inline-end`, `--kui-radius-s`, `--kui-radius-m`, `--kui-radius-l`, `--kui-radius-circle`, `--kui-radius-pill`, `--kui-disclosure-icon-scale`, `--kui-color-surface`, `--kui-color-surface-raised`, `--kui-color-surface-lowered`, `--kui-sunken-texture`, `--kui-sunken-surface`, `--kui-color-surface-current`, `--kui-color-surface-current-region`, `--kui-color-surface-current-tint`, `--kui-chrome-material`, `--kui-chrome-material-raised`, `--kui-chrome-material-filter`, `--kui-color-text`, `--kui-color-text-quiet`, `--kui-color-text-link`, `--kui-color-neutral-fill-quiet`, `--kui-color-neutral-fill-normal`, `--kui-color-neutral-fill-loud`, `--kui-color-neutral-border-quiet`, `--kui-color-neutral-border-normal`, `--kui-color-neutral-border-loud`, `--kui-color-neutral-on-quiet`, `--kui-color-neutral-on-normal`, `--kui-color-neutral-on-loud`, `--kui-color-brand-fill-loud`, `--kui-color-brand-fill-quiet`, `--kui-color-brand-fill-normal`, `--kui-color-brand-border-quiet`, `--kui-color-brand-border-normal`, `--kui-color-brand-border-loud`, `--kui-color-brand-on-quiet`, `--kui-color-brand-on-normal`, `--kui-color-brand-on-loud`, `--kui-color-brand-on-fill`, `--kui-color-pop-fill-loud`, `--kui-color-pop-fill-quiet`, `--kui-color-pop-fill-normal`, `--kui-color-pop-border-quiet`, `--kui-color-pop-border-normal`, `--kui-color-pop-border-loud`, `--kui-color-pop-on-quiet`, `--kui-color-pop-on-normal`, `--kui-color-pop-on-loud`, `--kui-color-pop-on-fill`, `--kui-color-success-fill-loud`, `--kui-color-success-fill-quiet`, `--kui-color-success-fill-normal`, `--kui-color-success-border-quiet`, `--kui-color-success-border-normal`, `--kui-color-success-border-loud`, `--kui-color-success-on-quiet`, `--kui-color-success-on-normal`, `--kui-color-success-on-loud`, `--kui-color-success-on-fill`, `--kui-color-warning-fill-loud`, `--kui-color-warning-fill-quiet`, `--kui-color-warning-fill-normal`, `--kui-color-warning-border-quiet`, `--kui-color-warning-border-normal`, `--kui-color-warning-border-loud`, `--kui-color-warning-on-quiet`, `--kui-color-warning-on-normal`, `--kui-color-warning-on-loud`, `--kui-color-warning-on-fill`, `--kui-color-danger-fill-loud`, `--kui-color-danger-fill-quiet`, `--kui-color-danger-fill-normal`, `--kui-color-danger-border-quiet`, `--kui-color-danger-border-normal`, `--kui-color-danger-border-loud`, `--kui-color-danger-on-quiet`, `--kui-color-danger-on-normal`, `--kui-color-danger-on-loud`, `--kui-color-danger-on-fill`, `--kui-focus-ring`, `--kui-shadow-s`, `--kui-shadow-m`, `--kui-shadow-l`. Set a token only where its public contract allows; prefer a prop.
59
59
 
60
60
  Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-*` selectors (`KUI-L001`, `KUI-L010`), unknown or private tokens (`KUI-L002`, `KUI-L012`), and any application rule that restyles the component, forced dimensions included (`KUI-L019`).
61
61
 
@@ -75,7 +75,7 @@ Margin: parent · border: self · padding: child (layout role: structure). `self
75
75
  ## Accessibility
76
76
 
77
77
  - It is a native modal \<dialog> named by label: the page is inert, focus stays inside, Escape asks to close, and focus returns to the opener on close.
78
- - On roomy devices the detail title is a level-2 heading; every presentation keeps a close control named Close (closeLabel), and the compact back control is named by backLabel.
78
+ - Pass describedBy, the id of app-owned summary copy, for the dialog's accessible description; the detail description describes the selection and is not wired. On roomy devices the detail title is a level-2 heading; every presentation keeps a close control named Close (closeLabel), and the compact back control is named by backLabel.
79
79
 
80
80
  ## Styling boundary
81
81
 
@@ -50,7 +50,8 @@ Exact prop names and types: [`@kerfjs/ui/message-composer`](../public-api-signat
50
50
 
51
51
  Zones (a zone is bound to JSX only through its listed prop; never assume the zone id is a prop):
52
52
 
53
- - `actions` — JSX prop `actions`; accepts `KerfUiContent`; at least 1.
53
+ - `leading` — JSX prop `leading`; accepts `KerfUiContent`; any number.
54
+ - `trailing` — JSX prop `trailing`; accepts `KerfUiContent`; at least 1.
54
55
 
55
56
  ## State and wiring
56
57