@godxjp/ui 19.0.0 → 19.2.0

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 (445) hide show
  1. package/README.md +25 -128
  2. package/dist/app/app-provider.d.ts +1 -1
  3. package/dist/app/app-provider.js +3 -0
  4. package/dist/app/theme-axes.d.ts +4 -15
  5. package/dist/app/timezones.d.ts +1 -4
  6. package/dist/components/charts/area-chart.d.ts +2 -3
  7. package/dist/components/charts/bar-chart.d.ts +2 -3
  8. package/dist/components/charts/chart-frame.d.ts +7 -16
  9. package/dist/components/charts/chart-summary.d.ts +2 -2
  10. package/dist/components/charts/compact-bar-trend.d.ts +9 -24
  11. package/dist/components/charts/index.d.ts +1 -6
  12. package/dist/components/data-display/badge.d.ts +2 -12
  13. package/dist/components/data-display/card.d.ts +21 -41
  14. package/dist/components/data-display/carousel.d.ts +1 -2
  15. package/dist/components/data-display/code-block.d.ts +4 -0
  16. package/dist/components/data-display/code-block.js +24 -0
  17. package/dist/components/data-display/data-table.d.ts +15 -37
  18. package/dist/components/data-display/data-table.js +1 -3
  19. package/dist/components/data-display/descriptions.d.ts +2 -6
  20. package/dist/components/data-display/descriptions.js +0 -2
  21. package/dist/components/data-display/index.d.ts +4 -0
  22. package/dist/components/data-display/index.js +4 -0
  23. package/dist/components/data-display/list-row.d.ts +3 -22
  24. package/dist/components/data-display/permission-matrix.d.ts +7 -20
  25. package/dist/components/data-display/progress.d.ts +1 -1
  26. package/dist/components/data-display/prose.d.ts +4 -0
  27. package/dist/components/data-display/prose.js +20 -0
  28. package/dist/components/data-display/service-launcher-card.d.ts +3 -10
  29. package/dist/components/data-display/table.d.ts +20 -80
  30. package/dist/components/data-display/table.js +0 -3
  31. package/dist/components/data-display/timeline.d.ts +3 -5
  32. package/dist/components/data-entry/branch-scope-picker.d.ts +2 -19
  33. package/dist/components/data-entry/branch-scope-picker.js +0 -1
  34. package/dist/components/data-entry/calendar.d.ts +3 -2
  35. package/dist/components/data-entry/calendar.js +58 -3
  36. package/dist/components/data-entry/cascader.js +3 -1
  37. package/dist/components/data-entry/checkbox-group.js +12 -5
  38. package/dist/components/data-entry/checkbox.js +30 -21
  39. package/dist/components/data-entry/command-palette.d.ts +8 -22
  40. package/dist/components/data-entry/date-picker.d.ts +4 -5
  41. package/dist/components/data-entry/date-picker.js +12 -3
  42. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  43. package/dist/components/data-entry/date-range-picker.js +17 -6
  44. package/dist/components/data-entry/form-errors.d.ts +7 -18
  45. package/dist/components/data-entry/form-field.d.ts +1 -1
  46. package/dist/components/data-entry/form-field.js +15 -6
  47. package/dist/components/data-entry/form.d.ts +1 -9
  48. package/dist/components/data-entry/input.d.ts +16 -14
  49. package/dist/components/data-entry/input.js +13 -3
  50. package/dist/components/data-entry/month-picker.js +6 -4
  51. package/dist/components/data-entry/month-range-picker.d.ts +4 -6
  52. package/dist/components/data-entry/month-range-picker.js +11 -5
  53. package/dist/components/data-entry/number-input.d.ts +1 -5
  54. package/dist/components/data-entry/number-input.js +1 -0
  55. package/dist/components/data-entry/radio.js +19 -6
  56. package/dist/components/data-entry/search-select.d.ts +3 -5
  57. package/dist/components/data-entry/search-select.js +8 -2
  58. package/dist/components/data-entry/select.d.ts +15 -2
  59. package/dist/components/data-entry/select.js +57 -10
  60. package/dist/components/data-entry/switch.js +9 -2
  61. package/dist/components/data-entry/textarea.d.ts +10 -16
  62. package/dist/components/data-entry/textarea.js +8 -1
  63. package/dist/components/data-entry/time-picker.d.ts +3 -5
  64. package/dist/components/data-entry/tree-select.js +3 -1
  65. package/dist/components/data-entry/upload-types.d.ts +1 -5
  66. package/dist/components/data-entry/use-upload-draft.d.ts +0 -4
  67. package/dist/components/feedback/dialog.d.ts +6 -15
  68. package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
  69. package/dist/components/feedback/sheet.d.ts +3 -10
  70. package/dist/components/feedback/sheet.js +1 -1
  71. package/dist/components/feedback/sonner.js +2 -3
  72. package/dist/components/general/activity.d.ts +4 -30
  73. package/dist/components/general/button.js +1 -10
  74. package/dist/components/general/logo.d.ts +6 -28
  75. package/dist/components/general/reveal.d.ts +3 -9
  76. package/dist/components/general/typography.d.ts +1 -3
  77. package/dist/components/layout/account-chip.d.ts +8 -0
  78. package/dist/components/layout/account-chip.js +45 -0
  79. package/dist/components/layout/auth-divider.d.ts +2 -9
  80. package/dist/components/layout/auth-shell.d.ts +1 -14
  81. package/dist/components/layout/breadcrumb.d.ts +1 -5
  82. package/dist/components/layout/centered-shell.d.ts +4 -18
  83. package/dist/components/layout/error-surface.d.ts +11 -28
  84. package/dist/components/layout/error-surface.js +4 -1
  85. package/dist/components/layout/index.d.ts +2 -0
  86. package/dist/components/layout/index.js +2 -0
  87. package/dist/components/layout/legal-document-shell.d.ts +5 -28
  88. package/dist/components/layout/master-detail.d.ts +2 -18
  89. package/dist/components/layout/page-container.js +0 -1
  90. package/dist/components/layout/responsive-grid.d.ts +6 -1
  91. package/dist/components/layout/responsive-grid.js +9 -2
  92. package/dist/components/layout/service-role-panel.d.ts +7 -19
  93. package/dist/components/layout/sidebar.d.ts +5 -27
  94. package/dist/components/layout/sidebar.js +0 -1
  95. package/dist/components/layout/split-pane.d.ts +0 -7
  96. package/dist/components/layout/topbar.d.ts +1 -14
  97. package/dist/components/navigation/app-setting-picker.js +1 -3
  98. package/dist/components/navigation/filter-bar.d.ts +1 -6
  99. package/dist/components/navigation/pagination-utils.d.ts +2 -12
  100. package/dist/components/navigation/tabs-scroll.d.ts +2 -36
  101. package/dist/components/navigation/tabs.js +4 -8
  102. package/dist/components/query/data-state.d.ts +2 -2
  103. package/dist/components/ui/credential-reveal.d.ts +1 -4
  104. package/dist/components/ui/hover-card.d.ts +1 -2
  105. package/dist/components/ui/separator.d.ts +2 -15
  106. package/dist/components/ui/toggle-group.d.ts +1 -5
  107. package/dist/components/ui/toggle.d.ts +6 -31
  108. package/dist/email/brand-mark.d.ts +6 -34
  109. package/dist/email/color.d.ts +7 -17
  110. package/dist/email/geometry.d.ts +4 -8
  111. package/dist/email/index.d.ts +4 -48
  112. package/dist/email/inline-style.d.ts +3 -9
  113. package/dist/email/urgency.d.ts +2 -7
  114. package/dist/form/form-context.d.ts +2 -5
  115. package/dist/form/form-root.d.ts +1 -2
  116. package/dist/i18n/messages/en.json +4 -0
  117. package/dist/i18n/messages/ja.json +4 -0
  118. package/dist/i18n/messages/vi.json +4 -0
  119. package/dist/inertia/index.d.ts +2 -10
  120. package/dist/lib/control-styles.d.ts +10 -21
  121. package/dist/lib/control-styles.js +4 -6
  122. package/dist/lib/datetime/format-date.d.ts +1 -4
  123. package/dist/lib/field-a11y.d.ts +31 -36
  124. package/dist/lib/field-a11y.js +16 -2
  125. package/dist/lib/format.d.ts +4 -8
  126. package/dist/lib/hooks.d.ts +6 -12
  127. package/dist/lib/permission-grid.d.ts +3 -18
  128. package/dist/lib/query-error.d.ts +3 -3
  129. package/dist/props/components/app.prop.d.ts +19 -27
  130. package/dist/props/components/charts.prop.d.ts +2 -3
  131. package/dist/props/components/data-display.prop.d.ts +48 -72
  132. package/dist/props/components/data-entry.prop.d.ts +41 -30
  133. package/dist/props/components/form.prop.d.ts +2 -6
  134. package/dist/props/components/general.prop.d.ts +11 -27
  135. package/dist/props/components/layout.prop.d.ts +89 -260
  136. package/dist/props/components/navigation.prop.d.ts +27 -42
  137. package/dist/props/registry.d.ts +31 -0
  138. package/dist/props/registry.js +33 -0
  139. package/dist/props/vocabulary/data.prop.d.ts +18 -49
  140. package/dist/props/vocabulary/index.d.ts +1 -1
  141. package/dist/props/vocabulary/interaction.prop.d.ts +10 -12
  142. package/dist/props/vocabulary/layout.prop.d.ts +8 -30
  143. package/dist/props/vocabulary/shared.prop.d.ts +3 -2
  144. package/dist/styles/alert-layout.css +1 -32
  145. package/dist/styles/badge-layout.css +4 -18
  146. package/dist/styles/base.css +14 -113
  147. package/dist/styles/card-layout.css +6 -128
  148. package/dist/styles/chart-layout.css +2 -17
  149. package/dist/styles/control.css +380 -458
  150. package/dist/styles/core.css +21 -0
  151. package/dist/styles/data-display-layout.css +175 -94
  152. package/dist/styles/data-entry-layout.css +0 -45
  153. package/dist/styles/density.css +1 -30
  154. package/dist/styles/dialog-layout.css +5 -69
  155. package/dist/styles/focus-ring.css +13 -87
  156. package/dist/styles/fonts.css +1 -31
  157. package/dist/styles/form-layout.css +7 -91
  158. package/dist/styles/index.css +2 -20
  159. package/dist/styles/layout.css +38 -456
  160. package/dist/styles/logo-layout.css +1 -35
  161. package/dist/styles/motion.css +3 -42
  162. package/dist/styles/navigation-layout.css +3 -50
  163. package/dist/styles/shell-layout.css +37 -403
  164. package/dist/styles/table-layout.css +16 -274
  165. package/dist/styles/text-layout.css +7 -21
  166. package/dist/styles/toggle.css +1 -23
  167. package/dist/theme/dxs.canonical.css +1 -55
  168. package/dist/theme/example.service.css +1 -26
  169. package/dist/theme/famgia.service.css +5 -15
  170. package/dist/tokens/axes.css +3 -40
  171. package/dist/tokens/base.css +1 -4
  172. package/dist/tokens/components/activity.css +6 -42
  173. package/dist/tokens/components/badge.css +4 -13
  174. package/dist/tokens/components/banner.css +4 -9
  175. package/dist/tokens/components/card.css +32 -88
  176. package/dist/tokens/components/chart.css +9 -26
  177. package/dist/tokens/components/control.css +37 -222
  178. package/dist/tokens/components/data-display.css +28 -79
  179. package/dist/tokens/components/data-entry.css +5 -19
  180. package/dist/tokens/components/descriptions.css +7 -27
  181. package/dist/tokens/components/email.css +38 -84
  182. package/dist/tokens/components/error-surface.css +2 -18
  183. package/dist/tokens/components/feedback.css +27 -124
  184. package/dist/tokens/components/form.css +13 -45
  185. package/dist/tokens/components/legal-document.css +16 -41
  186. package/dist/tokens/components/list-row.css +11 -36
  187. package/dist/tokens/components/logo.css +2 -57
  188. package/dist/tokens/components/navigation.css +16 -94
  189. package/dist/tokens/components/scroll-area.css +2 -18
  190. package/dist/tokens/components/separator.css +8 -30
  191. package/dist/tokens/components/sheet.css +3 -14
  192. package/dist/tokens/components/shell.css +53 -307
  193. package/dist/tokens/components/sidebar.css +5 -22
  194. package/dist/tokens/components/table.css +19 -140
  195. package/dist/tokens/components/toggle.css +3 -65
  196. package/dist/tokens/components/upload.css +7 -43
  197. package/dist/tokens/foundation.css +108 -383
  198. package/dist/tokens/semantic/layout.css +33 -207
  199. package/docs/CANONICAL-CONTRACTS.md +206 -0
  200. package/docs/COMPONENTS.md +157 -0
  201. package/docs/COMPOSITION-VS-COMPONENT.md +105 -0
  202. package/docs/CONSUMER-RULES.md +14 -0
  203. package/docs/CUSTOMER-THEMING.md +162 -0
  204. package/docs/DATETIME.md +69 -0
  205. package/docs/DEVELOPMENT.md +173 -0
  206. package/docs/FORMS.md +143 -0
  207. package/docs/FRAME-A11Y-CI.md +267 -0
  208. package/docs/FRAME-COVERAGE-LEDGER.md +123 -0
  209. package/docs/FRAME-COVERAGE-REPORT.md +140 -0
  210. package/docs/FRAME-COVERAGE-STANDARD.md +65 -0
  211. package/docs/FRAME-COVERAGE.md +29 -0
  212. package/docs/PROPS-REGISTRY.md +61 -0
  213. package/docs/PROPS-VOCABULARY.md +100 -0
  214. package/docs/README.md +60 -0
  215. package/docs/REGISTRY.md +70 -0
  216. package/docs/SPACING.md +56 -0
  217. package/docs/STANDARDS-vocabulary-tokens.md +48 -0
  218. package/docs/TESTING.md +98 -0
  219. package/docs/TOKENS.md +347 -0
  220. package/docs/asset-modules.d.ts +46 -0
  221. package/docs/assets/cover-terrain.svg +6 -0
  222. package/docs/assets/portrait-amber.svg +5 -0
  223. package/docs/assets/portrait-clay.svg +5 -0
  224. package/docs/assets/portrait-indigo.svg +5 -0
  225. package/docs/assets/portrait-iris.svg +5 -0
  226. package/docs/assets/portrait-moss.svg +5 -0
  227. package/docs/assets/portrait-plum.svg +5 -0
  228. package/docs/assets/portrait-slate.svg +5 -0
  229. package/docs/charts/compact-bar-trend.tsx +328 -0
  230. package/docs/charts/size-tiers.tsx +192 -0
  231. package/docs/data-display/accordion.tsx +99 -0
  232. package/docs/data-display/avatar.tsx +355 -0
  233. package/docs/data-display/badge.tsx +247 -0
  234. package/docs/data-display/card/examples/detail-panel.tsx +106 -0
  235. package/docs/data-display/card/index.md +3 -0
  236. package/docs/data-display/card/index.tsx +285 -0
  237. package/docs/data-display/carousel.tsx +156 -0
  238. package/docs/data-display/charts.tsx +198 -0
  239. package/docs/data-display/code-block.tsx +90 -0
  240. package/docs/data-display/collapsible.tsx +162 -0
  241. package/docs/data-display/credential-reveal.tsx +145 -0
  242. package/docs/data-display/data-table/examples/approval-queue.tsx +212 -0
  243. package/docs/data-display/data-table/examples/grid-features.tsx +158 -0
  244. package/docs/data-display/data-table/examples/list-page.tsx +175 -0
  245. package/docs/data-display/data-table/index.md +5 -0
  246. package/docs/data-display/data-table/index.tsx +295 -0
  247. package/docs/data-display/descriptions.tsx +143 -0
  248. package/docs/data-display/empty-state.tsx +164 -0
  249. package/docs/data-display/hover-card.tsx +181 -0
  250. package/docs/data-display/list-row.tsx +420 -0
  251. package/docs/data-display/permission-matrix.tsx +184 -0
  252. package/docs/data-display/popover-touch.tsx +27 -0
  253. package/docs/data-display/popover.tsx +245 -0
  254. package/docs/data-display/progress.tsx +84 -0
  255. package/docs/data-display/prose.tsx +105 -0
  256. package/docs/data-display/qr-code.tsx +68 -0
  257. package/docs/data-display/scroll-area.tsx +271 -0
  258. package/docs/data-display/service-launcher-card.tsx +145 -0
  259. package/docs/data-display/stat-card.tsx +99 -0
  260. package/docs/data-display/table.tsx +251 -0
  261. package/docs/data-display/timeline.tsx +112 -0
  262. package/docs/data-display/touch-actions.tsx +36 -0
  263. package/docs/data-display/tree-list.tsx +107 -0
  264. package/docs/data-entry/branch-scope-picker.tsx +123 -0
  265. package/docs/data-entry/calendar.tsx +180 -0
  266. package/docs/data-entry/cascader.tsx +378 -0
  267. package/docs/data-entry/checkbox.tsx +256 -0
  268. package/docs/data-entry/color-picker.tsx +117 -0
  269. package/docs/data-entry/command-palette.tsx +179 -0
  270. package/docs/data-entry/command.tsx +262 -0
  271. package/docs/data-entry/country-picker-recipe.tsx +170 -0
  272. package/docs/data-entry/date-picker.tsx +191 -0
  273. package/docs/data-entry/date-range-picker.tsx +149 -0
  274. package/docs/data-entry/form/examples/employee-settings.tsx +267 -0
  275. package/docs/data-entry/form/examples/invoice-form.tsx +387 -0
  276. package/docs/data-entry/form-field/examples/a11y-contract.tsx +240 -0
  277. package/docs/data-entry/form-field/examples/create-form.tsx +105 -0
  278. package/docs/data-entry/form-field/index.md +8 -0
  279. package/docs/data-entry/form-field/index.tsx +326 -0
  280. package/docs/data-entry/form-inertia.tsx +80 -0
  281. package/docs/data-entry/form-root.tsx +102 -0
  282. package/docs/data-entry/form.tsx +724 -0
  283. package/docs/data-entry/input-otp.tsx +231 -0
  284. package/docs/data-entry/input.tsx +114 -0
  285. package/docs/data-entry/label.tsx +100 -0
  286. package/docs/data-entry/month-picker.tsx +101 -0
  287. package/docs/data-entry/month-range-picker.tsx +115 -0
  288. package/docs/data-entry/number-input.tsx +240 -0
  289. package/docs/data-entry/password-input.tsx +106 -0
  290. package/docs/data-entry/password-strength.tsx +97 -0
  291. package/docs/data-entry/radio-group.tsx +95 -0
  292. package/docs/data-entry/rating.tsx +87 -0
  293. package/docs/data-entry/search-input.tsx +120 -0
  294. package/docs/data-entry/select-async-contracts.tsx +108 -0
  295. package/docs/data-entry/select-matrix.tsx +204 -0
  296. package/docs/data-entry/select.tsx +369 -0
  297. package/docs/data-entry/slider.tsx +130 -0
  298. package/docs/data-entry/switch.tsx +67 -0
  299. package/docs/data-entry/tag-input.tsx +108 -0
  300. package/docs/data-entry/textarea.tsx +209 -0
  301. package/docs/data-entry/time-picker.tsx +99 -0
  302. package/docs/data-entry/toggle-count.tsx +293 -0
  303. package/docs/data-entry/toggle-group.tsx +225 -0
  304. package/docs/data-entry/toggle.tsx +127 -0
  305. package/docs/data-entry/transfer.tsx +97 -0
  306. package/docs/data-entry/tree-select.tsx +204 -0
  307. package/docs/data-entry/upload.tsx +184 -0
  308. package/docs/feedback/alert-dialog.tsx +270 -0
  309. package/docs/feedback/alert.tsx +248 -0
  310. package/docs/feedback/banner.tsx +183 -0
  311. package/docs/feedback/danger-confirm.tsx +88 -0
  312. package/docs/feedback/dialog-touch.tsx +24 -0
  313. package/docs/feedback/dialog.tsx +193 -0
  314. package/docs/feedback/sheet.tsx +410 -0
  315. package/docs/feedback/skeleton.tsx +111 -0
  316. package/docs/feedback/toast.tsx +135 -0
  317. package/docs/feedback/tooltip.tsx +171 -0
  318. package/docs/feedback/two-factor-setup.tsx +68 -0
  319. package/docs/foundation/_email-specimen.ts +274 -0
  320. package/docs/foundation/colors.tsx +202 -0
  321. package/docs/foundation/density.tsx +232 -0
  322. package/docs/foundation/email-tokens.tsx +306 -0
  323. package/docs/foundation/radius-shadow.tsx +152 -0
  324. package/docs/foundation/spacing.tsx +211 -0
  325. package/docs/foundation/typography.tsx +147 -0
  326. package/docs/general/activity.tsx +224 -0
  327. package/docs/general/button/examples/destructive-confirm.tsx +90 -0
  328. package/docs/general/button/examples/form-actions.tsx +85 -0
  329. package/docs/general/button/examples/toolbar.tsx +160 -0
  330. package/docs/general/button/index.md +12 -0
  331. package/docs/general/button/index.tsx +211 -0
  332. package/docs/general/logo.tsx +131 -0
  333. package/docs/general/reveal.tsx +41 -0
  334. package/docs/general/typography.tsx +152 -0
  335. package/docs/layout/account-chip.tsx +72 -0
  336. package/docs/layout/admin-collection.tsx +122 -0
  337. package/docs/layout/app-shell.tsx +374 -0
  338. package/docs/layout/aspect-ratio.tsx +119 -0
  339. package/docs/layout/auth-account-summary.tsx +103 -0
  340. package/docs/layout/auth-divider.tsx +28 -0
  341. package/docs/layout/auth-footer.tsx +37 -0
  342. package/docs/layout/auth-identity.tsx +36 -0
  343. package/docs/layout/auth-recovery/examples/mfa-challenge.md +41 -0
  344. package/docs/layout/auth-recovery/examples/mfa-challenge.tsx +254 -0
  345. package/docs/layout/auth-recovery/examples/mobile-390.md +25 -0
  346. package/docs/layout/auth-recovery/examples/mobile-390.tsx +82 -0
  347. package/docs/layout/auth-recovery/examples/password-recovery.md +24 -0
  348. package/docs/layout/auth-recovery/examples/password-recovery.tsx +197 -0
  349. package/docs/layout/auth-recovery/examples/wrapping-ja-en-vi.md +27 -0
  350. package/docs/layout/auth-recovery/examples/wrapping-ja-en-vi.tsx +156 -0
  351. package/docs/layout/auth-recovery/index.md +140 -0
  352. package/docs/layout/auth-recovery/index.tsx +96 -0
  353. package/docs/layout/auth-shell-context.tsx +103 -0
  354. package/docs/layout/auth-shell-device.tsx +112 -0
  355. package/docs/layout/auth-shell-registration.tsx +335 -0
  356. package/docs/layout/auth-shell-variants.tsx +139 -0
  357. package/docs/layout/auth-shell.tsx +59 -0
  358. package/docs/layout/auth-stack.tsx +40 -0
  359. package/docs/layout/centered-shell.tsx +102 -0
  360. package/docs/layout/error-surface/examples/application-403.tsx +84 -0
  361. package/docs/layout/error-surface/examples/application-404.tsx +77 -0
  362. package/docs/layout/error-surface/examples/system-500.tsx +47 -0
  363. package/docs/layout/error-surface/examples/system-503.tsx +48 -0
  364. package/docs/layout/error-surface/index.md +153 -0
  365. package/docs/layout/error-surface/index.tsx +239 -0
  366. package/docs/layout/flex.tsx +262 -0
  367. package/docs/layout/legal-document-shell/_data.ts +110 -0
  368. package/docs/layout/legal-document-shell/examples/desktop-1440.md +12 -0
  369. package/docs/layout/legal-document-shell/examples/desktop-1440.tsx +83 -0
  370. package/docs/layout/legal-document-shell/examples/mobile-390.md +22 -0
  371. package/docs/layout/legal-document-shell/examples/mobile-390.tsx +70 -0
  372. package/docs/layout/legal-document-shell/examples/tablet-1024.md +3 -0
  373. package/docs/layout/legal-document-shell/examples/tablet-1024.tsx +84 -0
  374. package/docs/layout/legal-document-shell/examples/wrapping-ja-en-vi.md +18 -0
  375. package/docs/layout/legal-document-shell/examples/wrapping-ja-en-vi.tsx +61 -0
  376. package/docs/layout/legal-document-shell/index.md +51 -0
  377. package/docs/layout/legal-document-shell/index.tsx +101 -0
  378. package/docs/layout/master-detail.tsx +387 -0
  379. package/docs/layout/org-switcher.tsx +131 -0
  380. package/docs/layout/page-container.tsx +946 -0
  381. package/docs/layout/resizable-panel.tsx +419 -0
  382. package/docs/layout/responsive-grid.tsx +235 -0
  383. package/docs/layout/separator.tsx +327 -0
  384. package/docs/layout/service-role-panel.tsx +144 -0
  385. package/docs/layout/sidebar.tsx +586 -0
  386. package/docs/layout/split-pane.tsx +451 -0
  387. package/docs/layout/topbar.tsx +347 -0
  388. package/docs/navigation/app-setting-picker.tsx +141 -0
  389. package/docs/navigation/breadcrumb.tsx +128 -0
  390. package/docs/navigation/context-menu.tsx +117 -0
  391. package/docs/navigation/dropdown-menu.tsx +213 -0
  392. package/docs/navigation/filter-bar.tsx +227 -0
  393. package/docs/navigation/menubar.tsx +141 -0
  394. package/docs/navigation/navigation-menu.tsx +165 -0
  395. package/docs/navigation/pagination.tsx +241 -0
  396. package/docs/navigation/steps.tsx +263 -0
  397. package/docs/navigation/tabs-rtl.tsx +28 -0
  398. package/docs/navigation/tabs.tsx +308 -0
  399. package/docs/navigation/toolbar.tsx +339 -0
  400. package/docs/providers/app-provider.tsx +191 -0
  401. package/docs/providers/format-date.tsx +230 -0
  402. package/docs/query/button-refetch.tsx +160 -0
  403. package/docs/query/data-state.tsx +192 -0
  404. package/docs/query/infinite-query-state.tsx +93 -0
  405. package/docs/query/mutation-feedback.tsx +70 -0
  406. package/docs/query/prefetch-link.tsx +262 -0
  407. package/docs/query/touch-actions.tsx +68 -0
  408. package/docs/showcase/acme-portal.tsx +441 -0
  409. package/docs/showcase/acme-website.tsx +455 -0
  410. package/docs/showcase/case1-warehouse-dashboard.tsx +343 -0
  411. package/docs/showcase/case2-employee-me.tsx +441 -0
  412. package/docs/showcase/case3-approval-workflow.tsx +527 -0
  413. package/docs/showcase/case4-login.tsx +307 -0
  414. package/docs/showcase/case5-shift-calendar.tsx +795 -0
  415. package/docs/showcase/case6-agency-handy.tsx +1072 -0
  416. package/docs/showcase/futurelastic-web.tsx +390 -0
  417. package/docs/showcase/org-switcher.tsx +313 -0
  418. package/docs/showcase/permission-matrix.tsx +343 -0
  419. package/docs/showcase/public-landing.tsx +210 -0
  420. package/docs/showcase/settings-account-sections.tsx +559 -0
  421. package/docs/showcase/settings-security-mfa.tsx +145 -0
  422. package/docs/showcase/table-approval-queue.tsx +199 -0
  423. package/docs/showcase/table-bulk-actions.tsx +460 -0
  424. package/docs/showcase/table-compact-kintone.tsx +438 -0
  425. package/docs/showcase/table-conditional-format.tsx +367 -0
  426. package/docs/showcase/table-crud-list.tsx +544 -0
  427. package/docs/showcase/table-density.tsx +340 -0
  428. package/docs/showcase/table-expandable-rows.tsx +471 -0
  429. package/docs/showcase/table-filter-chips.tsx +461 -0
  430. package/docs/showcase/table-footer-totals.tsx +311 -0
  431. package/docs/showcase/table-grouped-subtotals.tsx +388 -0
  432. package/docs/showcase/table-master-detail.tsx +479 -0
  433. package/docs/showcase/table-pagination.tsx +320 -0
  434. package/docs/showcase/table-states.tsx +363 -0
  435. package/docs/showcase/table-sticky-columns.tsx +373 -0
  436. package/docs/showcase/table-tree-rows.tsx +444 -0
  437. package/docs/showcase/table-view-tabs.tsx +488 -0
  438. package/package.json +10 -11
  439. package/scripts/_agent-setup.mjs +16 -2
  440. package/scripts/audit-hook.mjs +3 -13
  441. package/scripts/init-agent-kit.mjs +1 -14
  442. package/scripts/postinstall.mjs +12 -12
  443. package/scripts/ui-audit.mjs +93 -31
  444. package/scripts/visual-audit-rules.mjs +51 -7
  445. package/scripts/visual-audit.mjs +118 -47
