@godxjp/ui 18.15.3 → 19.0.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 (145) hide show
  1. package/dist/app/date-format-labels.d.ts +1 -1
  2. package/dist/app/time-format-labels.d.ts +1 -1
  3. package/dist/components/data-display/badge.d.ts +1 -1
  4. package/dist/components/data-display/card.d.ts +4 -13
  5. package/dist/components/data-display/card.js +2 -15
  6. package/dist/components/data-display/data-table.d.ts +3 -8
  7. package/dist/components/data-display/data-table.js +106 -68
  8. package/dist/components/data-display/descriptions.js +34 -5
  9. package/dist/components/data-display/empty-state.js +1 -1
  10. package/dist/components/data-display/permission-matrix.js +7 -7
  11. package/dist/components/data-display/popover.js +2 -9
  12. package/dist/components/data-display/qr-code.d.ts +1 -1
  13. package/dist/components/data-display/scroll-area.d.ts +3 -1
  14. package/dist/components/data-display/scroll-area.js +150 -16
  15. package/dist/components/data-display/table.js +12 -3
  16. package/dist/components/data-entry/branch-scope-picker.js +4 -4
  17. package/dist/components/data-entry/calendar.js +15 -24
  18. package/dist/components/data-entry/cascader.js +67 -71
  19. package/dist/components/data-entry/checkbox.js +9 -1
  20. package/dist/components/data-entry/command.d.ts +6 -6
  21. package/dist/components/data-entry/date-picker.js +28 -15
  22. package/dist/components/data-entry/date-range-picker.js +10 -10
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/index.d.ts +2 -1
  25. package/dist/components/data-entry/input.js +8 -19
  26. package/dist/components/data-entry/label.js +4 -2
  27. package/dist/components/data-entry/month-picker.js +16 -15
  28. package/dist/components/data-entry/month-range-picker.js +17 -16
  29. package/dist/components/data-entry/radio.js +6 -1
  30. package/dist/components/data-entry/search-select.js +29 -29
  31. package/dist/components/data-entry/select.js +15 -18
  32. package/dist/components/data-entry/switch.js +7 -1
  33. package/dist/components/data-entry/textarea.d.ts +56 -0
  34. package/dist/components/data-entry/textarea.js +103 -40
  35. package/dist/components/data-entry/time-picker.js +52 -52
  36. package/dist/components/data-entry/toggle-group.d.ts +1 -0
  37. package/dist/components/data-entry/toggle.d.ts +1 -1
  38. package/dist/components/data-entry/transfer.js +14 -15
  39. package/dist/components/data-entry/tree-select.js +16 -22
  40. package/dist/components/data-entry/upload-crop-dialog.js +4 -4
  41. package/dist/components/data-entry/upload.d.ts +1 -1
  42. package/dist/components/data-entry/upload.js +46 -46
  43. package/dist/components/feedback/alert.js +7 -14
  44. package/dist/components/feedback/dialog.d.ts +23 -32
  45. package/dist/components/feedback/dialog.js +16 -13
  46. package/dist/components/feedback/sheet.d.ts +1 -1
  47. package/dist/components/feedback/sheet.js +14 -14
  48. package/dist/components/feedback/skeleton.js +13 -10
  49. package/dist/components/feedback/sonner.d.ts +1 -1
  50. package/dist/components/feedback/sonner.js +17 -8
  51. package/dist/components/feedback/tooltip.js +1 -1
  52. package/dist/components/general/activity.d.ts +44 -0
  53. package/dist/components/general/activity.js +65 -0
  54. package/dist/components/general/button.d.ts +2 -2
  55. package/dist/components/general/button.js +11 -24
  56. package/dist/components/general/index.d.ts +2 -0
  57. package/dist/components/general/index.js +2 -0
  58. package/dist/components/layout/app-shell.js +5 -3
  59. package/dist/components/layout/auth-divider.d.ts +9 -2
  60. package/dist/components/layout/auth-divider.js +7 -11
  61. package/dist/components/layout/index.d.ts +1 -0
  62. package/dist/components/layout/page-container.d.ts +1 -1
  63. package/dist/components/layout/page-container.js +32 -20
  64. package/dist/components/layout/resizable.d.ts +12 -12
  65. package/dist/components/layout/separator.d.ts +1 -0
  66. package/dist/components/layout/service-role-panel.js +47 -42
  67. package/dist/components/layout/sidebar.js +13 -1
  68. package/dist/components/layout/split-pane.d.ts +12 -1
  69. package/dist/components/layout/split-pane.js +13 -4
  70. package/dist/components/navigation/app-setting-picker.js +5 -14
  71. package/dist/components/navigation/dropdown-menu.js +10 -16
  72. package/dist/components/navigation/menubar.d.ts +3 -6
  73. package/dist/components/navigation/pagination-utils.d.ts +15 -2
  74. package/dist/components/navigation/pagination-utils.js +27 -21
  75. package/dist/components/navigation/pagination.d.ts +4 -4
  76. package/dist/components/navigation/pagination.js +2 -2
  77. package/dist/components/navigation/steps.js +31 -67
  78. package/dist/components/navigation/tabs.js +17 -5
  79. package/dist/components/query/infinite-query-state.js +2 -2
  80. package/dist/components/ui/accordion.d.ts +1 -1
  81. package/dist/components/ui/avatar.d.ts +1 -1
  82. package/dist/components/ui/avatar.js +29 -10
  83. package/dist/components/ui/input-otp.d.ts +8 -6
  84. package/dist/components/ui/rating.js +1 -1
  85. package/dist/components/ui/separator.d.ts +27 -2
  86. package/dist/components/ui/separator.js +42 -11
  87. package/dist/components/ui/toggle-group.d.ts +15 -5
  88. package/dist/components/ui/toggle-group.js +43 -18
  89. package/dist/components/ui/toggle.d.ts +66 -5
  90. package/dist/components/ui/toggle.js +64 -9
  91. package/dist/i18n/messages/en.json +8 -0
  92. package/dist/i18n/messages/ja.json +8 -0
  93. package/dist/i18n/messages/vi.json +8 -0
  94. package/dist/lib/control-styles.d.ts +14 -0
  95. package/dist/lib/control-styles.js +2 -0
  96. package/dist/props/components/data-display.prop.d.ts +85 -3
  97. package/dist/props/components/data-entry.prop.d.ts +17 -0
  98. package/dist/props/components/general.prop.d.ts +52 -1
  99. package/dist/props/components/layout.prop.d.ts +137 -2
  100. package/dist/props/registry.d.ts +116 -5
  101. package/dist/props/registry.js +165 -4
  102. package/dist/props/vocabulary/index.d.ts +2 -2
  103. package/dist/props/vocabulary/interaction.prop.d.ts +15 -0
  104. package/dist/props/vocabulary/layout.prop.d.ts +8 -0
  105. package/dist/styles/alert-layout.css +69 -3
  106. package/dist/styles/badge-layout.css +2 -2
  107. package/dist/styles/base.css +6 -0
  108. package/dist/styles/card-layout.css +27 -7
  109. package/dist/styles/control.css +1150 -29
  110. package/dist/styles/data-display-layout.css +185 -11
  111. package/dist/styles/data-entry-layout.css +352 -1
  112. package/dist/styles/density.css +2 -2
  113. package/dist/styles/dialog-layout.css +203 -4
  114. package/dist/styles/focus-ring.css +155 -0
  115. package/dist/styles/form-layout.css +50 -5
  116. package/dist/styles/index.css +1 -0
  117. package/dist/styles/layout.css +412 -23
  118. package/dist/styles/motion.css +178 -0
  119. package/dist/styles/navigation-layout.css +398 -22
  120. package/dist/styles/shell-layout.css +137 -39
  121. package/dist/styles/table-layout.css +153 -22
  122. package/dist/styles/toggle.css +59 -0
  123. package/dist/tokens/base.css +5 -0
  124. package/dist/tokens/components/activity.css +64 -0
  125. package/dist/tokens/components/badge.css +7 -0
  126. package/dist/tokens/components/card.css +14 -10
  127. package/dist/tokens/components/chart.css +1 -1
  128. package/dist/tokens/components/control.css +358 -8
  129. package/dist/tokens/components/data-display.css +75 -0
  130. package/dist/tokens/components/data-entry.css +25 -0
  131. package/dist/tokens/components/descriptions.css +22 -0
  132. package/dist/tokens/components/feedback.css +143 -4
  133. package/dist/tokens/components/form.css +16 -0
  134. package/dist/tokens/components/legal-document.css +5 -1
  135. package/dist/tokens/components/navigation.css +109 -0
  136. package/dist/tokens/components/scroll-area.css +21 -0
  137. package/dist/tokens/components/separator.css +52 -0
  138. package/dist/tokens/components/shell.css +132 -20
  139. package/dist/tokens/components/table.css +52 -3
  140. package/dist/tokens/components/toggle.css +82 -0
  141. package/dist/tokens/components/upload.css +107 -0
  142. package/dist/tokens/foundation.css +171 -10
  143. package/dist/tokens/semantic/layout.css +120 -1
  144. package/package.json +84 -68
  145. package/scripts/ui-audit.mjs +51 -2
