actual-css 0.3.0 → 0.4.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 (205) hide show
  1. package/CHANGELOG.md +78 -3
  2. package/README.md +35 -30
  3. package/dist/actual.css +98 -4882
  4. package/dist/actual.full.css +7090 -0
  5. package/dist/actual.full.js +1 -0
  6. package/dist/actual.full.min.css +1 -0
  7. package/dist/actual.js +1 -1
  8. package/dist/actual.min.css +1 -1
  9. package/docs/design-notes/cascade-layer.md +35 -13
  10. package/docs/design-notes/column-layout.md +677 -0
  11. package/docs/design-notes/forced-colors.md +3 -0
  12. package/docs/design-notes/grid.md +612 -0
  13. package/docs/design-notes/naming.md +15 -0
  14. package/docs/design-notes/widget-primitives.md +1 -0
  15. package/docs/navigation.json +5 -1
  16. package/docs/pages/components/alert.md +16 -14
  17. package/docs/pages/components/aura.md +1 -1
  18. package/docs/pages/components/avatar.md +6 -6
  19. package/docs/pages/components/badge.md +7 -7
  20. package/docs/pages/components/button.md +22 -17
  21. package/docs/pages/components/card.md +29 -13
  22. package/docs/pages/components/chat.md +8 -8
  23. package/docs/pages/components/dialog.md +7 -7
  24. package/docs/pages/components/drawer.md +3 -3
  25. package/docs/pages/components/fab.md +7 -7
  26. package/docs/pages/components/navbar.md +9 -8
  27. package/docs/pages/components/pagination.md +4 -3
  28. package/docs/pages/components/skeleton.md +4 -4
  29. package/docs/pages/components/spinner.md +5 -5
  30. package/docs/pages/components/table.md +30 -5
  31. package/docs/pages/enhancements/flyout.md +13 -13
  32. package/docs/pages/enhancements/overview.md +1 -1
  33. package/docs/pages/enhancements/scrollspy.md +6 -6
  34. package/docs/pages/enhancements/status-bar.md +5 -1
  35. package/docs/pages/enhancements/tabs.md +6 -6
  36. package/docs/pages/enhancements/tooltip.md +1 -1
  37. package/docs/pages/examples/overview.md +41 -22
  38. package/docs/pages/forms/choice-cards.md +11 -6
  39. package/docs/pages/forms/controls.md +1 -1
  40. package/docs/pages/forms/detached-actions.md +1 -1
  41. package/docs/pages/forms/floating-field.md +10 -5
  42. package/docs/pages/forms/form-actions.md +1 -1
  43. package/docs/pages/forms/input-policy.md +1 -1
  44. package/docs/pages/forms/joined-controls.md +4 -4
  45. package/docs/pages/forms/labels.md +6 -6
  46. package/docs/pages/forms/otp.md +1 -1
  47. package/docs/pages/forms/overview.md +19 -19
  48. package/docs/pages/forms/password.md +1 -1
  49. package/docs/pages/forms/range.md +1 -1
  50. package/docs/pages/forms/states-and-sizes.md +41 -45
  51. package/docs/pages/forms/sticky-actions.md +3 -3
  52. package/docs/pages/forms/switch.md +110 -2
  53. package/docs/pages/forms/validation.md +9 -9
  54. package/docs/pages/foundations/philosophy.md +1 -1
  55. package/docs/pages/foundations/theming.md +29 -7
  56. package/docs/pages/foundations/tokens.md +22 -10
  57. package/docs/pages/foundations/typography.md +50 -20
  58. package/docs/pages/guides/bootstrap.md +11 -11
  59. package/docs/pages/guides/integrating-actual-css.md +186 -0
  60. package/docs/pages/guides/introduction.md +23 -26
  61. package/docs/pages/guides/modular-import.md +173 -0
  62. package/docs/pages/guides/progressive-enhancement.md +81 -78
  63. package/docs/pages/guides/tailwind.md +6 -8
  64. package/docs/pages/layout/app-shell.md +1 -1
  65. package/docs/pages/layout/center.md +1 -1
  66. package/docs/pages/layout/choosing.md +39 -0
  67. package/docs/pages/layout/cluster.md +4 -2
  68. package/docs/pages/layout/column-layout.md +206 -0
  69. package/docs/pages/layout/frame.md +6 -1
  70. package/docs/pages/layout/grid.md +100 -102
  71. package/docs/pages/layout/list.md +1 -1
  72. package/docs/pages/layout/media.md +10 -3
  73. package/docs/pages/layout/overview.md +15 -2
  74. package/docs/pages/layout/scroll-snap.md +2 -2
  75. package/docs/pages/layout/scroll-target.md +45 -0
  76. package/docs/pages/layout/sidebar-layout.md +20 -19
  77. package/docs/pages/layout/stack.md +90 -1
  78. package/docs/pages/layout/switcher.md +10 -26
  79. package/docs/pages/layout/topbar.md +2 -2
  80. package/docs/pages/patterns/actions.md +1 -1
  81. package/docs/pages/patterns/code-block.md +1 -1
  82. package/docs/pages/patterns/exclusive-accordion.md +1 -1
  83. package/docs/pages/patterns/hierarchical-nav.md +1 -1
  84. package/docs/pages/patterns/icon-input.md +1 -1
  85. package/docs/pages/patterns/intent-nesting.md +1 -1
  86. package/docs/pages/patterns/navigation-list.md +1 -1
  87. package/docs/pages/patterns/overview.md +1 -1
  88. package/docs/pages/patterns/structured-lists.md +1 -1
  89. package/docs/pages/utilities/accessibility.md +3 -3
  90. package/docs/pages/utilities/flex.md +15 -15
  91. package/docs/pages/utilities/links.md +4 -4
  92. package/docs/pages/utilities/list.md +8 -7
  93. package/docs/pages/utilities/overflow.md +8 -8
  94. package/docs/pages/utilities/overview.md +1 -1
  95. package/docs/pages/utilities/shape.md +3 -3
  96. package/docs/pages/utilities/sizing-wrapping.md +23 -16
  97. package/docs/pages/utilities/spacing.md +20 -20
  98. package/docs/pages/utilities/text.md +10 -10
  99. package/llms.txt +46 -4
  100. package/package.json +43 -13
  101. package/scripts/reserved-classes.json +261 -0
  102. package/src/cli/actual-css.js +133 -0
  103. package/src/css/actual.css +1 -28
  104. package/src/css/actual.full.css +11 -2
  105. package/src/css/components/alert.css +33 -10
  106. package/src/css/components/badge.css +38 -19
  107. package/src/css/components/breadcrumb.css +1 -1
  108. package/src/css/components/button.css +35 -13
  109. package/src/css/components/card.css +37 -4
  110. package/src/css/components/drawer.css +27 -2
  111. package/src/css/components/flyout.css +5 -0
  112. package/src/css/components/index.css +32 -27
  113. package/src/css/components/join.css +8 -1
  114. package/src/css/components/key.css +2 -0
  115. package/src/css/components/menu.css +11 -1
  116. package/src/css/components/meter.css +4 -1
  117. package/src/css/components/modal.css +5 -0
  118. package/src/css/components/navbar.css +13 -5
  119. package/src/css/components/pagination.css +11 -0
  120. package/src/css/components/spinner.css +5 -0
  121. package/src/css/components/status-bar.css +5 -0
  122. package/src/css/components/surface.css +5 -0
  123. package/src/css/components/table.css +6 -0
  124. package/src/css/components/tooltip.css +5 -0
  125. package/src/css/core/index.css +9 -0
  126. package/src/css/core/print.css +18 -0
  127. package/src/css/{theme.css → core/theme.css} +20 -10
  128. package/src/css/{tokens.css → core/tokens.css} +15 -2
  129. package/src/css/{variants.css → core/variants.css} +27 -13
  130. package/src/css/effects/index.css +2 -0
  131. package/src/css/forms/base.css +8 -0
  132. package/src/css/forms/choice-card.css +5 -4
  133. package/src/css/forms/choice.css +49 -37
  134. package/src/css/forms/control.css +6 -1
  135. package/src/css/forms/custom-select.css +2 -0
  136. package/src/css/{optional → forms}/floating-field.css +25 -7
  137. package/src/css/forms/index.css +10 -13
  138. package/src/css/forms/native.css +15 -2
  139. package/src/css/{optional → forms}/otp.css +20 -16
  140. package/src/css/forms/switch.css +31 -29
  141. package/src/css/layout/app-shell.css +6 -0
  142. package/src/css/layout/center.css +13 -0
  143. package/src/css/layout/cluster.css +21 -0
  144. package/src/css/layout/column-layout.css +144 -0
  145. package/src/css/layout/container-query.css +13 -0
  146. package/src/css/layout/frame.css +19 -0
  147. package/src/css/layout/grid.css +141 -0
  148. package/src/css/layout/index.css +42 -0
  149. package/src/css/layout/measure.css +9 -0
  150. package/src/css/layout/media.css +10 -0
  151. package/src/css/layout/scroll-target.css +15 -0
  152. package/src/css/layout/scroller.css +41 -0
  153. package/src/css/layout/sidebar-layout.css +31 -0
  154. package/src/css/layout/stack.css +21 -0
  155. package/src/css/layout/switcher.css +23 -0
  156. package/src/css/{optional/layout-extra.css → layout/topbar.css} +0 -20
  157. package/src/css/typography/index.css +5 -0
  158. package/src/css/typography/lead.css +7 -0
  159. package/src/css/{components → typography}/overline.css +10 -4
  160. package/src/css/{prose.css → typography/prose.css} +53 -38
  161. package/src/css/{utilities.css → utilities/base.css} +2 -0
  162. package/src/css/utilities/index.css +3 -0
  163. package/src/js/escape.js +36 -0
  164. package/src/js/flyout.js +22 -12
  165. package/src/js/full.js +26 -0
  166. package/src/js/index.js +5 -21
  167. package/src/js/surface.js +4 -32
  168. package/src/js/tooltip.js +65 -24
  169. package/src/js/validation.js +1 -1
  170. package/src/tooling/css-bundle.js +308 -0
  171. package/dist/actual-themes.min.css +0 -1
  172. package/dist/optional.css +0 -1048
  173. package/dist/optional.min.css +0 -1
  174. package/docs/pages/guides/optional-components.md +0 -33
  175. package/src/css/grid.css +0 -96
  176. package/src/css/layout.css +0 -108
  177. package/src/css/optional/index.css +0 -10
  178. package/src/css/optional/scroller.css +0 -63
  179. package/src/css/print.css +0 -47
  180. package/src/css/themes/corporate.css +0 -60
  181. package/src/css/themes/cyberpunk.css +0 -60
  182. package/src/css/themes/dim.css +0 -33
  183. package/src/css/themes/edge.css +0 -117
  184. package/src/css/themes/forest.css +0 -65
  185. package/src/css/themes/gradient.css +0 -97
  186. package/src/css/themes/index.css +0 -43
  187. package/src/css/themes/indigo.css +0 -33
  188. package/src/css/themes/lavender.css +0 -66
  189. package/src/css/themes/mono.css +0 -60
  190. package/src/css/themes/neon.css +0 -66
  191. package/src/css/themes/ocean.css +0 -60
  192. package/src/css/themes/petrol.css +0 -64
  193. package/src/css/themes/spruce.css +0 -64
  194. package/src/css/themes/square.css +0 -65
  195. package/src/css/themes/sunset.css +0 -66
  196. /package/src/css/{optional → components}/chat.css +0 -0
  197. /package/src/css/{optional → components}/fab.css +0 -0
  198. /package/src/css/{base.css → core/base.css} +0 -0
  199. /package/src/css/{focus.css → core/focus.css} +0 -0
  200. /package/src/css/{intents.css → core/intents.css} +0 -0
  201. /package/src/css/{reset.css → core/reset.css} +0 -0
  202. /package/src/css/{optional → effects}/aura.css +0 -0
  203. /package/src/css/{optional → layout}/scroll-snap.css +0 -0
  204. /package/src/css/{optional/typography-fluid.css → typography/fluid.css} +0 -0
  205. /package/src/css/{optional/utilities-extra.css → utilities/extra.css} +0 -0