@@ -1,21 +1,7 @@
1
- /*
2
- * LAYOUT — Flex / Page shell / EmptyState.
3
- * Apps: <Flex gap> · <PageContainer> — never ui-flex-* or Tailwind gap-*.
4
- */
1
+
5
2
 
6
3
  @layer components {
7
- /* SEPARATOR — a tokenized rule, optionally INTERRUPTED by a label (gh#308).
8
- *
9
- * Two shapes share one class. WITHOUT a label the element IS the rule, exactly as before: the
10
- * `:not([data-labelled])` guard keeps the plain form byte-identical (the `1px` and the `--border`
11
- * are now `--separator-rule-{size,color}`, whose defaults are those same values). WITH a label
12
- * the root becomes the three-cell grid `rule · label · rule` — `minmax(0, 1fr)` on both halves so
13
- * a long ja/vi label clamps the rules instead of overflowing the container, and `auto` on the
14
- * label so it is never squeezed to zero. `labelAlign` only re-measures the SHORT half, and it is
15
- * a grid TRACK on the inline axis, so start/end swap under `dir="rtl"` with no extra rule.
16
- *
17
- * `tone` re-points the two colour knobs on the root rather than restating `background`/`color`
18
- * per tone, so one declaration paints both shapes and a service override still wins. */
4
+
19
5
  .ui-separator {
20
6
  flex-shrink: 0;
21
7
  }
@@ -59,8 +45,7 @@
59
45
  font-size: var(--separator-label-font-size);
60
46
  font-weight: var(--separator-label-font-weight);
61
47
  line-height: var(--separator-label-line-height);
62
- /* A single unbroken token (a URL, a long ja compound) must still wrap rather than push the
63
- * grid past its container. */
48
+
64
49
  overflow-wrap: anywhere;
65
50
  }