@@ -73,19 +73,19 @@
73
73
  * Tokenised (rule #44) so a service theme can re-tune it without forking CSS.
74
74
  * The slot padding compensation in card-layout.css subtracts the same token,
75
75
  * so content stays aligned on the shell whatever the rail width. */
76
- --card-accent-rail-width: 6px;
76
+ --card-accent-rail-width: var(--stroke-2xl);
77
77
  /* Accent placement `perimeter` (gh#12) — the FULL attention border. Two knobs so a service can
78
78
  * tune the edge weight and the outer ring independently; the defaults reproduce the optical
79
79
  * weight of `variant="featured"` (1px border + 1px ring) in the card's own semantic accent tone
80
80
  * instead of --primary. Structural 1px literals, like the base card hairline. */
81
- --card-accent-perimeter-width: 1px;
82
- --card-accent-perimeter-ring-width: 1px;
81
+ --card-accent-perimeter-width: var(--stroke-hairline);
82
+ --card-accent-perimeter-ring-width: var(--stroke-hairline);
83
83
  /* `variant="featured"` edge — role-mirror knob (docs/TOKENS.md). `initial` so the --primary
84
84
  * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary
85
85
  * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card
86
86
  * at once. Default = hsl(var(--primary)). */
87
87
  --card-featured-border-color: initial;
88
- --card-featured-ring-width: 1px;
88
+ --card-featured-ring-width: var(--stroke-hairline);
89
89
  --stat-card-label-font-size: var(--font-size-xs);
90
90
  --stat-card-label-font-weight: var(--font-weight-medium);
91
91
  --stat-card-label-letter-spacing: 0.04em;
@@ -94,8 +94,8 @@
94
94
  --stat-card-value-font-weight: var(--font-weight-semibold);
95
95
  --stat-card-hint-font-size: var(--font-size-xs);
96
96
  --stat-card-gap: var(--space-stack-xs);
97
- --stat-card-icon-size: 2.25rem;
98
- --stat-card-icon-glyph-size: 1.25rem;
97
+ --stat-card-icon-size: var(--icon-size-2xl);
98
+ --stat-card-icon-glyph-size: var(--icon-size-lg);
99
99
  --stat-card-icon-radius: var(--radius-md);
100
100
  /* Medallion tint — soft brand wash + brand glyph by default; a service retints by overriding
101
101
  * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default
@@ -108,10 +108,14 @@
108
108
  * Internal rhythm of a launcher tile: one gap knob drives the vertical stack AND the
109
109
  * icon↔title↔status row, so a service theme retunes the whole tile density once. */
110
110
  --card-service-launcher-space-gap: var(--space-stack-sm);
111
- /* Semantic icon surface. `--control-height-lg` is the 36px control tier the hi-fi calls for —
112
- * a tier token, never a literal, so the medallion tracks --scaling with its sibling controls. */
113
- --card-service-launcher-icon-size: var(--control-height-lg);
114
- --card-service-launcher-icon-glyph-size: var(--space-5);
111
+ /* Semantic icon surface — 36px, the `--icon-size-2xl` step, multiplied by --scaling so the
112
+ * medallion still breathes with its sibling controls (the gh#328 rule: an icon that wants
113
+ * density says so itself). It read `var(--control-height-lg)` until gh#324: a CONTROL tier is
114
+ * the wrong axis for an icon box, and the bug was visible — `@media (pointer: coarse)` lifts
115
+ * the control ladder to the 44px tap floor, so on every touch device the medallion silently
116
+ * inflated to 48px while the glyph inside it stayed 20px. */
117
+ --card-service-launcher-icon-size: calc(var(--icon-size-2xl) * var(--scaling));
118
+ --card-service-launcher-icon-glyph-size: calc(var(--icon-size-lg) * var(--scaling));
115
119
  --card-service-launcher-icon-radius: var(--radius-md);
116
120
  /* Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role
117
121
  * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them.
@@ -25,7 +25,7 @@
25
25
  /* Ceiling on a single mark so a 3-point trend does not render slabs. */
26
26
  --chart-trend-bar-max-width: 1.5rem;
27
27
  /* Floor so a zero/absent value still reads as a plotted category rather than a hole. */
28
- --chart-trend-bar-min-height: 2px;
28
+ --chart-trend-bar-min-height: var(--stroke-md);
29
29
  /* Muted mark fill — role-mirror knob, declared `initial` so the role default re-resolves
30
30
  * at the CALL SITE under `.dark` / `[data-tenant]` (docs/TOKENS.md · "Role-mirror knobs
31
31
  * MUST be `initial`"). Default = hsl(var(--muted-foreground) / 0.75), which clears the
@@ -1,9 +1,9 @@
1
1
  /* Control primitive tokens: heights, horizontal padding, adjacent control sizes. */
2
2
 
3
3
  :root {
4
- --control-height-compact: 1.75rem;
5
- --control-height-default: 2rem;
6
- --control-height-comfortable: 2.75rem;
4
+ --control-height-compact: var(--band-height-sm);
5
+ --control-height-default: var(--band-height-md);
6
+ --control-height-comfortable: var(--band-height-xl);
7
7
  --control-padding-x-compact: var(--space-2);
8
8
  --control-padding-x-default: var(--space-3);
9
9
  --control-padding-x-comfortable: var(--space-4);
@@ -27,12 +27,24 @@
27
27
  * once instead of each component hard-coding Tailwind utilities. Defaults preserve
28
28
  * the historical look (font-size-base, 1px border, shadow-xs). */
29
29
  --control-font-size: var(--font-size-base);
30
- --control-border-width: 1px;
30
+ --control-border-width: var(--stroke-hairline);
31
31
  --control-shadow: var(--shadow-xs);
32
- --control-icon-size: calc(1rem * var(--scaling));
33
- --control-icon-size-sm: calc(0.875rem * var(--scaling));
32
+ --control-icon-size: calc(var(--icon-size-md) * var(--scaling));
33
+ --control-icon-size-sm: calc(var(--icon-size-sm) * var(--scaling));
34
34
  --control-focus-ring-width: var(--focus-ring-width);
35
35
 
36
+ /* Ring knobs for the two controls that need a softer, heavier mark than the
37
+ * global default — both are filled surfaces where a hard 2px ring reads as a
38
+ * second border. Values preserve the look these controls always had; they are
39
+ * knobs now instead of hand-written box-shadows (styles/focus-ring.css). */
40
+ --toggle-focus-ring-width: var(--stroke-lg);
41
+ --toggle-focus-ring-alpha: 0.35;
42
+ --time-input-focus-ring-width: var(--stroke-lg);
43
+ --time-input-focus-ring-alpha: 0.3;
44
+
45
+ /* Outline-form gaps — marks where a radius-hugging ring would touch the glyph. */
46
+ --rating-focus-ring-offset: 2px;
47
+
36
48
  --checkbox-size: calc(1rem * var(--scaling));
37
49
  --checkbox-size-compact: 0.875rem;
38
50
  --checkbox-size-comfortable: 1.125rem;
@@ -94,8 +106,16 @@
94
106
  --slider-track-background: initial;
95
107
  --slider-range-background: initial;
96
108
 
109
+ /* Switch OFF track fill (gh#315) — `initial`, default hsl(var(--input)). Its own knob so a
110
+ * service can quieten the off-track without dragging the --input control-boundary role back
111
+ * below the WCAG SC 1.4.11 floor (rule #45); whatever you set still owes 3:1 against the page
112
+ * and against the thumb (--background), or "off" stops being a visible state. */
113
+ --switch-unchecked-background: initial;
114
+
115
+ /* ColorPicker — width of the hex text field beside the swatch. */
97
116
  --color-picker-input-width: 6.5rem;
98
117
 
118
+ /* Command / CommandPalette — list height, inner paddings and the palette's own box. */
99
119
  --command-list-max-height: min(300px, 50vh);
100
120
  --command-input-padding-x: var(--space-3);
101
121
  --command-group-padding: var(--space-1);
@@ -107,6 +127,9 @@
107
127
  --command-palette-hint-padding-y: var(--space-2);
108
128
  --command-palette-hint-padding-x: var(--space-4);
109
129
  --command-palette-hint-gap: var(--space-4);
130
+
131
+ /* SearchInput — the inset the leading/trailing icons sit at, and the field padding derived
132
+ * from it so the text never runs under an icon. */
110
133
  --search-input-edge-inset: var(--space-3);
111
134
  --search-input-start-padding: calc(
112
135
  var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap)
@@ -115,6 +138,8 @@
115
138
  var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap)
116
139
  );