@@ -13,6 +13,10 @@
13
13
  * --badge-radius
14
14
  *
15
15
  * Internal:
16
+ * --badge-default-bg
17
+ * --badge-default-fg
18
+ * --badge-default-border
19
+ * --badge-default-hover-bg
16
20
  * --badge-bg
17
21
  * --badge-fg
18
22
  * --badge-border
@@ -31,11 +35,15 @@
31
35
  .badge {
32
36
  --badge-size: var(--density-compact-size);
33
37
  --badge-pad-x: 0.55em;
34
- --badge-font-size: 0.8125rem;
38
+ --badge-font-size: var(--font-size-xs);
35
39
  --badge-dot-size: 0.5rem;
36
- --badge-bg: var(--ui-bg, var(--surface-subtle));
37
- --badge-fg: var(--ui-fg, var(--intent, var(--text)));
38
- --badge-border: var(--ui-border, var(--border));
40
+ --badge-default-bg: var(--surface-subtle);
41
+ --badge-default-fg: var(--intent, var(--text));
42
+ --badge-default-border: var(--border);
43
+ --badge-default-hover-bg: var(--badge-bg);
44
+ --badge-bg: var(--ui-bg, var(--badge-default-bg));
45
+ --badge-fg: var(--ui-fg, var(--badge-default-fg));
46
+ --badge-border: var(--ui-border, var(--badge-default-border));
39
47
  --badge-radius: var(--radius-full);
40
48
 
41
49
  display: inline-flex;
@@ -62,22 +70,26 @@
62
70
  box-shadow var(--duration);
63
71
  }