66
51
 
@@ -98,11 +83,6 @@
98
83
  overflow: hidden;
99
84
  }
100
85
 
101
- /* Decorative brand SPOTLIGHT backdrop — the token-driven radial halo behind a hero / auth card,
102
- * replacing a hand-authored `radial-gradient(... hsl(var(--primary)/.18) ...)`. Apply to an
103
- * `aria-hidden` layer (e.g. an absolutely-positioned full-bleed div behind the card). All geometry
104
- * and tint come from the `--brand-glow*` tokens, so a theme retints/repositions it with no markup
105
- * change. `pointer-events:none` keeps the backdrop from stealing clicks from the card above it. */
106
86
  .ui-brand-glow {
107
87
  background-image: var(--brand-glow);
108
88
  pointer-events: none;
@@ -181,9 +161,7 @@
181
161
  .ui-flex-gap-xs {
182
162
  gap: var(--space-stack-xs);
183
163
  }
184
- /* Match the other `.ui-stack-*` sizes: a stack is a vertical column, not a row.
185
- * Without this, `gap="xs"` stacks (e.g. ToolbarGroup label + control) laid out
186
- * as a row and squeezed CJK labels into a vertical wrap. */
164
+
187
165
  .ui-stack-xs {
188
166
  flex-direction: column;
189
167
  gap: var(--space-stack-xs);
@@ -250,12 +228,6 @@
250
228
  gap: var(--space-inline-lg);
251
229
  }
252
230
 
253
- /* ResponsiveGrid OWNS its query container. The component wraps the grid in
254
- * `.ui-responsive-grid-scope` (container-type: inline-size, named `responsive-grid`) so the
255
- * column breakpoints resolve against the WIDTH AVAILABLE TO THE GRID — not the viewport and not
256
- * an undeclared ancestor `container-type`. This makes it correct inside a narrow card, a
257
- * SplitPane aside, or a bare `<div>`; it can never silently stay one column for want of an
258
- * external container context (gh#165). Named so it queries its own scope, never a closer one. */
259
231
  .ui-responsive-grid-scope {
260
232
  container: responsive-grid / inline-size;
261
233
  }
@@ -266,32 +238,26 @@
266
238
  grid-template-columns: minmax(0, 1fr);
267
239
  }