117
140
 
141
+ /* The --font-size-xs tier, spelled once per control that reads it, so a service can retune a
142
+ * single one of them without moving the whole scale. */
118
143
  --choice-description-font-size: var(--font-size-xs);
119
144
  --color-picker-hex-font-size: var(--font-size-xs);
120
145
  --command-group-heading-font-size: var(--font-size-xs);
@@ -127,9 +152,334 @@
127
152
  /* Rule #24 — on touch devices (coarse pointer) interactive controls keep a ≥44px tap target
128
153
  * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height
129
154
  * resolves through these via var(), so inputs/buttons/selects/table rows all bump together. */
155
+ /* Control-family tokens added for #319.
156
+ *
157
+ * These MUST live in a plain :root, not inside the @media (pointer: coarse) block below —
158
+ * bare declarations in a conditional group rule are invalid CSS and the parser drops them
159
+ * silently. They spent this whole refactor dead for exactly that reason: the guards read the
160
+ * file textually, so check-token-tiers and the geometry ratchet both passed while every one of
161
+ * these resolved to nothing. */
162
+ :root {
163
+ /* TRAILING AFFIX — the clear ✕ / chevron overlay a select-family trigger parks at its inline
164
+ * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm
165
+ * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the
166
+ * literal through several components. One shared set of knobs instead — this is a CONTROL-level
167
+ * concern, not a per-component one. */
168
+ --control-affix-inset-inline-end: var(--space-2);
169
+ --control-affix-action-size: 1.5rem;
170
+ --control-affix-action-radius: var(--radius-sm);
171
+ --control-affix-icon-size: var(--icon-size-md);
172
+ /* Resting weight of an affix glyph — it must read as secondary until hovered/focused. */
173
+ --control-affix-rest-alpha: 0.5;
174
+ /* Room a trigger reserves so its label never runs under the affix. */
175
+ /* 2.25rem — the room a single trailing affix needs. Written as a raw rem because the
176
+ * literal it replaced (`pe-9`) is a flat Tailwind step, not a density-scaled one. */
177
+ --control-trigger-space-inline-end: 2.25rem;
178
+
179
+ /* INSIDE-FIELD AFFIX — the leading/trailing controls Input and TimePicker render WITHIN the
180
+ * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect
181
+ * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit
182
+ * on the field's own surface rather than over it, they rest a touch heavier than the overlay
183
+ * pair — that difference is the reason the two sets stay separate. */
184
+ --control-inline-affix-size: 1.25rem;
185
+ --control-inline-affix-icon-size: var(--control-affix-icon-size);
186
+ --control-inline-affix-space-gap: var(--space-1);
187
+ --control-inline-affix-inset-inline: var(--space-2);
188
+ --control-inline-affix-rest-alpha: 0.7;
189
+ /* Inline room the field reserves so its text never runs under a single affix. */
190
+ --control-inline-affix-space-inline-end: 2.25rem;
191
+ /* Input centres its affix on the field's single line; a textarea has no single line to centre
192
+ * on, so its clear control parks at the top-end corner instead. */
193
+ --textarea-clear-inset-block-start: var(--space-2);
194
+ /* MULTILINE BOX — the block padding of `.ui-control-multiline` (Textarea). It was a bare
195
+ * `--space-2` inside the padding shorthand, which made it unreachable for a service that runs a
196
+ * denser composer, AND it is the constant the auto-grow row maths has to add back, so the two
197
+ * had to read the same knob or drift (#45). */
198
+ --control-multiline-padding-block: var(--space-2);
199
+ /* TEXTAREA AUTO-GROW (gh#310) — `autoGrow` grows the box with its content between a floor and a
200
+ * ceiling expressed in TEXT ROWS. Rows, not pixels, because a row count survives a density
201
+ * change and a `--font-size-base` retheme where a px ceiling would silently stop matching the
202
+ * visible line count. Defaults reproduce today's geometry when `autoGrow` is absent (#44). */
203
+ /* The unit a "row" is measured in. `initial` on purpose: the call site reads
204
+ * `var(--textarea-autogrow-line-height, var(--line-height-normal))`, so a service that retunes
205
+ * the multiline line-height for CJK legibility retunes the ceiling with it (#46). Unitless —
206
+ * it is multiplied by `--control-font-size`. */
207
+ --textarea-autogrow-line-height: initial;
208
+ /* Floor of the grown box, in text rows. The `minRows` prop overrides per instance — the same
209
+ * theme-global / prop-local priority as `--form-label-width` vs `labelWidth`. The floor is
210
+ * additionally clamped up to `--control-height` so a resting one-row composer still lines up
211
+ * with the Input / Button beside it instead of undercutting the control tier. */
212
+ --textarea-autogrow-min-height-rows: 1;
213
+ /* Ceiling of the grown box, in text rows; past it the control scrolls internally instead of
214
+ * pushing the page. `maxRows={0}` sets this to `infinity` for an unbounded box. */
215
+ --textarea-autogrow-max-height-rows: 8;
216
+ /* The non-text part of the box the row maths must add back: block padding + border, both
217
+ * edges. `.ui-textarea-autogrow--ghost` re-declares it without the border, because the ghost
218
+ * variant drops its own chrome. */
219
+ --textarea-autogrow-box-inset: calc(
220
+ (var(--control-multiline-padding-block) + var(--control-border-width)) * 2
221
+ );
222
+ /* A field parking TWO inline affixes (TimePicker's clear+clock, DatePicker's clear+calendar)
223
+ * reserves a wider end than the single-affix case. */
224
+ --control-inline-affix-pair-space-inline-end: 3.5rem;
225
+ /* COMPOSITE FIELD — the bordered box that wraps two inputs plus a separator (date/month
226
+ * range pickers) or one input plus affixes. Shared so the four range/picker fields cannot
227
+ * drift into four slightly different boxes. */
228
+ --control-composite-field-space-gap: var(--space-2);
229
+ /* The <input type=file> button is a browser-owned box we restyle; it sits one tier below the
230
+ * field so it reads as a control INSIDE the control. */
231
+ --input-file-button-height: var(--band-height-sm);
232
+ --input-file-button-font-size: var(--font-size-sm);
233
+
234
+ /* SEARCH-SELECT — the Popover+Command combobox. Its panel geometry was baked onto the
235
+ * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could
236
+ * not widen the panel or change the viewport gutter without forking. */
237
+ --search-select-panel-max-width: 32rem;
238
+ --search-select-panel-viewport-inset: var(--space-6);
239
+ --search-select-list-space-inset: var(--space-1);
240
+ --search-select-footer-space-inset: var(--space-1);
241
+ --search-select-option-space-gap: var(--space-2);
242
+ --search-select-option-font-size: var(--font-size-sm);
243
+ --search-select-option-sublabel-font-size: var(--font-size-xs);
244
+ --search-select-status-space-inline: var(--space-2);
245
+ --search-select-status-space-block: var(--space-3);
246
+ /* Empty/error states sit alone in the panel, so they get a taller, centred block. */
247
+ --search-select-placeholder-space-block: var(--space-6);
248
+ --search-select-status-font-size: var(--font-size-sm);
249
+ --search-select-spinner-size: 1rem;
250
+
251
+ /* CASCADER — column browser + flat search list. Its option rows, column widths and panel
252
+ * paddings were literals on the component (#319), so a service could not widen a column to fit
253
+ * longer JA labels or tighten the row rhythm without forking. */
254
+ --cascader-column-min-width: 9rem;
255
+ --cascader-columns-max-height: min(280px, 50vh);
256
+ --cascader-list-max-height: min(300px, 50vh);
257
+ --cascader-panel-space-inset: var(--space-1);
258
+ --cascader-option-space-inline: var(--space-3);
259
+ --cascader-option-space-block: var(--space-2);
260
+ --cascader-option-space-gap: var(--space-1);
261
+ --cascader-option-font-size: var(--font-size-sm);
262
+ --cascader-option-icon-size: var(--icon-size-md);
263
+ /* The flat search result row is denser than a column row — it has no chevron to balance. */
264
+ --cascader-result-space-inline: var(--space-2);
265
+ --cascader-result-space-block: 0.375rem;
266
+ --cascader-result-radius: var(--radius-sm);
267
+ --cascader-result-icon-space-inline-end: var(--space-2);
268
+ --cascader-empty-space-block: var(--space-6);
269
+
270
+ /* SELECT — the Radix listbox surface. Its popup shares the menu row rhythm (--menu-item-*),
271
+ * so only what is genuinely its own lives here (#319). */
272
+ --select-content-max-height: 24rem;
273
+ --select-scroll-button-space-block: var(--space-1);
274
+ /* Select rows sit slightly wider than a menu row: a listbox has no leading icon column to
275
+ * absorb the optical inset, so 2.5 reads level with a menu's 2 + icon. */
276
+ --select-item-space-inline: 0.625rem;
277
+
278
+ /* TIME PICKER — scroll columns of hour/minute/second plus an inline affix pair. All of its
279
+ * geometry was literal on the component (#319): a service could not shorten the column, widen
280
+ * the panel for a 12-hour layout, or retune the row rhythm without forking. */
281
+ --time-picker-column-height: 13rem;
282
+ --time-picker-panel-width: 9rem;
283
+ /* 12-hour mode adds an AM/PM column, so the panel is wider. */
284
+ --time-picker-panel-width-12h: 13rem;
285
+ --time-picker-heading-space-inline: var(--space-1);
286
+ --time-picker-heading-space-block: 0.375rem;
287
+ --time-picker-heading-font-size: var(--font-size-xs);
288
+ --time-picker-column-space-inset: var(--space-1);
289
+ --time-picker-option-space-block: 0.375rem;
290
+ --time-picker-option-radius: var(--radius-md);
291
+ --time-picker-option-font-size: var(--font-size-sm);
292
+ --time-picker-footer-space-inset: var(--space-2);
293
+
294
+ --time-picker-affix-space-gap: var(--control-inline-affix-space-gap);
295
+
296
+ /* TREE SELECT — a Select whose panel holds a tree. Its rows, indent and toggle were literal
297
+ * on the component, and the depth indent was a magic expression in JSX
298
+ * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319). */
299
+ --tree-select-list-max-height: min(300px, 50vh);
300
+ --tree-select-row-space-block: 0.375rem;
301
+ --tree-select-row-space-inline-end: var(--space-2);
302
+ --tree-select-row-radius: var(--radius-sm);
303
+ --tree-select-row-font-size: var(--font-size-sm);
304
+ /* Indent = base + depth × step. A denser service dials the step down without touching JSX. */
305
+ --tree-select-depth-space-base: var(--space-2);
306
+ --tree-select-depth-space-step: 1.25rem;
307
+ --tree-select-toggle-size: 1.25rem;
308
+ --tree-select-toggle-space-inline-end: var(--space-1);
309
+ --tree-select-label-space-gap: var(--space-2);
310
+ --tree-select-empty-space-block: var(--space-6);
311
+
312
+ /* CALENDAR — the react-day-picker grid. Day/weekday cells already size from --control-height
313
+ * (the system control tier), but the frame around them — root inset, month gaps, nav offset,
314
+ * grid rhythm — was literal on the component (#319), so a service could not tighten the
315
+ * calendar to its own density without forking the classNames map. */
316
+ --calendar-space-inset: var(--space-3);
317
+ --calendar-month-space-gap: var(--space-4);
318
+ --calendar-caption-space-inline: var(--space-8);
319
+ --calendar-caption-font-size: var(--font-size-sm);
320
+ --calendar-nav-space-block-start: var(--space-3);
321
+ --calendar-nav-space-inline: var(--space-1);
322
+ /* Nav chevrons read as secondary until hovered — they frame the month, they are not the point. */
323
+ --calendar-nav-rest-alpha: 0.7;
324
+ --calendar-grid-space-block-start: var(--space-4);
325
+ --calendar-week-space-block-start: var(--space-2);
326
+ --calendar-weekday-font-size: var(--font-size-xs);
327
+ --calendar-weekday-radius: var(--radius-md);
328
+ --calendar-day-font-size: var(--font-size-sm);
329
+ --calendar-day-radius: var(--radius-md);
330
+ --calendar-chevron-size: var(--icon-size-md);
331
+
332
+ /* TRANSFER — the two-pane list mover. Pane height, header rhythm and row density were literal
333
+ * on the component (#319), so a service could not fit the panes to its own page grid or
334
+ * tighten the row for a dense admin table without forking. */
335
+ --transfer-pane-min-height: 14rem;
336
+ --transfer-pane-radius: var(--radius-md);
337
+ --transfer-panes-space-gap: var(--space-3);
338
+ --transfer-header-space-inline: var(--space-3);
339
+ --transfer-header-space-block: var(--space-2);
340
+ --transfer-header-font-size: var(--font-size-sm);
341
+ --transfer-header-space-gap: var(--space-2);
342
+ --transfer-search-space-inset: var(--space-2);
343
+ --transfer-list-space-inset: var(--space-1);
344
+ --transfer-row-space-inline: var(--space-2);
345
+ --transfer-row-space-block: var(--space-2);
346
+ --transfer-row-space-gap: var(--space-2);
347
+ --transfer-row-radius: var(--radius-sm);
348
+ --transfer-row-font-size: var(--font-size-sm);
349
+ /* The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to
350
+ * sit level with the first line's cap height rather than its box. */
351
+ --transfer-row-check-space-block-start: 0.125rem;
352
+ --transfer-row-description-font-size: var(--font-size-xs);
353
+ --transfer-empty-space-block: var(--space-8);
354
+ --transfer-actions-space-gap: var(--space-2);
355
+ --transfer-action-icon-size: var(--icon-size-md);
356
+
357
+ /* MONTH PICKER — shared by MonthPicker and MonthRangePicker, which render the same year-nav +
358
+ * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs
359
+ * now drives both, so they cannot drift apart the way Select and DropdownMenu once did. */
360
+ --month-picker-panel-space-inset: var(--space-3);
361
+ --month-picker-grid-space-block-start: var(--space-3);
362
+ --month-picker-grid-space-gap: var(--space-1);
363
+ --month-picker-cell-space-inline: var(--space-4);
364
+ --month-picker-nav-font-size: var(--font-size-sm);
365
+ /* Year-nav chevrons read as secondary until hovered — same treatment as the Calendar nav, so
366
+ * the two panels stay visually consistent when either is retuned. */
367
+ --month-picker-nav-rest-alpha: var(--calendar-nav-rest-alpha);
368
+ --month-picker-icon-size: var(--icon-size-md);
369
+ --month-picker-separator-icon-size: var(--icon-size-sm);
370
+
371
+ /* BUTTON size-xs box + the count pill (#319, reconciled in #316).
372
+ *
373
+ * `size="xs"` now reads the tier, like every other control in the library. It used to be
374
+ * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs`
375
+ * (1.5rem) that `size="icon-xs"` already used — the same tier name, two heights, so an xs
376
+ * Button never lined up with the xs control beside it. It was also the exact shape this repo's
377
+ * own rule forbids: an ad-hoc `calc(var(--control-height) ± length)` silently re-derives a tier
378
+ * and drifts from its siblings (the bug that made Pagination's size-changer taller than its
379
+ * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not
380
+ * move with density while the tier did.
381
+ *
382
+ * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under
383
+ * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside
384
+ * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an
385
+ * inconsistency, not a visual preference. It is a real visual change on the most-used component
386
+ * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets
387
+ * --button-xs-height back. */
388
+ --button-xs-height: var(--control-height-xs);
389
+ --button-xs-space-inline: var(--space-2);
390
+ --button-xs-space-gap: var(--space-1);
391
+ --button-xs-font-size: var(--font-size-xs);
392
+ --button-xs-icon-size: var(--icon-size-xs);
393
+ /* Icon-adjacent padding: a button whose only child is an icon needs less inline room than one
394
+ * carrying a label, or the glyph floats in a too-wide box. One knob per size tier. */
395
+ --button-icon-space-inline-md: var(--space-3);
396
+ --button-icon-space-inline-sm: 0.625rem;
397
+ --button-icon-space-inline-lg: var(--space-4);
398
+ --button-icon-space-inline-xs: 0.375rem;
399
+ --button-space-block: var(--space-2);
400
+ /* Count pill — a borderless counter that reads on the button's own surface. */
401
+ /* OTP separator glyph. Was a bare 1rem in styles/control.css, so the two-tier contract had no
402
+ * tier-2 route here at all: a consumer wanting a different separator could only use !important
403
+ * or fork the stylesheet. Same value, now reachable. */
404
+ /* The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a
405
+ * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to
406
+ * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared
407
+ * rather than forced onto one.
408
+ * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band */
409
+ --otp-caret-block-size: 1rem;
410
+ --otp-caret-inline-size: var(--stroke-hairline);
411
+ --otp-separator-icon-size: var(--icon-size-md);
412
+
413
+ --button-count-min-width: var(--space-4);
414
+ --button-count-space-inline: var(--space-1);
415
+ --button-count-font-size: var(--font-size-xs);
416
+
417
+ /* Count pill · COLOUR (gh#320). The pill used to tint itself translucently over whatever the
418
+ * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means
419
+ * its contrast was a FUNCTION of that surface — and five of those combinations measured below
420
+ * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text:
421
+ *
422
+ * default (light) 3.88 · destructive (dark) 4.29
423
+ * outline family (light, at rest) 4.32 · outline family (light, hover) 3.64
424
+ * outline family (dark, hover) 3.68
425
+ *
426
+ * The hover rows are the reason a static sweep never caught this: `--accent` only appears under
427
+ * the cursor, so no screenshot of a resting page contains the failing state. Same shape as
428
+ * gh#299 and gh#315 — a value that passes on the surface someone happened to check.
429
+ *
430
+ * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's
431
+ * contrast independent of the button's variant AND of hover. Each filled variant uses its own
432
+ * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less
433
+ * legible under any theme that keeps that button readable at all:
434
+ *
435
+ * default --primary on --primary-foreground 5.04 light · 7.07 dark
436
+ * destructive --destructive on --destructive-foreground 6.10 light · 5.53 dark
437
+ * secondary --secondary on --secondary-foreground 14.25 light · 12.40 dark
438
+ * outline family --foreground on --muted 14.25 light · 12.40 dark
439
+ *
440
+ * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the
441
+ * counter still reads as quiet text rather than a badge (#44) — it is legibility of the DIGITS
442
+ * that went up, not loudness of the pill.
443
+ *
444
+ * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so
445
+ * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md ·
446
+ * "Role-mirror knobs MUST be `initial`"). */
447
+ --button-count-background: initial;
448
+ --button-count-color: initial;
449
+ --button-count-default-background: initial;
450
+ --button-count-default-color: initial;
451
+ --button-count-destructive-background: initial;
452
+ --button-count-destructive-color: initial;
453
+ --button-count-secondary-background: initial;
454
+ --button-count-secondary-color: initial;
455
+
456
+ /* LABEL — the Label primitive's own box and type (#319). Every one of these was a Tailwind
457
+ * literal baked into label.tsx (`text-sm leading-none flex items-center gap-2`), so a service
458
+ * whose form grid wanted a smaller/denser label had to hand-write font-size on every call site
459
+ * (which is exactly why --form-label-font-size had to be invented as a per-instance escape).
460
+ * They live under the `control` prefix because Label is a control-family primitive and
461
+ * src/tokens/components/ has no `label` file of its own.
462
+ *
463
+ * Defaults reproduce today's rendering exactly:
464
+ * font-size ← `text-sm` → --text-sm → var(--font-size-sm)
465
+ * line-height ← `leading-none` → 1 (leading-none beats text-sm's companion line-height) */
466
+ --control-label-font-size: var(--font-size-sm);
467
+ --control-label-line-height: 1;
468
+ /* Label → addon/required-marker gap. A RAW rem, deliberately NOT var(--space-2): the `gap-2` it
469
+ * replaces reads Tailwind's own --spacing grid, which this repo does NOT density-scale, so a
470
+ * --space-* default would silently resize every label row under a compact/comfortable density.
471
+ * A service that WANTS it to follow density points the knob at var(--space-2) itself. */
472
+ --control-label-space-gap: 0.5rem;
473
+ /* Weight of a label whose `peer` control is disabled. Deliberately its OWN knob rather than the
474
+ * global --disabled-opacity (0.5): a label must stay READABLE next to a disabled control — it is
475
+ * still the field's accessible name — so it rests one step heavier than the control it names.
476
+ * Default = the historical `peer-disabled:opacity-70`. */
477
+ --control-label-disabled-alpha: 0.7;
478
+ }
479
+
130
480
  @media (pointer: coarse) {
131
481
  :root {
132
- --control-height-compact: 2.75rem;
133
- --control-height-default: 2.75rem;
482
+ --control-height-compact: var(--band-height-xl);
483
+ --control-height-default: var(--band-height-xl);
134
484
  }
135
485
  }