64
72
 
65
- /* Soft by default — duplicated here with @sync markers for check-sync.js
66
- to guard, the same way alert.css guards its own soft recipe. */
73
+ /* Soft by default. Local defaults remain behind --ui-* in the resolution
74
+ chain, so explicit shared treatments compose without selector exclusions. */
67
75
  @supports (color: color-mix(in oklch, red, white)) {
68
- .badge:where(:not(.solid, .outline)) {
76
+ .badge {
69
77
  /* @sync soft-recipe */
70
- --badge-bg: color-mix(
71
- in oklch,
78
+ --badge-default-bg: color-mix(
79
+ in oklab,
72
80
  var(--surface) var(--soft-bg-mix),
73
81
  var(--intent, var(--neutral))
74
82
  );
75
- --badge-fg: var(--intent, var(--text));
76
- --badge-border: color-mix(
77
- in oklch,
83
+ --badge-default-border: color-mix(
84
+ in oklab,
78
85
  var(--surface) var(--soft-border-mix),
79
86
  var(--intent, var(--neutral))
80
87
  );
88
+ --badge-default-fg: color-mix(
89
+ in oklab,
90
+ var(--text),
91
+ var(--intent, var(--text)) var(--soft-fg-mix)
92
+ );
81
93
  /* @sync soft-recipe */
82
94
  }
83
95
  }
@@ -97,8 +109,8 @@
97
109
  display: inline-grid;
98
110
  place-items: center;
99
111
  flex: 0 0 auto;
100
- min-inline-size: 1.5rem;
101
- min-block-size: 1.5rem;
112
+ min-inline-size: var(--density-compact-size);
113
+ min-block-size: var(--density-compact-size);
102
114
  margin-block: -0.25rem;
103
115
  margin-inline-start: -0.15em;
104
116
  margin-inline-end: -0.45em;
@@ -119,7 +131,7 @@
119
131
  /* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
120
132
  Declared outside :hover like button.css for the same reason. */
121
133
  :is(a.badge, button.badge).outline {
122
- --ui-hover-bg: var(--surface-subtle);
134
+ --ui-hover-bg: transparent;
123
135
  }
124
136
 
125
137
  @supports (color: color-mix(in oklch, red, white)) {
@@ -128,23 +140,24 @@
128
140
  }
129
141
 
130
142
  :is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
131
- --ui-hover-bg: color-mix(
132
- in oklch,
143
+ --badge-default-hover-bg: color-mix(
144
+ in oklab,
133
145
  var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
134
146
  var(--intent, var(--neutral))
135
147
  );
136
148
  }
137
149
 
138
150
  :is(a.badge, button.badge).outline {
139
- --ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
151
+ --ui-hover-bg: color-mix(in oklch, currentColor 10%, transparent);
140
152
  }
141
153
  }
142
154
 
143
155
  :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"])):hover {
144
- background: var(--ui-hover-bg, var(--badge-bg));
156
+ background: var(--ui-hover-bg, var(--badge-default-hover-bg));
145
157
  }
146
158
 
147
159
  :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"], .outline)):hover {
160
+ /* Huge inset spread paints the overlay over the pill radius (see button.css). */
148
161
  box-shadow: inset 0 0 0 999px var(--hover-overlay);
149
162
  }