268
240
 
241
+ .ui-responsive-grid[data-gap="xs"] {
242
+ gap: var(--space-stack-xs);
243
+ }
244
+ .ui-responsive-grid[data-gap="sm"] {
245
+ gap: var(--space-stack-sm);
246
+ }
247
+ .ui-responsive-grid[data-gap="md"] {
248
+ gap: var(--space-stack-md);
249
+ }
250
+ .ui-responsive-grid[data-gap="lg"] {
251
+ gap: var(--space-stack-lg);
252
+ }
253
+ .ui-responsive-grid[data-gap="xl"] {
254
+ gap: var(--space-stack-xl);
255
+ }
256
+
269
257
  .ui-responsive-grid > * {
270
258
  min-width: 0;
271
259
  }
272
260
 
273
- /* MasterDetail owns its tracks AND its collapse threshold — both token-driven (gh#223).
274
- *
275
- * WHY FLEX AND NOT `@container (min-width: …)`: a media/container-query CONDITION cannot read a
276
- * `var()` — it is resolved before custom properties, so a tokenised breakpoint is impossible
277
- * there (the reason the first cut hard-coded `40rem`, violating rule #45). The flex threshold
278
- * below ("Holy Albatross") puts the breakpoint in a `calc()`, where a `var()` DOES resolve, so
279
- * `--master-detail-collapse-below` is a genuine knob a service theme can retune once globally.
280
- *
281
- * How it reads: the fluid region's flex-basis is `(threshold - 100%) * 999`, where 100% is the
282
- * composition's own inline size (so, like the old container query, the decision comes from the
283
- * space available to the layout — never the viewport).
284
- * · width < threshold → a huge positive basis → each region takes a whole flex line and
285
- * shrinks to it: master stacked above detail, both full width.
286
- * · width ≥ threshold → the calc goes negative and is clamped to 0 → one line: the fluid
287
- * region absorbs the free space while the rail keeps its token width (`max()` keeps the
288
- * rail's basis at the preset because a negative operand can never win a max).
289
- *
290
- * `data-rail` picks WHICH region is the fixed rail. Default `detail` = the canonical
291
- * "fluid list + fixed detail rail" (1fr / 320px) composition; `master` puts the fixed rail on
292
- * the leading edge for a category/navigator list. DOM order is always master → detail, so the
293
- * stacked order is list-then-detail either way, and the flex row follows the writing direction
294
- * (RTL-safe without a single physical property). */
295
261
  .ui-master-detail {
296
262
  display: flex;
297
263
  flex-wrap: wrap;
@@ -303,7 +269,6 @@
303
269
  --master-detail-rail-size: var(--master-detail-rail-compact);
304
270
  }
305
271
 
306
- /* Per-instance override of the token threshold (theme sets it globally, prop overrides here). */
307
272
  .ui-master-detail[data-collapse-below="false"] {
308
273
  --master-detail-collapse-below: 0px;
309
274
  }
@@ -325,31 +290,17 @@
325
290
  min-inline-size: 0;
326
291
  }
327
292
 
328
- /* The fluid region. */
329
293
  .ui-master-detail[data-rail="detail"] > .ui-master-detail-master,
330
294
  .ui-master-detail[data-rail="master"] > .ui-master-detail-detail {
331
295
  flex: 1 1 calc((var(--master-detail-collapse-below) - 100%) * 999);
332
296
  }
333
297
 
334
- /* The fixed rail. */
335
298
  .ui-master-detail[data-rail="detail"] > .ui-master-detail-detail,
336
299
  .ui-master-detail[data-rail="master"] > .ui-master-detail-master {
337
300
  flex: 0 1
338
301
  max(var(--master-detail-rail-size), calc((var(--master-detail-collapse-below) - 100%) * 999));
339
302
  }
340
303
 
341
- /* Bounded master viewport (gh#231) — OPT-IN via `masterViewport`. `auto` (the default) emits no
342
- * bound at all, so an existing composition renders byte-identically; the two presets cap the
343
- * collection's BLOCK size with a token and scroll it INSIDE the region, which is what keeps a
344
- * long real collection from pushing the detail thousands of pixels below the fold once the
345
- * layout stacks. Block axis only — the inline axis stays owned by the flex threshold above, so
346
- * the bound is writing-direction agnostic (RTL-safe).
347
- *
348
- * A11y: the region carries tabIndex={0} in this mode (see master-detail.tsx) so the scroll
349
- * container is reachable and scrollable by keyboard alone, and the inset below reserves room
350
- * for a focused row's focus ring — an overflow container clips BOTH axes, so a ring drawn at
351
- * the region's edge would otherwise be cut. Focus is never trapped: the region is a plain tab
352
- * stop that Tab/Shift+Tab leave normally. */
353
304
  .ui-master-detail[data-master-viewport="compact"] > .ui-master-detail-master,
354
305
  .ui-master-detail[data-master-viewport="standard"] > .ui-master-detail-master {
355
306
  overflow-y: auto;
@@ -366,10 +317,6 @@
366
317
  max-block-size: var(--master-detail-master-viewport-standard);
367
318
  }
368
319
 
369
- /* ── ServiceRolePanel (gh#319) ─────────────────────────────────────────────────
370
- * MasterDetail above owns every bit of the two-track geometry; what follows is only the role
371
- * LIST that sits in the master rail. `.ui-service-role-panel` was a bare hook (class emitted,
372
- * no rule anywhere), so all of this used to be Tailwind literals on the component. */
373
320
  .ui-service-role-panel-list {
374
321
  display: flex;
375
322
  flex-direction: column;
@@ -377,27 +324,16 @@
377
324
  list-style: none;
378
325
  }
379
326
 
380
- /* One role: the select button plus the optional delete button, as SIBLINGS (never nested
381
- * interactive controls). Deliberately NOT `.ui-inline-xs` — that helper wraps, and the icon
382
- * button must stay on the row beside the flex-1 select button at every width. */
383
327
  .ui-service-role-panel-row {
384
328
  display: flex;
385
329
  align-items: center;
386
330
  gap: var(--service-role-panel-row-gap);
387
331
  }
388
332
 
389
- /* ORDERING TRAP (gh#319): this needs the `.ui-service-role-panel` ancestor. The role button is
390
- * a real `<Button>`, and `.ui-button--md { padding-block: var(--button-space-block) }` lives in
391
- * control.css, which styles/index.css imports AFTER layout.css — same `@layer components`, so
392
- * at equal (0,1,0) specificity control.css would win and the row would snap back to the single
393
- * control height. (0,2,0) settles it. `h-auto` and `flex-1` stay UTILITIES on the component for
394
- * the same reason inverted: they must beat the same rule from a layer that always outranks it. */
395
333
  .ui-service-role-panel .ui-service-role-panel-item {
396
334
  padding-block: var(--service-role-panel-item-padding-block);
397
335
  }
398
336
 
399
- /* The button's label column. `min-inline-size: 0` is what lets the name and the meta line
400
- * truncate instead of forcing the rail wider (the flexbox min-content default). */
401
337
  .ui-service-role-panel-item-body {
402
338
  display: flex;
403
339
  flex-direction: column;
@@ -431,11 +367,6 @@
431
367
  }
432
368
  }
433
369
 
434
- /* SplitPane OWNS its query container too — `.ui-split-pane-scope`
435
- * (container-type: inline-size, named `split-pane`). The two-column layout is chosen from the
436
- * pane's OWN available width, so a narrow embedded pane on a large viewport correctly stays a
437
- * single column, and a wide pane on a small viewport can split (gh#165 — replaces the old
438
- * `@media (min-width: 1080px)` viewport query that ignored the container). */
439
370
  .ui-split-pane-scope {
440
371
  container: split-pane / inline-size;
441
372
  }
@@ -446,12 +377,6 @@
446
377
  grid-template-columns: minmax(0, 1fr);
447
378
  }
448
379
 
449
- /* CLOSED rail — `aside={null}`. The `<aside>` element is gone, so the pane is one full-width
450
- * column and there is nothing left for the gap to separate. The wrappers stay mounted (that is
451
- * what keeps `children` from remounting), so this attribute is the ONLY thing that changes.
452
- * The second attribute is deliberate: it lifts the rule above the `[data-aside-width]` presets
453
- * in the container queries below, which sit at equal specificity, so a closed pane stays one
454
- * column no matter where those blocks end up in source order. */
455
380
  .ui-split-pane[data-aside="closed"][data-aside-width] {
456
381
  grid-template-columns: minmax(0, 1fr);
457
382
  gap: 0;
@@ -480,15 +405,11 @@
480
405
  color: hsl(var(--muted-foreground));
481
406
  }
482
407
 
483
- /* Vertical orientation: the separator is a horizontal strip, so swap the
484
- geometry — give it height instead of width. v4 sets aria-orientation. */
485
408
  .ui-resizable-handle[aria-orientation="vertical"] {
486
409
  width: auto;
487
410
  height: var(--control-border-width);
488
411
  }