@@ -1,6 +1,16 @@
1
1
  /* Data-display component tokens — small-by-design text knobs (rule #45/#46). */
2
2
 
3
3
  :root {
4
+ /* Accordion chevron and Carousel arrow. Both were bare 1rem literals in
5
+ * data-display-layout.css — glyphs with no tier-2 route, invisible to the icon ratchet because
6
+ * neither selector contains the word "icon". */
7
+ --accordion-chevron-size: var(--icon-size-md);
8
+ --carousel-arrow-icon-size: var(--icon-size-md);
9
+
10
+ /* Outline-form ring gaps — a hugging ring would touch these marks
11
+ * (styles/focus-ring.css). */
12
+ --accordion-focus-ring-offset: 2px;
13
+ --carousel-dot-focus-ring-offset: 2px;
4
14
  --progress-label-font-size: var(--font-size-xs);
5
15
  --tree-item-title-font-size: var(--font-size-xs);
6
16
  --tree-item-description-font-size: var(--font-size-xs);
@@ -40,6 +50,46 @@
40
50
  --avatar-tinted-foreground: initial;
41
51
  --avatar-tinted-glyph-size: var(--control-icon-size);
42
52
 
53
+ /* PRESENCE INDICATOR — `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime
54
+ reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is
55
+ a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the
56
+ hand-rolled workaround this replaces —
57
+ `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span — bakes four
58
+ service-tunable constants and a raw palette colour into a consumer page.
59
+
60
+ --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks
61
+ EVERY avatar the system paints — the --control-height default box, a `size-12` call site, the
62
+ --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size
63
+ (6rem), the 36px ListRow density="compact" leading mark — instead of freezing one diameter that
64
+ is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the
65
+ legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot
66
+ further into a round mark than into a square one.
67
+
68
+ --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared
69
+ `initial` here so the --background / --success / --warning / --destructive / --muted-foreground
70
+ defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still
71
+ reaches them (docs/TOKENS.md · "Role-mirror knobs MUST be `initial`"). Binding them to the role
72
+ here would freeze the ring light-mode-white on a dark avatar.
73
+
74
+ Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE — filled · half
75
+ filled · barred · hollow — and a localized sr-only string, so --avatar-presence-stroke-width and
76
+ --avatar-presence-bar-* are part of the accessible encoding, not decoration.
77
+
78
+ Defaults = 30% of the mark, floor 0.5rem · flush with the mark's corner · 2px separator ring in
79
+ --background · 1.5px state stroke · a 56%-wide, 1.5px dnd bar. */
80
+ --avatar-presence-size: 30%;
81
+ --avatar-presence-min-size: var(--space-2);
82
+ --avatar-presence-inset: 0;
83
+ --avatar-presence-ring-width: var(--stroke-md);
84
+ --avatar-presence-stroke-width: var(--stroke-sm);
85
+ --avatar-presence-bar-inline-size: 56%;
86
+ --avatar-presence-bar-block-size: var(--stroke-sm);
87
+ --avatar-presence-ring-color: initial;
88
+ --avatar-presence-online-color: initial;
89
+ --avatar-presence-away-color: initial;
90
+ --avatar-presence-busy-color: initial;
91
+ --avatar-presence-offline-color: initial;
92
+
43
93
  /* Progress track + fill — `initial` so the role defaults re-resolve under a scoped theme.
44
94
  Track reads --secondary, fill reads --success; a service re-tones once.
45
95
  Defaults = hsl(var(--secondary)) track · hsl(var(--success)) fill. */
@@ -65,4 +115,29 @@
65
115
  --qr-code-size-sm: 8rem;
66
116
  --qr-code-size-md: 10rem;
67
117
  --qr-code-size-lg: 12.5rem;
118
+
119
+ /* SCROLL AREA — the custom scrollbar rail. `.ui-scroll-area-*` did not exist at all: the whole
120
+ * rail shipped as Tailwind literals on the component (`w-2.5 p-px` / `h-2.5 p-px`,
121
+ * `rounded-full`), so a service could not widen the bar for a touch/coarse-pointer console or
122
+ * square the thumb to match a sharp-cornered theme without forking ScrollArea (rule #45).
123
+ * --scroll-area-bar-size is a RAW rem on purpose: it replaces a FLAT Tailwind step (2.5 = 10px),
124
+ * not a --space-* step, so binding it to the density-scaled scale would silently change today's
125
+ * rendering under a non-default --scaling. Defaults reproduce the previous rail exactly. */
126
+ --scroll-area-bar-size: 0.625rem;
127
+ /* Hairline inset that keeps the thumb off the rail edge — a service running a thicker bar
128
+ * usually wants a proportionally larger gutter here. */
129
+ --scroll-area-bar-padding: 1px;
130
+ --scroll-area-thumb-radius: var(--radius-pill);
131
+
132
+ /* PERMISSION MATRIX — the sticky-first-column grid. Nothing here was themeable: the class
133
+ * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant
134
+ * lived on the component (#319). A JA/VI service whose role names run longer than the English
135
+ * ones could not widen the label column without forking. */
136
+ --permission-matrix-label-width: 16rem;
137
+ --permission-matrix-role-space-gap: var(--space-1);
138
+ --permission-matrix-name-space-gap: 0.375rem;
139
+ --permission-matrix-cell-icon-size: var(--control-icon-size);
140
+ /* Floor for the scrolling body — below this the columns collapse into unreadable slivers, so
141
+ * the table scrolls instead. Mirrors --table-surface-min-inline-size on DataTable. */
142
+ --permission-matrix-min-width: 48rem;
68
143
  }