150
163
 
@@ -184,3 +197,9 @@ a.badge[aria-disabled="true"] {
184
197
  color: GrayText;
185
198
  }
186
199
  }
200
+ @media print {
201
+ .badge {
202
+ border: var(--border-width) solid CanvasText;
203
+ break-inside: avoid;
204
+ }
205
+ }
@@ -42,7 +42,7 @@
42
42
  border-radius: var(--radius-sm);
43
43
  }
44
44
 
45
- .breadcrumb :where(a, span):where([aria-current]:not([aria-current="false"])) {
45
+ .breadcrumb :where(li, a, span):where([aria-current]:not([aria-current="false"])) {
46
46
  color: var(--text);
47
47
  font-weight: var(--font-weight-medium);
48
48
  }
@@ -18,11 +18,16 @@
18
18
  * --btn-focus-color
19
19
  * --btn-gap
20
20
  * --btn-hover-overlay
21
+ * --btn-default-bg
22
+ * --btn-default-fg
23
+ * --btn-default-border
24
+ * --btn-border-color
21
25
  *
22
26
  * Internal:
23
27
  * --btn-bg
24
28
  * --btn-fg
25
29
  * --btn-border
30
+ * --btn-default-hover-bg
26
31
  * --btn-focus-ring-color
27
32
  * --btn-border-block-end-color
28
33
  * --btn-border-block-end-width
@@ -47,6 +52,7 @@
47
52
  --btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
48
53
  --btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
49
54
  --btn-border: var(--ui-border, var(--btn-default-border, transparent));
55
+ --btn-default-hover-bg: var(--btn-bg);
50
56
  --btn-radius: var(--radius);
51
57
  --btn-focus-color: var(--intent, var(--focus));
52
58
  --btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
@@ -99,7 +105,7 @@
99
105
  fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
100
106
  .btn.ghost {
101
107
  --ui-bg: transparent;
102
- --ui-fg: var(--intent, var(--text));
108
+ --ui-fg: var(--intent, currentColor);
103
109
  --ui-border: transparent;
104
110
  }
105
111
 
@@ -117,35 +123,35 @@
117
123
  Declared outside :hover: the variables resolve only when the hover rule
118
124
  reads them, so the values are inert until interaction. */
119
125
  .btn:is(.outline, .ghost) {
120
- --ui-hover-bg: var(--surface-subtle);
126
+ --ui-hover-bg: transparent;
121
127
  }
122
128
 
123
129
  @supports (color: color-mix(in oklch, red, white)) {
124
- .btn:not(:where(.soft, .outline, .ghost, .link)) {
125
- --ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
130
+ .btn {
131
+ --btn-default-hover-bg: color-mix(in oklch, var(--btn-bg) 88%, black);
126
132
  }
127
133
 
128
134
  .btn.soft {
129
135
  --ui-hover-bg: color-mix(
130
- in oklch,
136
+ in oklab,
131
137
  var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
132
138
  var(--intent, var(--neutral))
133
139
  );
134
140
  }
135
141
 
136
- .btn.outline {
137
- --ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
138
- }
139
-
142
+ .btn.outline,
140
143
  .btn.ghost {
141
- --ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
144
+ --ui-hover-bg: color-mix(in oklch, currentColor 10%, transparent);
142
145
  }
143
146
  }
144
147
 
145
148
  /* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
146
149
  the overlay darkening for solid/soft is .btn-specific. */
147
150
  .btn:not(:where(:disabled, [aria-disabled="true"])):hover {
148
- background: var(--ui-hover-bg, var(--btn-bg));
151
+ background: var(--ui-hover-bg, var(--btn-default-hover-bg));
152
+ /* Huge inset spread paints the overlay over any border-radius without a
153
+ pseudo-element; a plain background would replace the fill instead of
154
+ darkening it. */
149
155
  box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
150
156
  }
151
157
 
@@ -155,7 +161,7 @@
155
161
 
156
162
  .btn.link {
157
163
  --ui-bg: transparent;
158
- --ui-fg: var(--intent, var(--text));
164
+ --ui-fg: var(--intent, currentColor);
159
165
  --ui-border: transparent;
160
166
  --btn-radius: var(--radius-sm);
161
167
  --btn-border-block-end-color: transparent;
@@ -163,6 +169,8 @@
163
169
 
164
170
  min-block-size: auto;
165
171
  padding-inline: 0;
172
+ inline-size: fit-content;
173
+ max-inline-size: 100%;
166
174
  border-color: transparent;
167
175
  text-decoration: underline;
168
176
  text-underline-offset: 0.18em;
@@ -173,7 +181,6 @@
173
181
 
174
182
  .btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
175
183
  background: transparent;
176
- color: var(--intent, var(--text));
177
184
  text-decoration-thickness: 0.12em;
178
185
  }
179
186
 
@@ -245,6 +252,11 @@ a.btn[aria-disabled="true"] {
245
252
  forced-color-adjust: none;
246
253
  }
247
254
 
255
+ .btn:is([aria-pressed="true"], [aria-current="page"]):not(.link):focus-visible {
256
+ outline: var(--focus-outline);
257
+ box-shadow: none;
258
+ }
259
+
248
260
  .btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
249
261
  outline: var(--border-width) solid ButtonText;
250
262
  outline-offset: 2px;
@@ -258,3 +270,13 @@ a.btn[aria-disabled="true"] {
258
270
  forced-color-adjust: auto;
259
271
  }
260
272
  }
273
+ @media print {
274
+ :is(button, input[type="button"], input[type="submit"], input[type="reset"]).btn {
275
+ display: none !important;
276
+ }
277
+
278
+ a.btn[href^="http"]::after {
279
+ content: " (" attr(href) ")";
280
+ font-size: 0.875em;
281
+ }
282
+ }
@@ -2,8 +2,10 @@
2
2
  * Cards provide a neutral raised surface for grouped content.
3
3
  *
4
4
  * Surface, border, radius, padding, density, and elevation stay local here.
5
- * Direct header/footer children are structural slots. Page layout remains the
6
- * job of markup and layout primitives outside the card.
5
+ * Direct header/footer children are structural slots; a direct footer is
6
+ * anchored to the card's bottom edge when extra block space is available (so
7
+ * price/action rows align across equal-height cards in a grid). Page layout
8
+ * remains the job of markup and layout primitives outside the card.
7
9
  *