489
412
 
490
- /* v4 react-resizable-panels marks the active (dragging) and disabled state
491
- via the data-separator attribute value, not data-state. */
492
413
  .ui-resizable-handle:hover,
493
414
  .ui-resizable-handle[data-separator="active"] {
494
415
  background: hsl(var(--accent));
@@ -517,11 +438,6 @@
517
438
  }
518
439
  }
519
440
 
520
- /* `lg` splits later than the other two ON PURPOSE. A rail is only useful when
521
- * the column it sits beside still reads as the main one, and at the 48rem
522
- * threshold a 30rem rail would leave 16.5rem of main — narrower than the rail
523
- * itself, which inverts the whole point. 64rem leaves the main column 33.5rem,
524
- * about twice the rail, so the page still reads main-plus-rail. */
525
441
  @container split-pane (min-width: 64rem) {
526
442
  .ui-split-pane[data-aside-width="lg"] {
527
443
  grid-template-columns: minmax(0, 1fr) 30rem;
@@ -533,18 +449,10 @@
533
449
  min-width: 0;
534
450
  }
535
451
 
536
- /* `--page-band-gap` is INTERNAL plumbing, not a theming knob (the same shape as
537
- * --master-detail-rail-size above): the ONE resolved number that separates the page's bands,
538
- * so the container's `gap` and the toolbar band's negation of it can never disagree. It has to
539
- * be a named variable because the number is not always --space-section-active — `ghost` swaps
540
- * it for --space-stack-md, while the 720px step and the admin-collection preset swap the
541
- * section token underneath it. A toolbar rule that hard-coded
542
- * `calc(-1 * var(--space-section-active))` would look correct only because those two tokens
543
- * both resolve to 16px today: retune --space-section in a service theme and a ghost page's
544
- * band would overlap or float by the difference, silently. Services still retune the SPACING
545
- * through --space-section / --space-section-compact, exactly as before. */
546
452
  .ui-page-container {
547
453
  --page-band-gap: var(--space-section-active);
454
+ --page-body-gap: var(--space-section-active);
455
+ --page-body-gap: var(--space-section-active);
548
456
 
549
457
  display: flex;
550
458
  flex-direction: column;
@@ -553,85 +461,28 @@
553
461
  gap: var(--page-band-gap);
554
462
  }
555
463
 
556
- /* `headerScale="chrome"` — the top band is CHROME, so it sits ON the frame's edge, not in a
557
- * document's top margin. The shorthand above opens every page with --space-page-active-y (24px,
558
- * 16px below the 720px step), which is right for a document: a title needs air above it. Chrome
559
- * does not. A chat channel head, a mail thread's subject bar, an IDE tab strip are the FRAME —
560
- * measured against the design of a consumer chat screen at 1512x805, the design's channel head
561
- * occupies y 0..47 while the app's identical-height head started at y 24, pushing the whole top
562
- * chrome down and taking those 24px straight off the scroll viewport (design 617px of transcript,
563
- * app 587px). The band heights already matched; the page was simply floating.
564
- *
565
- * This is the SAME question `headerScale` already answers, not a second one: "is the first row a
566
- * document title or the surface's own furniture". The type step (see .ui-page-title below) and
567
- * the flush edge are two consequences of that one fact, which is why they ride the same
568
- * attribute rather than a second prop that every real call site would have to set in lockstep.
569
- *
570
- * BLOCK-START ONLY, deliberately. `headerScale` names the HEADER, so it may only speak for the
571
- * top edge; the bottom belongs to `stickyFooter`, which already zeroes `padding-block-end` for
572
- * its own (documented) reason. A longhand, so that contract is untouched and the shorthand above
573
- * still owns the default page.
574
- *
575
- * Chrome is a token, default quiet (rule #44): the inset is `--page-pad-block-start-chrome`,
576
- * 0 by default (flush IS the quiet state for chrome), so a service whose design grid wants its
577
- * chrome to breathe retunes it once instead of forking the page shell. The specificity (0,2,0)
578
- * out-ranks both the base rule and the 720px block — which re-declares the TOKEN, never this
579
- * padding — so the mechanism holds identically on both sides of that step. A page that never
580
- * passes `headerScale` emits no attribute and is geometrically byte-identical. */
581
464
  .ui-page-container[data-header-scale="chrome"] {
582
465
  padding-block-start: var(--page-pad-block-start-chrome);
583
466
  }
584
467
 
585
- /* `headerScale="chrome"`, the BAND-HEIGHT consequence of that same fact (gh#331). A document
586
- * header is content-height — correctly, a title is as tall as the title is. Chrome is furniture,
587
- * and furniture has a band that things centre INTO; without one, the band's vertical centre is a
588
- * function of the copy inside it (measured here: 42.02px with an `extra` control, 40.38px
589
- * without), so nothing in the page can ever be aligned to it and no service can aim at a number.
590
- *
591
- * A MIN, not a height: an `lg` control or a wrapped JA/VI channel name must still fit, and
592
- * `min-block-size` floors the band without ever clipping it. `justify-content: center` is the
593
- * half that makes the floor useful — a taller band with top-packed content is a band with dead
594
- * air under it, not a centred one — and it is inert while the knob is `auto`, because a column
595
- * whose min IS its content height has nothing to distribute. Both declarations are therefore
596
- * byte-identical for every page shipped before this token existed, chrome pages included.
597
- *
598
- * Scoped to the container attribute (0,2,0), like every other half of the chrome contract, so a
599
- * document page never matches and the 720px block (which re-declares tokens, never these
600
- * properties) cannot out-rank it. The band-height axis has ONE owner and it is
601
- * `--app-shell-bar-height`: see tokens/semantic/layout.css for why the DEFAULT still is not it. */
602
468
  .ui-page-container[data-header-scale="chrome"] .ui-page-header {
603
469
  min-block-size: var(--page-header-min-block-size-chrome);
604
470
  justify-content: center;
605
471
  }
606
472
 
607
- /* Horizontal page insets live on the header/body/footer — NOT the container —
608
- * so the header divider and footer border span the full page width
609
- * (full-bleed) while their content stays aligned with the body. */
610
473
  .ui-page-header,
611
474
  .ui-page-toolbar,
612
475
  .ui-page-body,
613
476
  .ui-page-footer {
614
477
  padding-inline-start: var(--space-page-active-x);
615
478
  padding-inline-end: var(--space-page-active-x);
479
+ padding-block-end: var(--page-footer-pad-block-end);
616
480
  }
617
481
 
618
482
  .ui-page-container--narrow .ui-page-body {
619
483
  max-width: 42rem;
620
484
  }
621
485
 
622
- /* `measure` (gh#245 / gh#247) — a bounded page measure axis ORTHOGONAL to `variant`, so the
623
- * quiet `ghost` chrome and a narrow/medium measure finally compose (they used to be the same
624
- * variant axis, which is exactly why a notification feed could not have both). Unlike
625
- * `variant="narrow"` — which caps only the body and leaves the header `extra` action stranded
626
- * at the page edge — this caps the HEADER and the BODY to ONE shared measure, so the action
627
- * ends on the same edge as the body surface. Both elements carry the identical page gutters,
628
- * so their content edges line up exactly at both ends.
629
- *
630
- * `measure="default"` is the inert default: it emits `data-measure="default"`, which matches NO
631
- * rule here, so every existing page renders byte-identically (the gh#231 `data-layout="stack"`
632
- * precedent). The cap is a max, never a width — below it the page stays fluid, so 390px keeps
633
- * the full compact-gutter surface. The footer is deliberately NOT capped: its border/background
634
- * is page-level chrome that must span the page when `stickyFooter` pins it. */
635
486
  .ui-page-container[data-measure="narrow"] .ui-page-header,
636
487
  .ui-page-container[data-measure="narrow"] .ui-page-toolbar,
637
488
  .ui-page-container[data-measure="narrow"] .ui-page-body {
@@ -644,9 +495,6 @@
644
495
  max-inline-size: var(--page-measure-medium);
645
496
  }
646
497
 
647
- /* flush = full-bleed body AND toolbar band (DataTable, a full-width filter strip);
648
- * header/footer keep their inset. `PageContainer.Inset` re-applies the page gutter to any
649
- * padded strip inside either one, so a toolbar's own content can still align with the title. */
650
498
  .ui-page-container--flush .ui-page-toolbar,
651
499
  .ui-page-container--flush .ui-page-body {
652
500
  padding-inline-start: 0;
@@ -659,38 +507,11 @@
659
507
  padding-inline-end: var(--space-page-active-x);
660
508
  }
661
509
 
662
- /* `ghost` is the quiet chrome WEIGHT: no header rule, no header bottom pad. The two halves are
663
- * NOT the same kind of decision, and the border half used to get it wrong.
664
- *
665
- * Hard-setting `none` here threw away an EXPLICIT `--page-header-divider` as well as the
666
- * nothing it was meant to block — so a service could opt the page chrome into a rule and a
667
- * ghost page would still refuse to draw it. Measured against the design of a consumer chat
668
- * screen: the design carries TWO horizontal rules in the top chrome (channel head at y=47, work
669
- * band at y=88) and the app rendered ONE (y=133) — the missing y=47 is this line eating the
670
- * divider the service had already turned on. It is the identical bug that was fixed on
671
- * `.ui-page-toolbar` below, and it takes the identical cure: re-declare the property from the
672
- * SAME knob with a `none` fallback. Nothing inherits in, the opt-in passes through, and the
673
- * default (`--page-header-divider: none`) is byte-identical to the hard-set version.
674
- *
675
- * `padding-bottom: 0` is untouched — THAT is the real quiet half of ghost, it answers "how loud
676
- * is the chrome", not "did the service ask for a rule", and no token mediates it. */
677
510
  .ui-page-container--ghost .ui-page-header {
678
511
  border-bottom: var(--page-header-divider, none);
679
512
  padding-bottom: 0;
680
513
  }
681
514
 
682
- /* The band's half of the same contract, and it must stay in step with the header rule above:
683
- * neither one lets a divider INHERIT in, and both let an explicit opt-in through — otherwise a
684
- * ghost page would draw a rule under the band that the header itself refuses to draw, or vice
685
- * versa.
686
- *
687
- * Inherited, and only inherited. An earlier version hard-set `none` here, which also threw away
688
- * an EXPLICIT `--page-toolbar-divider` — so a service could paint the band (ghost deliberately
689
- * leaves the ground alone) but could never rule it, and the two knobs disagreed about who ghost
690
- * outranks. A chat shell is the case that exposed it: a quiet header, and a workflow band that
691
- * has to read as a separate surface from the transcript below it. Re-declaring the property with
692
- * a `none` fallback keeps the original guarantee (nothing inherits in) while letting the opt-in
693
- * through, which is what the paragraph above always meant. */
694
515
  .ui-page-container--ghost .ui-page-toolbar {
695
516
  border-block-end: var(--page-toolbar-divider, none);
696
517
  }
@@ -702,15 +523,6 @@
702
523
  .ui-page-container--sticky-footer {
703
524
  min-height: 100%;
704
525
 
705
- /* The container's own block-end padding must NOT stay under a sticky
706
- * footer. A sticky box cannot travel into its containing block's padding
707
- * area, so `bottom: 0` resolves that much ABOVE the true bottom edge —
708
- * the footer floats short of the fold and page content scrolls through
709
- * the gap behind it. Measured in Chromium at the 24px page padding: the
710
- * footer pinned at 837px inside an 861px scroller.
711
- *
712
- * The space itself is still wanted, so it moves onto the body, which is
713
- * what needs breathing room above the footer anyway. */
714
526
  padding-block-end: 0;
715
527
  }
716
528
 
@@ -727,10 +539,6 @@
727
539
  background-color: hsl(var(--background));
728
540
  }