@@ -1,6 +1,31 @@
1
1
  /* Data-entry component tokens — small-by-design text knobs (rule #45/#46). */
2
2
 
3
3
  :root {
4
+
4
5
  --password-strength-score-font-size: var(--font-size-xs);
5
6
  --password-strength-checklist-font-size: var(--font-size-xs);
7
+
8
+ /* BranchScopePicker (gh#319). `.ui-branch-scope-picker` was a bare hook with no rule anywhere:
9
+ * every measure lived as a Tailwind literal on the component, so a service could only retune
10
+ * the control by forking it (#45).
11
+ *
12
+ * All four are FLAT rems, not `--space-*` aliases, on purpose: the literals they replace read
13
+ * Tailwind's own `--spacing` grid, which this package never remaps and which does NOT move with
14
+ * the density axis. Aliasing them to `--space-3`/`--space-2`/`--space-1` would silently start
15
+ * scaling the control inside a `.ui-density-*` subtree — a visual change, not a refactor. */
16
+ /* Stack rhythm between the mode radios, the subset box and the error line (was `gap-3`). */
17
+ --branch-scope-picker-gap: 0.75rem;
18
+ /* Read-only summary: the wrapped run of branch Badges (was `gap-1.5`). */
19
+ --branch-scope-picker-badges-gap: 0.375rem;
20
+ /* The `mode="selected"` subset box — its inner stack rhythm and the inset that clears its
21
+ * indent rule (was `gap-2` / `ps-4`). */
22
+ --branch-scope-picker-subset-gap: 0.5rem;
23
+ --branch-scope-picker-subset-padding-inline: 1rem;
24
+ /* The indent rule itself is CHROME (#44), so it is a knob: a service that wants the flat
25
+ * treatment sets it to 0 rather than forking the component. Default = today's `border-s-2`. */
26
+ --branch-scope-picker-subset-border-width: var(--stroke-md);
27
+ /* Bounded height of the scrollable branch list (was `max-h-64`). Flat by design: the box is a
28
+ * keyboard-reachable scroll region whose job is to cap the control's measure, so it must not
29
+ * grow with density and push the surrounding form off a 390px frame. */
30
+ --branch-scope-picker-list-max-height: 16rem;
6
31
  }