8
10
  * Public hooks:
9
11
  * --card-pad
@@ -15,6 +17,8 @@
15
17
  * edge children inherit the card corners. .bleed must be a direct child.
16
18
  *
17
19
  * Internal:
20
+ * --card-bg
21
+ * --card-fg
18
22
  * --heading
19
23
  * --busy-overlay-bg
20
24
  */
@@ -30,15 +34,32 @@
30
34
  --card-pad: var(--space-40);
31
35
  --card-radius: var(--radius-lg);
32
36
  --card-max-inline-size: none;
37
+ --card-bg: var(--ui-bg, var(--surface-raised));
38
+ --card-fg: var(--ui-fg, var(--text));
39
+
40
+ /* Keep contextual descendants tied to the surface this card actually owns. */
41
+ --heading: var(--card-fg);
42
+ --busy-overlay-bg: var(--card-bg);
33
43
 
34
44
  max-inline-size: var(--card-max-inline-size);
35
45
  padding: var(--card-pad);
36
- background: var(--ui-bg, var(--surface-raised));
37
- color: var(--ui-fg, var(--text));
46
+ background: var(--card-bg);
47
+ color: var(--card-fg);
38
48
  border: var(--border-width) solid var(--ui-border, var(--border));
39
49
  border-radius: var(--card-radius);