729
541
 
730
- /* footerReveal="onScroll": the sticky footer stays mounted (reserving its
731
- * flow space, so toggling never reflows the body → no scroll jitter) but is
732
- * slid below the fold until the header leaves the viewport, then slides up.
733
- * data-revealed is toggled by an IntersectionObserver on the header. */
734
542
  .ui-page-container--reveal-footer .ui-page-footer {
735
543
  transform: translateY(100%);
736
544
  opacity: 0;
@@ -758,43 +566,10 @@
758
566
  gap: var(--space-stack-sm);
759
567
  padding-bottom: var(--page-header-pad-bottom);
760
568
  border-bottom: var(--page-header-divider);
761
- /* Hero banner hook — invisible by default (--gradient-hero: none); a service paints a hero
762
- * gradient across the page header (full-bleed) by configuring the token alone. */
569
+
763
570
  background-image: var(--gradient-hero);
764
571
  }
765
572
 
766
- /* `toolbar` — the FIXED chrome band between the page header and the body: a filter strip, a
767
- * status bar, a channel workflow rail. There was no slot for it before, so a page either put
768
- * the strip inside the body (where `fill` scrolls it away) or hand-laid `position: sticky` at
769
- * the call site — the exact "never hand-lay page chrome" the system forbids.
770
- *
771
- * `flex: none` is the whole contract: under `fill` the BODY is the scroll viewport (see
772
- * .ui-page-container--fill below), so the band is a plain flex item OUTSIDE the scroller and
773
- * cannot be squeezed by it. Sticky could not give that — sticky keeps content flowing
774
- * BENEATH the pinned box, which is the half-sliced row a hand-rolled strip always shows.
775
- *
776
- * Chrome is a token, default quiet (rule #44): the band draws NO rule of its own, and its
777
- * bottom rule falls back — at the CALL SITE, never bound at :root — to whatever the page
778
- * header's divider is, so a service opting into `--page-header-divider` gets a consistent
779
- * page chrome in ONE declaration and can still silence just this band with
780
- * `--page-toolbar-divider: none`.
781
- *
782
- * The GROUND is a knob too, defaulting to `transparent` so the band looks exactly as it did
783
- * before the token existed. Without it the only way to lift the band off the page ground was a
784
- * `bg-card` utility at the call site — hand-laid page chrome, which is what this whole slot
785
- * exists to abolish, and which cannot be re-themed per tenant. It is the `background`
786
- * shorthand, not `background-color`, so a service may equally hand it a gradient.
787
- *
788
- * The NEGATIVE block margins are what make it read as chrome rather than as a third section.
789
- * The container is a flex column with a `--page-band-gap` between every band, which is right
790
- * for a document page (header / body / footer are three separate blocks) and wrong here: a
791
- * band that carries a ground and a bottom rule and then FLOATS between two 16px voids divides
792
- * nothing — measured on a 1512×805 chat page, 24/69 header · 85/134 band · 150/685 body, i.e.
793
- * 32px of dead air wrapped around the one element whose job is to be attached. Chrome sits
794
- * between the header and the scrolling body; chrome is flush. Cancelling the gap from the band
795
- * itself keeps that in ONE declaration, leaves the container's rhythm intact for every page
796
- * that passes no `toolbar`, and keeps `--page-toolbar-pad-block` as what it always was: the
797
- * band's own INSIDE, the only place it is allowed to breathe. */
798
573
  .ui-page-toolbar {
799
574
  flex: none;
800
575
  margin-block: calc(-1 * var(--page-band-gap));
@@ -803,9 +578,6 @@
803
578
  border-block-end: var(--page-toolbar-divider, var(--page-header-divider));
804
579
  }
805
580
 
806
- /* The band is never the FIRST child — the header always renders — but it CAN be the last one
807
- * (a page given a `toolbar` and no `children`). There is no gap under a last flex item to
808
- * cancel, so the negation would eat the container's own bottom padding instead. */
809
581
  .ui-page-toolbar:last-child {
810
582
  margin-block-end: 0;
811
583
  }
@@ -817,12 +589,6 @@
817
589
  gap: var(--space-stack-sm);
818
590
  }
819
591
 
820
- /* The title/subtitle column is the SHRINKABLE side of the header row (gh#300): a flex item's
821
- * automatic minimum size is its content, so without this the heading refuses to shrink and the
822
- * whole overflow is charged to `extra`. The component used to carry it as a bare `min-w-0`
823
- * utility, which meant the >=640px arrangement depended on a Tailwind class while the compact
824
- * arrangement below declared it in CSS — the same property owned in two places (gh#319).
825
- * Unconditional, so both arrangements read one rule. */
826
592
  .ui-page-header-heading {
827
593
  min-inline-size: 0;
828
594
  }
@@ -834,9 +600,6 @@
834
600
  width: 100%;
835
601
  }
836
602
 
837
- /* Title line + status/meta band (PageContainer `status`, godxjp-ui#255). The band sits on
838
- * the title's baseline row at the token-owned gap and WRAPS under the title when the
839
- * viewport (390px) or a long JA/VI title leaves no room — never clips, never overlaps. */
840
603
  .ui-page-header-title-row {
841
604
  display: flex;
842
605
  flex-wrap: wrap;
@@ -865,24 +628,10 @@
865
628
  overflow-wrap: anywhere;
866
629
  }
867
630
 
868
- /* `headerScale="chrome"` — the page's top row is CHROME (a chat channel, a mail thread, an IDE
869
- * tab), not a document title, so the `<h1>` takes the body type step. The ELEMENT is untouched:
870
- * still an `<h1>`, still the page's one level-1 heading, because the screen-reader outline is
871
- * what a page is navigated by — only the type step moves, and only through a token.
872
- *
873
- * The compound selector's specificity (0,2,0) is load-bearing, not decoration: the 720px block
874
- * below re-declares `.ui-page-title` (0,1,0) at the COMPACT DOCUMENT step, which is h2 (18px) —
875
- * BIGGER than this. Without out-ranking it, a chrome header would swell on a phone, i.e. exactly
876
- * where the height matters most. The responsive step itself is untouched and still governs every
877
- * document-scaled page. */
878
631
  .ui-page-container[data-header-scale="chrome"] .ui-page-title {
879
632
  font-size: var(--page-title-font-size-chrome);
880
633
  }
881
634
 
882
- /* Compact viewports use a responsive type step, not compact density: body/control text and
883
- * touch targets remain readable while page chrome stops competing with primary actions. A
884
- * single Flex action group must wrap even when the consumer omitted `wrap`, because clipping
885
- * header actions makes functionality unreachable. */
886
635
  @media (max-width: 720px) {
887
636
  .ui-page-container {
888
637
  --space-page-active-x: var(--space-page-compact-x);
@@ -904,13 +653,6 @@
904
653
  }
905
654
  }
906
655
 
907
- /* `headerLayout="responsive-inline"` (gh#231): below the 640px step the DEFAULT arrangement
908
- * drops `extra` onto its own full-width line under the subtitle, which pushes a compact header
909
- * control (a member search, one primary action) off the title row on a 390px screen. This
910
- * arrangement keeps it beside the title band at a token-owned measure instead, and lets the
911
- * title/subtitle wrap into the space that is left. Scoped to the compact range only, so at
912
- * >=640px both arrangements resolve to the identical row below — and a page that never sets
913
- * `headerLayout` never matches these rules at all. */
914
656
  @media (max-width: 639.98px) {
915
657
  .ui-page-header[data-layout="responsive-inline"] .ui-page-header-row {
916
658
  flex-direction: row;
@@ -940,20 +682,6 @@
940
682
  gap: var(--space-inline-lg);
941
683
  }
942
684
 
943
- /* `extra` sizes to its content but STAYS SHRINKABLE (gh#300). `width: auto` releases the
944
- * base rule's full-width stretch so a one-button header hugs the end edge; the historical
945
- * `flex-shrink: 0` next to it then froze the box at the action group's max-content width,
946
- * which is unbounded — an admin header with 10–13 buttons asks for ~1594px inside a 1408px
947
- * content column. Because the box could not shrink, its own `flex-wrap: wrap` never had a
948
- * narrower width to wrap into, the whole deficit was charged to `.ui-page-header-heading`
949
- * (`min-w-0`, so it collapses to 0), and the `<h1>` wrapped one CJK character per line while
950
- * the action group still overflowed the page. The <=720px escape hatch above
951
- * (`.ui-page-header-extra > .ui-flex { max-width: 100% }`) could not help either: a
952
- * percentage resolved against a fit-content parent is circular.
953
- *
954
- * `flex-shrink: 1` + `min-inline-size: 0` keep the max-content base size (nothing moves on a
955
- * header that already fits) but let the row charge the overflow to `extra` too, so the wrap
956
- * it already declares finally engages and the title keeps a readable measure. */
957
685
  .ui-page-header-extra {
958
686
  width: auto;
959
687
  flex-shrink: 1;
@@ -961,21 +689,6 @@
961
689
  justify-content: flex-end;
962
690
  }
963
691
 
964
- /* `headerScale="chrome"`, second consequence of the SAME fact. The row's `align-items:
965
- * flex-start` above is right for a DOCUMENT: a `Save`/`Publish` group belongs on the first
966
- * line of a tall `<h1>`, not floating beside its second line. A chrome band has no tall
967
- * heading to align to — it is one bar, and its actions sit on the bar's middle. Measured
968
- * against the reference chat screen the difference was 8.65px: the header row is 45.3px, the
969
- * icon buttons 28px, so top-packing put their centres at y=14 while the title block's centre
970
- * sat at y=22.65. That gap is what reads, correctly, as "the icons are not centred".
971
- *
972
- * `align-self` on the EXTRA box, not `align-items` on the row: the heading keeps the stretch
973
- * it has today (nothing about the title's own alignment is in question), and the two axes stay
974
- * independent. Scoped INSIDE the 640px block on purpose — below it the row is a COLUMN, where
975
- * the cross axis is horizontal and this same declaration would centre `extra` sideways and
976
- * release its full-width stretch. `headerLayout="responsive-inline"` runs its own row
977
- * arrangement under 640px and keeps its own flex-start; that arrangement exists to hold ONE
978
- * compact control beside a wrapping title band, which is the document case again. */
979
692
  .ui-page-container[data-header-scale="chrome"] .ui-page-header-extra {
980
693
  align-self: center;
981
694
  }
@@ -987,28 +700,10 @@
987
700
  margin-top: var(--space-1);
988
701
  }