@@ -11,4 +11,26 @@
11
11
  * block above an editable field on the same card) retunes this to `var(--space-4)` to match
12
12
  * Form's own field-to-field rhythm instead of the two blocks reading as visually unrelated. */
13
13
  --descriptions-row-gap: var(--space-3);
14
+ /* Column-to-column gap of the <dl> grid (#319). Was a hardcoded `gap-x-6`, the one axis of the
15
+ * grid rhythm a service could NOT reach while --descriptions-row-gap was already a knob — so a
16
+ * 2/3-column detail block could be retuned vertically but never horizontally.
17
+ * A RAW rem, deliberately NOT var(--space-6): `gap-x-6` reads Tailwind's own --spacing grid,
18
+ * which this repo does NOT density-scale, so a --space-* default would silently rescale the
19
+ * column gap under a compact/comfortable density. Point it at var(--space-6) to opt in. */
20
+ --descriptions-column-gap: 1.5rem;
21
+ /* Label → value gap inside ONE item when layout="horizontal" (was `gap-x-3`). Pairs with
22
+ * --descriptions-label-width: a service that narrows the label column usually wants to close
23
+ * this gap in the same step. Raw rem for the same --spacing reason as above. */
24
+ --descriptions-label-gap: 0.75rem;
25
+ /* VALUE TYPOGRAPHY (gh#294 + #319). `FormField staticText` renders its read-only value with the
26
+ * SAME typography as a <dd> so a static Form row and a Descriptions value are indistinguishable
27
+ * when mixed on one card. That contract used to be two copies of the literal `text-sm` plus a
28
+ * comment asking future editors to keep them in sync; both call sites now read these tokens, so
29
+ * the mirror is mechanical and a service retunes BOTH from one place. */
30
+ --descriptions-value-font-size: var(--font-size-sm);
31
+ /* Companion to --descriptions-value-font-size (the gh#260 bug). The `text-sm` utility this
32
+ * replaces ALSO set a line-height, via Tailwind's default `--text-sm--line-height`
33
+ * (= calc(1.25 / 0.875)); the theme remaps --text-sm but never that companion. Without this the
34
+ * value would silently inherit ambient leading instead of its own 20px line box. */
35
+ --descriptions-value-line-height: calc(1.25 / 0.875);
14
36
  }