40
50
  }
41
51
 
52
+ /* The box lives at zero specificity so a layout primitive applied to the same
53
+ element owns it. components/ is imported after layout/, so a plain .card
54
+ declaration would beat .media (and any other non-column primitive) on source
55
+ order alone and silently flatten `class="card media"` into a column. The
56
+ column is still the default a bare .card gets, and > footer's margin-auto
57
+ bottom-anchoring depends on it. */
58
+ :where(.card) {
59
+ display: flex;
60
+ flex-direction: column;
61
+ }
62
+
42
63
  .card > * {
43
64
  min-inline-size: 0;
44
65
  }
@@ -61,6 +82,12 @@
61
82
  align-items: var(--items-align, flex-start);
62
83
  }
63
84
 
85
+ /* A direct footer is pinned to the bottom when the card has free space.
86
+ Specificity beats .stack > * so a .card.stack keeps this contract. */
87
+ .card > footer {
88
+ margin-block-start: auto;
89
+ }
90
+
64
91
  .card > :first-child {
65
92
  margin-block-start: 0;
66
93
  }
@@ -118,3 +145,9 @@
118
145
  .card > :where(header, footer, figure).bleed {
119
146
  padding: var(--card-pad);
120
147
  }
148
+ @media print {
149
+ .card {
150
+ border: var(--border-width) solid CanvasText;
151
+ break-inside: avoid;
152
+ }
153
+ }
@@ -108,8 +108,8 @@ dialog.drawer nav {
108
108
  }
109
109
  }
110
110
 
111
- /* RTL: swap closed transform directions so the drawer slides from the correct
112
- inline side. */
111
+ /* Safari before 16.4 does not support :dir(); keep the ancestor-attribute
112
+ path as the Minimal-tier fallback. */
113
113
  [dir="rtl"] dialog.drawer {
114
114
  --drawer-closed-translate: 100%;
115
115
  }
@@ -118,6 +118,26 @@ dialog.drawer nav {
118
118
  --drawer-closed-translate: -100%;
119
119
  }
120
120
 