989
702
 
990
- /* `headerScale="chrome"`, third consequence of the SAME fact: if the top row is chrome, the line
991
- * under it is a caption ON that chrome (a channel purpose, a mail preview), not a document's
992
- * standfirst. The document step is --font-size-base — the identical step the chrome TITLE takes —
993
- * so a chrome header was shipping a title and its subtitle at one size, which is not a hierarchy.
994
- * Same size is also not the expensive half: the step drives the LINE BOX (14px × 1.7 = 23.8px vs
995
- * 11px × 1.7 = 18.9px), and a band that exists to give the transcript room was spending 5px of it
996
- * on a caption. Type only — no line-height, no colour, no weight: `--line-height-body` still owns
997
- * the rhythm so a wrapped JA/VI purpose line stays readable at the smaller step.
998
- *
999
- * The specificity is load-bearing exactly as it is on the title above: the 720px block declares
1000
- * `.ui-page-header .ui-page-subtitle` (0,2,0) — already compounded, unlike the bare `.ui-page-title`
1001
- * — so this rule out-ranks it only because the container attribute makes it (0,3,0). Without that
1002
- * the compact step would win below 720px and a chrome subtitle would step UP to 14px on a phone,
1003
- * i.e. exactly where the band's height is worth the most. */
1004
703
  .ui-page-container[data-header-scale="chrome"] .ui-page-subtitle {
1005
704
  font-size: var(--page-subtitle-font-size-chrome);
1006
705
  }
1007
706
 
1008
- /* `headerLoading` placeholders — the real `<h1>`/`<p>` wearing the shared `.ui-skeleton-block`
1009
- * skin, so the pending band occupies the same rhythm the settled band will. `max-inline-size:
1010
- * 100%` keeps them inside a 390px column; the block size replaces the line box the (sr-only)
1011
- * text no longer fills. */
1012
707
  .ui-page-title-placeholder {
1013
708
  inline-size: var(--page-title-placeholder-measure);
1014
709
  max-inline-size: 100%;
@@ -1021,27 +716,19 @@
1021
716
  min-block-size: var(--page-subtitle-placeholder-block-size);
1022
717
  }
1023
718
 
1024
- /* Default: content-height + top-packed. The container's min-height:100% keeps
1025
- * the page background spanning the viewport-tall shell, but the body itself is
1026
- * NOT stretched — so short pages no longer leave a jarring void below their
1027
- * content (gh#103). Opt into a viewport-filling body with `fill`. */
1028
719
  .ui-page-body {
1029
720
  min-width: 0;
1030
721
  container-type: inline-size;
1031
722
  }
1032
723
 
1033
- /* fill = body grows to fill the remaining shell height (full-height DataTable,
1034
- * split-pane, or a chat surface whose message list scrolls and whose composer
1035
- * is pinned to the bottom via `footer` + `stickyFooter`). */
1036
- /* `fill` means the body OCCUPIES the remaining height — so when its content is
1037
- * taller, the content scrolls INSIDE it. The container therefore flexes to the
1038
- * region rather than growing past it, and the body owns the scroll.
1039
- *
1040
- * This is what makes a chat surface behave: header and footer are plain flex
1041
- * items at the two ends, outside the scroller, so transcript content can never
1042
- * travel underneath them. Sticky bands cannot give that — sticky keeps the
1043
- * content flowing beneath the pinned box, which is exactly the half-sliced
1044
- * message line the chat page was showing. */
724
+ .ui-page-body > * + * {
725
+ margin-block-start: var(--page-body-gap);
726
+ }
727
+
728
+ .ui-page-body > * + * {
729
+ margin-block-start: var(--page-body-gap);
730
+ }
731
+
1045
732
  .ui-page-container--fill {
1046
733
  flex: 1;
1047
734
  min-height: 0;
@@ -1053,19 +740,6 @@
1053
740
  overflow-y: auto;
1054
741
  }
1055
742
 
1056
- /* The THIRD chrome band, and until now the only one whose rule was not a knob. The header reads
1057
- * --page-header-divider and the toolbar reads --page-toolbar-divider, but this line was a
1058
- * hard-copied literal, so a page could not turn it off at all. Measured on a consumer chat
1059
- * screen: the composer lives in this slot and is itself a bordered Card, so the full-width rule
1060
- * landed straight above it as a SECOND line — a pixel diff against the design caught a 100%-wide
1061
- * rule at y=701 that the design does not have.
1062
- *
1063
- * Unlike the other two, the default here is a rule rather than silence, and that is deliberate:
1064
- * `footer` is the shared slot a form's Save/Cancel bar lands in, where the line separating the
1065
- * actions from the content is the behaviour every existing page already depends on. The literal
1066
- * simply moves into the fallback, so `--page-footer-divider` unset is byte-identical to what
1067
- * this rule drew before, and `--page-footer-divider: none` is the opt-OUT the chat composer
1068
- * needed. */
1069
743
  .ui-page-footer {
1070
744
  display: flex;
1071
745
  flex-direction: column-reverse;
@@ -1143,7 +817,6 @@
1143
817
  .ui-empty-state-description {
1144
818
  font-size: var(--font-size-base);
1145
819
  color: hsl(var(--muted-foreground));
1146
- /* Measure knob (rule #45) — default 28rem, retunable per service/locale. */
1147
820
  max-width: var(--empty-state-description-max-width);
1148
821
  }
1149
822
 
@@ -1154,8 +827,7 @@
1154
827
  inline-size: var(--empty-state-icon-size);
1155
828
  block-size: var(--empty-state-icon-size);
1156
829
  border-radius: var(--radius-pill);
1157
- /* Medallion fill + glyph colour read their knobs, defaulting to the LIVE --muted /
1158
- * --muted-foreground roles at the call site (re-resolves under a scoped theme). */
830
+
1159
831
  background-color: var(--empty-state-icon-tint, hsl(var(--muted)));
1160
832
  color: var(--empty-state-icon-foreground, hsl(var(--muted-foreground)));
1161
833
  }
@@ -1165,8 +837,6 @@
1165
837
  block-size: var(--empty-state-icon-glyph-size);
1166
838
  }
1167
839
 
1168
- /* tone — set the medallion foreground + tint from the matching role token, so a consumer never
1169
- * hand-rolls a `.ui-success-state` class. `muted` (default) keeps the neutral role fallback. */
1170
840
  .ui-empty-state[data-tone="success"] {
1171
841
  --empty-state-icon-foreground: hsl(var(--success));
1172
842
  --empty-state-icon-tint: hsl(var(--success) / 0.12);
@@ -1200,11 +870,7 @@
1200
870
  }
1201
871
 
1202
872
  @layer components {
1203
- /* Pagination is ONE horizontal row and never wraps to a second desktop line (gh#153 — the old
1204
- * `flex-wrap: wrap` split the range / size-changer / page strip across rows). The number strip
1205
- * (`.ui-pagination-list`) absorbs any overflow by scrolling horizontally rather than wrapping; a
1206
- * genuinely narrow viewport uses the INTENTIONAL compact form via the `simple` prop (Prev · n/N ·
1207
- * Next, no page buttons) — not browser-driven wrapping. */
873
+
1208
874
  .ui-pagination {
1209
875
  display: flex;
1210
876
  flex-wrap: nowrap;
@@ -1214,8 +880,6 @@
1214
880
  gap: var(--pagination-gap);
1215
881
  }
1216
882
 
1217
- /* Total / range label sits at the start and truncates rather than pushing the controls onto a
1218
- * second line — the nav row stays one line at every width (gh#153). */
1219
883
  .ui-pagination-total {
1220
884
  min-width: 0;
1221
885
  margin-inline-end: auto;
@@ -1234,8 +898,7 @@
1234
898
 
1235
899
  .ui-pagination-size-trigger {
1236
900
  width: var(--pagination-size-width);
1237
- /* Default control height — primitive never picks a sub-default tier; the app
1238
- * overrides via className if it wants smaller. Shared with the page buttons. */
901
+
1239
902
  height: var(--control-height);
1240
903
  }
1241
904
 
@@ -1244,8 +907,7 @@
1244
907
  min-width: 0;
1245
908
  align-items: center;
1246
909
  gap: var(--pagination-item-gap);
1247
- /* Overflow scrolls horizontally instead of wrapping onto a second row. Hidden scrollbar keeps
1248
- * the desktop bar clean; the strip stays swipeable on touch. */
910
+
1249
911
  overflow-x: auto;
1250
912
  scrollbar-width: none;
1251
913
  }
@@ -1270,13 +932,6 @@
1270
932
  color: hsl(var(--muted-foreground));
1271
933
  }
1272
934
 
1273
- /* The button GROWS with its label instead of being a rigid square. `width: var(--control-height)`
1274
- * capped every control at one control-height, so a five-figure page number spilled out of its
1275
- * own box (measured: `12345678` wanted 59.3px of text inside a 30px content box; `1043` cleared
1276
- * a 32px box by 0.3px and clipped outright at the consumer's 29px density). Ant Design, MUI and
1277
- * shadcn all size the same way — a min-width for the rhythm, padding for the overflow — so the
1278
- * one- and two-digit buttons keep the square look while longer ones widen. Height stays pinned so
1279
- * the row never changes rhythm. */
1280
935
  .ui-pagination-link {
1281
936
  display: inline-flex;
1282
937
  min-width: var(--control-height);
@@ -1290,11 +945,6 @@
1290
945
  text-decoration: none;
1291
946
  }
1292
947
 
1293
- /* Keyboard focus: drawn by the single-source rule in styles/focus-ring.css
1294
- * (`.ui-pagination-link` is in its shadow-form list). Before that, focus here was
1295
- * left to the browser, which paints its OWN ring — measured
1296
- * `outline: rgb(0, 95, 204) auto 1px`, a blue belonging to no theme. */
1297
-
1298
948
  .ui-pagination-link[data-active="true"] {
1299
949
  border-color: hsl(var(--ring) / 0.5);
1300
950
  background: hsl(var(--primary));
@@ -1343,9 +993,6 @@
1343
993
  }
1344
994
  }
1345
995
 
1346
- /* Toolbar `sticky` (#197): pin the filter strip while the list scrolls beneath it.
1347
- * `inset-block-start` (not `top`) keeps it RTL/vertical-writing-mode safe; the fill only
1348
- * paints in this pinned variant so a non-sticky toolbar stays transparent chrome. */
1349
996
  .ui-toolbar-sticky {
1350
997
  position: sticky;
1351
998
  inset-block-start: var(--filter-bar-sticky-offset);
@@ -1382,13 +1029,6 @@
1382
1029
  font-size: var(--filter-label-font-size);
1383
1030
  }
1384
1031
 
1385
- /* Toolbar/FilterBar `overflow="scroll"` (#216) — one bounded row that scrolls INLINE instead of
1386
- * wrapping onto extra rows. Below 640px the strip still stacks (the `wrap` base rules above own
1387
- * that): a 390px viewport must never hide a filter behind a horizontal scroll it cannot see.
1388
- * `overflow-x` matches the rest of the package's scroll regions (.ui-data-table-scroll, tabs
1389
- * list) and the browser already mirrors its scroll origin under `dir="rtl"`; the INSET/margin
1390
- * properties below stay logical. No `tabindex` is needed — every group holds a focusable
1391
- * control, so the region is keyboard-reachable (axe scrollable-region-focusable / WCAG 2.1.1). */
1392
1032
  @media (min-width: 640px) {
1393
1033
  .ui-toolbar[data-overflow="scroll"] {
1394
1034
  flex-wrap: nowrap;
@@ -1402,20 +1042,16 @@
1402
1042
  flex: 0 0 auto;
1403
1043
  }
1404
1044
 
1405
- /* The clear action stays parked at the inline end of the VIEWPORT of the scroll region, not at
1406
- * the end of its scroll width — `margin-inline-start: auto` would push it past the fold and
1407
- * make "clear all" unreachable without scrolling. */
1408
1045
  .ui-toolbar[data-overflow="scroll"] > .ui-toolbar-clear {
1409
1046
  position: sticky;
1410
1047
  inset-inline-end: 0;
1411
1048
  margin-inline-start: auto;
1412
1049
  flex: 0 0 auto;
1413
- /* Above the scrolling groups, and opaque, so a filter never slides underneath it. */
1050
+
1414
1051
  z-index: 1;
1415
1052
  background: hsl(var(--filter-bar-sticky-background, var(--background)));
1416
1053
  }
1417
1054
 
1418
- /* Typed-model reset+actions cluster gets the same viewport-pinned treatment (gh#258). */
1419
1055
  .ui-toolbar[data-overflow="scroll"] > .ui-filter-bar-end {
1420
1056
  position: sticky;
1421
1057
  inset-inline-end: 0;
@@ -1426,11 +1062,6 @@
1426
1062
  }
1427
1063
  }
1428
1064
 
1429
- /* ── FilterBar typed model (gh#258) ─────────────────────────────────────────────
1430
- * Canonical model layout: strip (search → filters → children → reset → actions) →
1431
- * applied-chips row → result-count/error line. Geometry is entirely token-owned
1432
- * (`--filter-bar-*`); stacking below 640px reuses the `.ui-toolbar` base rules, so
1433
- * 1440/1024/390 behave identically to the composition form. */
1434
1065
  .ui-filter-bar {
1435
1066
  display: flex;
1436
1067
  flex-direction: column;
@@ -1449,8 +1080,6 @@
1449
1080
  width: 100%;
1450
1081
  }
1451
1082
 
1452
- /* Inside the end cluster the clear button gives up its own auto margin — the CLUSTER is what
1453
- * parks at the inline end, so reset and actions stay adjacent in the keyboard/DOM order. */
1454
1083
  .ui-filter-bar-end .ui-toolbar-clear {
1455
1084
  margin-inline-start: 0;
1456
1085
  }
@@ -1500,20 +1129,6 @@
1500
1129
  color: hsl(var(--destructive));
1501
1130
  }
1502
1131
 
1503
- /* ── LegalDocumentShell (#222) ─────────────────────────────────────────────────
1504
- * The long-form legal/policy document surface: a readable, measure-capped document
1505
- * column with a STICKY table-of-contents rail beside it. Every value reads a
1506
- * `--legal-document-*` component token, so a service re-tunes the measure, the rail
1507
- * and the section rhythm from theme.css with zero consumer `.legal-*` CSS.
1508
- *
1509
- * It OWNS its query container (`container: legal-document / inline-size`, the SplitPane
1510
- * precedent — gh#165), so the one-column ⇄ two-column decision comes from the SHELL's
1511
- * own available width, not the viewport: dropped into a narrow pane on a 1440px screen
1512
- * it correctly stays single-column.
1513
- *
1514
- * DOM order is header → rail (contents) → sections → footer, which is exactly the
1515
- * mobile reading order; the desktop branch only re-places those boxes into a grid.
1516
- */
1517
1132
  .ui-legal-document-scope {
1518
1133
  container: legal-document / inline-size;
1519
1134
  }
@@ -1544,9 +1159,6 @@
1544
1159
  color: hsl(var(--legal-document-meta-foreground, var(--muted-foreground)));
1545
1160
  }
1546
1161
 
1547
- /* Long-form prose wrapping that holds in JA (kinsoku via `line-break: strict`), in EN
1548
- * (long compounds / URLs via `overflow-wrap: break-word`) and in VI (diacritic-heavy
1549
- * words must NOT be broken mid-word — `word-break: normal` keeps that true). */
1550
1162
  .ui-legal-document-summary,
1551
1163
  .ui-legal-document-section-body {
1552
1164
  min-width: 0;
@@ -1592,8 +1204,6 @@
1592
1204
  min-height: 0;
1593
1205
  }
1594
1206
 
1595
- /* REAL anchors — `href="#<section id>"` — so the contents list is keyboard-reachable,
1596
- * middle-clickable and deep-linkable even before JS boots. */
1597
1207
  .ui-legal-document-toc-link {
1598
1208
  display: block;
1599
1209
  padding: var(--legal-document-toc-item-padding);
@@ -1613,9 +1223,6 @@
1613
1223
  background: hsl(var(--legal-document-toc-active-background, var(--accent)) / 0.6);
1614
1224
  }
1615
1225
 
1616
- /* Active entry: colour + a leading marker + a heavier weight, so the state survives a
1617
- * monochrome / high-contrast rendering (never colour-only, WCAG 1.4.1). The anchor also
1618
- * carries `aria-current="location"`. */
1619
1226
  .ui-legal-document-toc-link[data-active] {
1620
1227
  color: hsl(var(--legal-document-toc-active-foreground, var(--foreground)));
1621
1228
  background: hsl(var(--legal-document-toc-active-background, var(--accent)));
@@ -1631,9 +1238,6 @@
1631
1238
  max-width: var(--legal-document-measure-max-width);
1632
1239
  }
1633
1240
 
1634
- /* `scroll-margin-block-start` (logical, so it survives vertical writing modes) is what
1635
- * makes a hash jump AND a programmatic `scrollIntoView` land BELOW sticky chrome — no
1636
- * JS offset arithmetic, no consumer CSS. */
1637
1241
  .ui-legal-document-section {
1638
1242
  display: flex;
1639
1243
  flex-direction: column;
@@ -1642,8 +1246,6 @@
1642
1246
  scroll-margin-block-start: var(--legal-document-scroll-offset);
1643
1247
  }
1644
1248
 
1645
- /* The section is `tabIndex={-1}` and receives focus when its contents anchor is
1646
- * activated, so the focus ring must be VISIBLE (WCAG 2.4.7 / 2.4.11). */
1647
1249
  .ui-legal-document-footer {
1648
1250
  display: flex;
1649
1251
  flex-wrap: wrap;
@@ -1654,15 +1256,6 @@
1654
1256
  border-block-start: var(--legal-document-footer-border);
1655
1257
  }
1656
1258
 
1657
- /* Two-column mode (shell ≥56rem — a 1024px and a 1440px viewport both qualify): the
1658
- * rail is PINNED beside a measure-capped document column and spans the header/body/
1659
- * footer rows, so it stays in view for the whole scroll. Logical grid placement means
1660
- * column 1 is the START side, i.e. it mirrors correctly under `dir="rtl"`.
1661
- *
1662
- * Below 56rem (the 390×844 case) NOTHING here applies: the shell stays a single column
1663
- * and the rail is a STATIC compact contents block between the document header and the
1664
- * first section — deliberately not sticky and not height-capped, so a phone never loses
1665
- * vertical space to pinned chrome. */
1666
1259
  @container legal-document (min-width: 56rem) {
1667
1260
  .ui-legal-document {
1668
1261
  grid-template-columns:
@@ -1699,18 +1292,7 @@
1699
1292
  }
1700
1293
 
1701
1294
  @layer components {
1702
- /* ── Flex responsive region visibility (gh#252) ──────────────────────────────────────────────
1703
- * `hideBelow` / `hideFrom` are the ONE public way to drop a layout region at a breakpoint step.
1704
- * Without them a public header cannot hide its anchor navigation below the tablet step, or show
1705
- * a compact-only affordance, without a page-local media query — which is exactly the API-first
1706
- * violation gh#252 reports. The steps are the package's canonical breakpoint scale, the same
1707
- * values `--master-detail-collapse-below` uses (sm 40rem · md 48rem · lg 64rem · xl 80rem); a
1708
- * media query cannot read a `var()`, so the step values are the tokenized scale written out.
1709
- *
1710
- * Both props default to `undefined`, so NO `data-hide-*` attribute is emitted and none of these
1711
- * selectors can match — every existing Flex is untouched (the gh#231 inert-default contract).
1712
- * `display: none` removes the region from the a11y tree too, which is the point: the destination
1713
- * must stay reachable from another region (the footer nav) at that width. */
1295
+
1714
1296
  @media (width < 40rem) {
1715
1297
  .ui-flex[data-hide-below="sm"] {
1716
1298
  display: none;