121
+ /* :dir() follows inherited HTML directionality and handles nested direction
122
+ changes. Explicit ltr rules neutralize an outer RTL fallback selector. */
123
+ @supports selector(:dir(rtl)) {
124
+ dialog.drawer:dir(ltr) {
125
+ --drawer-closed-translate: -100%;
126
+ }
127
+
128
+ dialog.drawer[data-side="end"]:dir(ltr) {
129
+ --drawer-closed-translate: 100%;
130
+ }
131
+
132
+ dialog.drawer:dir(rtl) {
133
+ --drawer-closed-translate: 100%;
134
+ }
135
+
136
+ dialog.drawer[data-side="end"]:dir(rtl) {
137
+ --drawer-closed-translate: -100%;
138
+ }
139
+ }
140
+
121
141
  @media (forced-colors: active) {
122
142
  dialog.drawer::backdrop {
123
143
  background: Canvas;
@@ -137,3 +157,8 @@ dialog.drawer > header,
137
157
  dialog.drawer > form > header {
138
158
  padding-inline-end: calc(var(--control-size) + var(--space-30));
139
159
  }
160
+ @media print {
161
+ dialog.drawer[open] {
162
+ display: none !important;
163
+ }
164
+ }
@@ -170,3 +170,8 @@
170
170
  /* Vertical dividers — .menu-separator (menu.css) is the canonical way to
171
171
  separate menu items. Flyout panels that are not menus use auto grid gap
172
172
  declared on .flyout itself. */
173
+ @media print {
174
+ .flyout {
175
+ display: none !important;
176
+ }
177
+ }
@@ -1,42 +1,47 @@
1
1
  /* Controls */
2
- @import "button.css";
3
- @import "badge.css";
4
- @import "alert.css";
5
- @import "actions.css";
2
+ @import "./button.css";
3
+ @import "./badge.css";
4
+ @import "./alert.css";
5
+ @import "./actions.css";
6
6
 
7
7
  /* Surfaces */
8
- @import "card.css";
9
- @import "surface.css";
10
- @import "modal.css";
11
- @import "drawer.css";
8
+ @import "./card.css";
9
+ @import "./surface.css";
10
+ @import "./modal.css";
11
+ @import "./drawer.css";
12
12
 
13
13
  /* Overlays */
14
- @import "flyout.css";
15
- @import "menu.css";
16
- @import "tooltip.css";
14
+ @import "./flyout.css";
15
+ @import "./menu.css";
16
+ @import "./tooltip.css";
17
17
 
18
18
  /* Disclosure */
19
- @import "accordion.css";
19
+ @import "./accordion.css";
20
20
 
21
21
  /* Feedback */
22
- @import "spinner.css";
23
- @import "skeleton.css";
24
- @import "busy.css";
25
- @import "progress.css";
26
- @import "status-bar.css";
22
+ @import "./spinner.css";
23
+ @import "./skeleton.css";
24
+ @import "./busy.css";
25
+ @import "./progress.css";
26
+ @import "./status-bar.css";
27
27
 
28
28
  /* Display */
29
- @import "avatar.css";
30
- @import "key.css";
31
- @import "overline.css";
29
+ @import "./avatar.css";
30
+ @import "./key.css";
31
+ @import "./chat.css";
32
+ @import "./fab.css";
32
33
 
33
34
  /* Navigation */
34
- @import "navbar.css";
35
- @import "breadcrumb.css";
36
- @import "pagination.css";
37
- @import "tab.css";
38
- @import "scrollspy.css";
35
+ @import "./navbar.css";
36
+ @import "./breadcrumb.css";
37
+ @import "./pagination.css";
38
+ @import "./tab.css";
39
+ @import "./scrollspy.css";
39
40
 
40
41
  /* Data */
41
- @import "table.css";
42
- @import "meter.css";
42
+ @import "./table.css";
43
+ @import "./meter.css";
44
+
45
+ /* Composed controls — imported last so equal-specificity corner rules remain
46
+ later than the controls they group. */
47
+ @import "./join.css";
@@ -91,7 +91,7 @@
91
91
  /* Keep an active focus ring above an adjacent hovered control. This matters
92
92
  on platforms where clicking a button does not move focus from the input. */
93
93
  .join > :is(:focus, :focus-within) {
94
- z-index: 2;
94
+ z-index: 4;
95
95
  }
96
96
 
97
97
  /* An input's border must read as the "primary" edge against an attached
@@ -115,3 +115,10 @@
115
115
  outline: none;
116
116
  box-shadow: none;
117
117
  }
118
+
119
+ /* A default outline action belongs to the composite field border. An explicit
120
+ intent still provides its own outline color — --intent is guaranteed-invalid
121
+ for a neutral button (the :where(.btn) boundary), so the fallback applies. */
122
+ .join:has(> :is(.input, .textarea, .select)) > .btn.outline {
123
+ --btn-border-color: var(--intent, var(--border));
124
+ }
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * Visual twin of .prose kbd (prose.css) — keep the two recipes in sync.
8
8
  */
9
+ /* @sync key-recipe */
9
10
  .key {
10
11
  display: inline-flex;
11
12
  align-items: center;
@@ -25,3 +26,4 @@
25
26
  white-space: nowrap;
26
27
  vertical-align: 0.08em;
27
28
  }
29
+ /* @sync key-recipe */
@@ -6,6 +6,9 @@
6
6
  *
7
7
  * Public hooks:
8
8
  * --menu-item-icon-size
9
+ *
10
+ * Intent: .menu-item consumes --intent as its text color, so intent classes
11
+ * (.danger, .success, …) tint an item without a per-intent variant.
9
12
  */
10
13
 
11
14
  .menu {
@@ -20,6 +23,13 @@
20
23
  margin: 0;
21
24
  }
22
25
 
26
+ /* Intent boundary — inherited intents do not tint this component.
27
+ Local intent classes still win because :where() has zero specificity. */
28
+ :where(.menu-item) {
29
+ --intent: initial;
30
+ --intent-fg: initial;
31
+ }
32
+
23
33
  .menu-item {
24
34
  --menu-item-icon-size: 1.25em;
25
35
 
@@ -32,7 +42,7 @@
32
42
  border: 0;
33
43
  border-radius: var(--radius-sm);
34
44
  background: transparent;
35
- color: var(--text);
45
+ color: var(--intent, var(--text));
36
46
  font: inherit;
37
47
  text-align: start;
38
48
  text-decoration: none;
@@ -51,7 +51,10 @@
51
51
  @media (forced-colors: active) {
52
52
  .meter::-webkit-meter-optimum-value,
53
53
  .meter::-webkit-meter-suboptimum-value,
54
- .meter::-webkit-meter-even-less-good-value,
54
+ .meter::-webkit-meter-even-less-good-value {
55
+ background: Highlight;
56
+ }
57
+
55
58
  .meter::-moz-meter-bar {
56
59
  background: Highlight;
57
60
  }
@@ -315,3 +315,8 @@ html.has-modal-open.had-scrollbar {
315
315
  outline: 2px solid transparent;
316
316
  box-shadow: var(--focus-ring-shadow);
317
317
  }
318
+ @media print {
319
+ dialog.modal[open] {
320
+ display: none !important;
321
+ }
322
+ }
@@ -8,6 +8,14 @@
8
8
  * Internal:
9
9
  * --gap (local value for the shared gap token)
10
10
  */
11
+ /* Intent boundary — inherited intents do not tint this component.
12
+ Local intent classes still win because :where() has zero specificity. */
13
+ :where(.navbar) {
14
+ --ui-bg: initial;
15
+ --ui-fg: initial;
16
+ --ui-border: initial;
17
+ }
18
+
11
19
  .navbar {
12
20
  display: flex;
13
21
  flex-wrap: wrap;
@@ -16,17 +24,17 @@
16
24
  gap: var(--gap);
17
25
  padding-block: var(--space-30);
18
26
  padding-inline: var(--space-40);
19
- border: var(--border-width) solid var(--border);
27
+ border: var(--border-width) solid var(--ui-border, var(--border));
20
28
  border-radius: var(--radius-lg);
21
- background: var(--surface-raised);
22
- color: var(--text);
29
+ background: var(--ui-bg, var(--surface-raised));
30
+ color: var(--ui-fg, var(--text));
23
31
  }
24
32
 
25
33
  .navbar-brand {
26
34
  display: inline-flex;
27
35
  align-items: center;
28
36
  gap: var(--space-20);
29
- color: var(--text);
37
+ color: var(--ui-fg, var(--text));
30
38
  font-weight: var(--font-weight-bold);
31
39
  text-decoration: none;
32
40
  }
@@ -51,7 +59,7 @@
51
59
  min-block-size: var(--control-size-sm);
52
60
  padding-inline: var(--space-20);
53
61
  border-radius: var(--radius);
54
- color: var(--text-muted);
62
+ color: var(--ui-fg, var(--text-muted));
55
63
  font-weight: var(--font-weight-medium);
56
64
  text-decoration: none;
57
65
  }
@@ -16,9 +16,20 @@
16
16
  margin: 0;
17
17
  padding: 0;
18
18
  list-style: none;
19
+
20
+ /* Arrows, page numbers, and truncation ellipses share one geometric
21
+ system: equal digit widths keep the row from re-flowing as numbers grow
22
+ (98 → 99 → 100). */
23
+ font-variant-numeric: tabular-nums;
19
24
  }
20
25
 
21
26
  .pagination .btn {
27
+ /* Every item gets the same min footprint; wider content (100, 1000,
28
+ text labels) grows past it naturally. The default 1em control padding
29
+ would let an icon-only arrow exceed the numbered items' footprint, so
30
+ the horizontal padding tightens here and the min size does the work. */
31
+ --btn-pad-x: var(--space-20);
32
+
22
33
  min-inline-size: var(--control-size);
23
34
  }
24
35
 
@@ -57,3 +57,8 @@
57
57
  transform: rotate(360deg);
58
58
  }
59
59
  }
60
+ @media print {
61
+ .spinner {
62
+ display: none !important;
63
+ }
64
+ }
@@ -70,3 +70,8 @@
70
70
  translate: 0 var(--space-20);
71
71
  pointer-events: none;
72
72
  }
73
+ @media print {
74
+ .status-bar {
75
+ display: none !important;
76
+ }
77
+ }
@@ -40,3 +40,8 @@
40
40
  display var(--duration) allow-discrete;
41
41
  }
42
42
  }
43
+ @media print {
44
+ .surface-backdrop {
45
+ display: none !important;
46
+ }
47
+ }
@@ -64,3 +64,9 @@
64
64
  .table > :last-child > tr:last-child > * {
65
65
  border-block-end: 0;
66
66
  }
67
+
68
+ /* Same compact vocabulary as .card.compact: one hook change, no selector
69
+ duplication. Density is padding only — typography never changes. */
70
+ .table.compact {
71
+ --table-cell-pad: var(--space-20);
72
+ }
@@ -112,3 +112,8 @@
112
112
  display: none;
113
113
  }
114
114
  }
115
+ @media print {
116
+ .tooltip {
117
+ display: none !important;
118
+ }
119
+ }