actual-css 0.1.0 → 0.3.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 (215) hide show
  1. package/CHANGELOG.md +90 -1
  2. package/README.md +25 -8
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1492 -931
  5. package/dist/actual.js +1 -3
  6. package/dist/actual.min.css +1 -1
  7. package/dist/optional.css +1048 -0
  8. package/dist/optional.min.css +1 -0
  9. package/docs/design-notes/browser-support.md +95 -6
  10. package/docs/design-notes/cascade-layer.md +1 -1
  11. package/docs/design-notes/enhancement-contract.md +101 -0
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +3 -1
  14. package/docs/design-notes/platform-alignment.md +492 -0
  15. package/docs/design-notes/references.md +8 -0
  16. package/docs/design-notes/widget-primitives.md +119 -0
  17. package/docs/navigation.json +135 -0
  18. package/docs/pages/components/accordion.md +80 -0
  19. package/docs/pages/components/alert.md +203 -0
  20. package/docs/pages/components/aura.md +35 -0
  21. package/docs/pages/components/avatar.md +95 -0
  22. package/docs/pages/components/badge.md +102 -0
  23. package/docs/pages/components/breadcrumb.md +19 -0
  24. package/docs/pages/components/button.md +205 -0
  25. package/docs/pages/components/card.md +183 -0
  26. package/docs/pages/components/chat.md +50 -0
  27. package/docs/pages/components/dialog.md +367 -0
  28. package/docs/pages/components/drawer.md +113 -0
  29. package/docs/pages/components/fab.md +118 -0
  30. package/docs/pages/components/key.md +33 -0
  31. package/docs/pages/components/meter.md +23 -0
  32. package/docs/pages/components/navbar.md +92 -0
  33. package/docs/pages/components/pagination.md +55 -0
  34. package/docs/pages/components/progress.md +29 -0
  35. package/docs/pages/components/skeleton.md +38 -0
  36. package/docs/pages/components/spinner.md +93 -0
  37. package/docs/pages/components/table.md +102 -0
  38. package/docs/pages/enhancements/flyout.md +391 -0
  39. package/docs/pages/enhancements/overview.md +36 -0
  40. package/docs/pages/enhancements/scrollspy.md +71 -0
  41. package/docs/pages/enhancements/status-bar.md +72 -0
  42. package/docs/pages/enhancements/tabs.md +80 -0
  43. package/docs/pages/enhancements/tooltip.md +130 -0
  44. package/docs/pages/examples/overview.md +41 -0
  45. package/docs/pages/forms/choice-cards.md +102 -0
  46. package/docs/pages/forms/controls.md +32 -0
  47. package/docs/pages/forms/detached-actions.md +59 -0
  48. package/docs/pages/forms/floating-field.md +88 -0
  49. package/docs/pages/forms/form-actions.md +56 -0
  50. package/docs/pages/forms/input-policy.md +91 -0
  51. package/docs/pages/forms/joined-controls.md +37 -0
  52. package/docs/pages/forms/labels.md +42 -0
  53. package/docs/pages/forms/otp.md +50 -0
  54. package/docs/pages/forms/overview.md +188 -0
  55. package/docs/pages/forms/password.md +40 -0
  56. package/docs/pages/forms/range.md +17 -0
  57. package/docs/pages/forms/states-and-sizes.md +321 -0
  58. package/docs/pages/forms/sticky-actions.md +187 -0
  59. package/docs/pages/forms/switch.md +31 -0
  60. package/docs/pages/forms/validation.md +241 -0
  61. package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
  62. package/docs/pages/foundations/theming.md +92 -0
  63. package/docs/pages/foundations/tokens.md +421 -0
  64. package/docs/{typography.md → pages/foundations/typography.md} +27 -9
  65. package/docs/pages/guides/bootstrap.md +438 -0
  66. package/docs/pages/guides/introduction.md +157 -0
  67. package/docs/pages/guides/optional-components.md +33 -0
  68. package/docs/pages/guides/progressive-enhancement.md +686 -0
  69. package/docs/pages/guides/tailwind.md +455 -0
  70. package/docs/pages/layout/app-shell.md +25 -0
  71. package/docs/pages/layout/center.md +29 -0
  72. package/docs/pages/layout/cluster.md +57 -0
  73. package/docs/pages/layout/frame.md +30 -0
  74. package/docs/pages/layout/grid.md +142 -0
  75. package/docs/pages/layout/list.md +41 -0
  76. package/docs/pages/layout/media.md +21 -0
  77. package/docs/pages/layout/overview.md +37 -0
  78. package/docs/pages/layout/scroll-snap.md +54 -0
  79. package/docs/pages/layout/sidebar-layout.md +27 -0
  80. package/docs/pages/layout/stack.md +18 -0
  81. package/docs/pages/layout/switcher.md +36 -0
  82. package/docs/pages/layout/topbar.md +23 -0
  83. package/docs/pages/patterns/actions.md +52 -0
  84. package/docs/pages/patterns/code-block.md +36 -0
  85. package/docs/pages/patterns/exclusive-accordion.md +16 -0
  86. package/docs/pages/patterns/hierarchical-nav.md +26 -0
  87. package/docs/pages/patterns/icon-input.md +25 -0
  88. package/docs/pages/patterns/intent-nesting.md +3 -0
  89. package/docs/pages/patterns/navigation-list.md +235 -0
  90. package/docs/pages/patterns/overview.md +58 -0
  91. package/docs/pages/patterns/structured-lists.md +47 -0
  92. package/docs/pages/utilities/accessibility.md +34 -0
  93. package/docs/pages/utilities/flex.md +105 -0
  94. package/docs/pages/utilities/links.md +18 -0
  95. package/docs/pages/utilities/list.md +31 -0
  96. package/docs/pages/utilities/overflow.md +43 -0
  97. package/docs/pages/utilities/overview.md +18 -0
  98. package/docs/pages/utilities/shape.md +25 -0
  99. package/docs/pages/utilities/sizing-wrapping.md +115 -0
  100. package/docs/pages/utilities/spacing.md +79 -0
  101. package/docs/pages/utilities/text.md +43 -0
  102. package/llms.txt +10 -4
  103. package/package.json +30 -16
  104. package/src/css/actual.css +12 -1
  105. package/src/css/base.css +3 -9
  106. package/src/css/components/accordion.css +18 -30
  107. package/src/css/components/actions.css +4 -1
  108. package/src/css/components/alert.css +162 -49
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +82 -39
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +7 -0
  113. package/src/css/components/button.css +82 -38
  114. package/src/css/components/card.css +17 -37
  115. package/src/css/components/drawer.css +30 -13
  116. package/src/css/components/flyout.css +39 -110
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -3
  119. package/src/css/components/key.css +2 -0
  120. package/src/css/components/menu.css +125 -0
  121. package/src/css/components/meter.css +0 -23
  122. package/src/css/components/modal.css +162 -7
  123. package/src/css/components/navbar.css +17 -18
  124. package/src/css/components/overline.css +0 -7
  125. package/src/css/components/pagination.css +1 -1
  126. package/src/css/components/progress.css +8 -26
  127. package/src/css/components/scrollspy.css +2 -2
  128. package/src/css/components/skeleton.css +7 -0
  129. package/src/css/components/spinner.css +10 -11
  130. package/src/css/components/status-bar.css +18 -28
  131. package/src/css/components/surface.css +2 -8
  132. package/src/css/components/tab.css +20 -48
  133. package/src/css/components/table.css +7 -5
  134. package/src/css/components/tooltip.css +14 -7
  135. package/src/css/focus.css +16 -3
  136. package/src/css/forms/choice-card.css +13 -15
  137. package/src/css/forms/choice.css +21 -28
  138. package/src/css/forms/control.css +16 -26
  139. package/src/css/forms/custom-select.css +9 -11
  140. package/src/css/forms/form-actions.css +14 -3
  141. package/src/css/forms/form.css +7 -4
  142. package/src/css/forms/index.css +5 -4
  143. package/src/css/forms/input-icon.css +30 -12
  144. package/src/css/forms/native.css +48 -33
  145. package/src/css/forms/range.css +103 -0
  146. package/src/css/forms/select.css +8 -8
  147. package/src/css/forms/switch.css +14 -13
  148. package/src/css/forms/validation.css +32 -5
  149. package/src/css/grid.css +19 -18
  150. package/src/css/intents.css +2 -2
  151. package/src/css/layout.css +29 -1
  152. package/src/css/optional/aura.css +93 -0
  153. package/src/css/optional/chat.css +105 -0
  154. package/src/css/optional/fab.css +104 -0
  155. package/src/css/optional/floating-field.css +87 -0
  156. package/src/css/optional/index.css +5 -0
  157. package/src/css/optional/layout-extra.css +7 -4
  158. package/src/css/optional/otp.css +115 -0
  159. package/src/css/optional/scroll-snap.css +6 -0
  160. package/src/css/optional/scroller.css +19 -7
  161. package/src/css/optional/typography-fluid.css +5 -0
  162. package/src/css/optional/utilities-extra.css +17 -2
  163. package/src/css/prose.css +18 -8
  164. package/src/css/reset.css +0 -10
  165. package/src/css/theme.css +29 -13
  166. package/src/css/themes/cyberpunk.css +1 -1
  167. package/src/css/themes/dim.css +3 -3
  168. package/src/css/themes/edge.css +11 -37
  169. package/src/css/themes/index.css +3 -3
  170. package/src/css/themes/{dark.css → indigo.css} +5 -4
  171. package/src/css/themes/neon.css +5 -5
  172. package/src/css/themes/square.css +0 -3
  173. package/src/css/tokens.css +81 -21
  174. package/src/css/utilities.css +8 -10
  175. package/src/css/variants.css +37 -65
  176. package/src/js/command.js +1 -3
  177. package/src/js/context-menu.js +72 -53
  178. package/src/js/dialog.js +68 -79
  179. package/src/js/dismiss.js +15 -0
  180. package/src/js/enhance.js +76 -19
  181. package/src/js/enhancement-loader.js +301 -0
  182. package/src/js/events.js +3 -0
  183. package/src/js/filter.js +3 -3
  184. package/src/js/floating.js +101 -131
  185. package/src/js/flyout.js +106 -96
  186. package/src/js/focus-group.js +116 -0
  187. package/src/js/focus.js +6 -1
  188. package/src/js/index.js +4 -2
  189. package/src/js/input.js +1 -1
  190. package/src/js/keys.js +27 -0
  191. package/src/js/mask.js +7 -6
  192. package/src/js/menu.js +89 -35
  193. package/src/js/parse-config.js +47 -0
  194. package/src/js/password.js +9 -1
  195. package/src/js/scrollspy.js +133 -62
  196. package/src/js/selectors.js +14 -31
  197. package/src/js/status.js +16 -14
  198. package/src/js/surface.js +219 -125
  199. package/src/js/tab.js +46 -66
  200. package/src/js/tooltip.js +53 -31
  201. package/src/js/validation.js +45 -51
  202. package/dist/actual.js.map +0 -31
  203. package/docs/README.md +0 -26
  204. package/docs/components.md +0 -860
  205. package/docs/design-notes/previous-work.md +0 -5
  206. package/docs/forms.md +0 -1265
  207. package/docs/javascript.md +0 -426
  208. package/docs/layout.md +0 -480
  209. package/docs/patterns.md +0 -415
  210. package/docs/reserved.md +0 -7
  211. package/docs/tokens.md +0 -455
  212. package/docs/ui.md +0 -937
  213. package/docs/utilities.md +0 -384
  214. package/src/css/dialog-fallback.css +0 -56
  215. package/src/js/dialog-fallback.js +0 -163
@@ -7,42 +7,48 @@
7
7
  * --alert-border-inline-start-color/-width are unset extension points, mirroring
8
8
  * --btn-border-block-end-* (button.css), for themes that give alerts a
9
9
  * callout-style colored flag on the leading edge instead of a uniform border.
10
+ *
11
+ * Public hooks:
12
+ * --alert-pad-block
13
+ * --alert-pad-inline
14
+ * --alert-font-size
15
+ * --alert-radius
16
+ * --alert-icon-size
17
+ * --alert-dismiss-size
18
+ *
19
+ * Internal:
20
+ * --alert-bg
21
+ * --alert-fg
22
+ * --alert-border
23
+ * --alert-dismiss-icon-size
10
24
  */
11
25
  /* Intent boundary — inherited intents do not tint this component.
12
26
  Local intent classes still win because :where() has zero specificity. */
13
27
  :where(.alert) {
14
- /* @sync intent-boundary */
15
28
  --intent: initial;
16
29
  --intent-fg: initial;
17
30
  --ui-bg: initial;
18
31
  --ui-fg: initial;
19
32
  --ui-border: initial;
20
- --ui-hover-bg: initial;
21
- /* @sync intent-boundary */
22
33
  }
23
34
 
24
35
  .alert {
25
- --alert-pad-block: var(--variant-pad-block, var(--space-4));
26
- --alert-pad-inline: var(--space-4);
27
- --alert-font-size: var(--variant-font-size, var(--font-size-md));
28
- --alert-icon-size: var(--variant-icon-size, 1.25rem);
29
- --alert-bg: var(--surface-subtle);
30
- --alert-border: var(--border);
31
- --ui-bg: var(--alert-bg);
32
- --ui-border: var(--alert-border);
33
- --ui-fg: var(--intent, var(--text));
36
+ --alert-pad-block: var(--space-30);
37
+ --alert-pad-inline: var(--space-40);
38
+ --alert-font-size: var(--font-size-md);
39
+ --alert-icon-size: 1.25rem;
40
+ --alert-bg: var(--ui-bg, var(--surface-subtle));
41
+ --alert-fg: var(--ui-fg, var(--intent, var(--text)));
42
+ --alert-border: var(--ui-border, var(--border));
34
43
 
35
44
  padding-block: var(--alert-pad-block);
36
45
  padding-inline: var(--alert-pad-inline);
37
- background: var(--ui-bg, transparent);
38
- color: var(--ui-fg, var(--text));
39
- border: var(--border-width) solid var(--ui-border, var(--border));
40
- border-inline-start-color: var(
41
- --alert-border-inline-start-color,
42
- var(--ui-border, var(--border))
43
- );
46
+ background: var(--alert-bg);
47
+ color: var(--alert-fg);
48
+ border: var(--border-width) solid var(--alert-border);
49
+ border-inline-start-color: var(--alert-border-inline-start-color, var(--alert-border));
44
50
  border-inline-start-width: var(--alert-border-inline-start-width, var(--border-width));
45
- border-radius: var(--radius-lg);
51
+ border-radius: var(--alert-radius, var(--radius-lg));
46
52
  font-size: var(--alert-font-size);
47
53
  }
48
54
 
@@ -58,21 +64,10 @@
58
64
  margin-block-end: 0;
59
65
  }
60
66
 
61
- /* Alerts are soft by default. Use .solid or .outline for stronger emphasis. */
62
- .alert.solid {
63
- --ui-bg: var(--intent, var(--neutral));
64
- --ui-fg: var(--intent-fg, var(--neutral-fg));
65
- --ui-border: transparent;
66
- }
67
-
68
- .alert.outline {
69
- --ui-bg: transparent;
70
- --ui-fg: var(--intent, var(--text));
71
- --ui-border: var(--intent, var(--border));
72
- }
73
-
67
+ /* Alerts are soft by default. .solid and .outline need no local rules — the
68
+ shared variant plumbing in variants.css sets --ui-* that alert consumes. */
74
69
  @supports (color: color-mix(in oklch, red, white)) {
75
- .alert:where(:not(.solid, .outline)) {
70
+ .alert:where(:not(.solid, .outline, .callout)) {
76
71
  /* @sync soft-recipe */
77
72
  --ui-bg: color-mix(in oklch, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral)));
78
73
  --ui-border: color-mix(
@@ -84,8 +79,8 @@
84
79
  }
85
80
  }
86
81
 
87
- /* Decorative leading icon */
88
- .alert > :first-child[aria-hidden="true"] {
82
+ /* Decorative leading icon — use .alert-icon on the icon element. */
83
+ .alert > .alert-icon {
89
84
  display: inline-block;
90
85
  flex: none;
91
86
  inline-size: var(--alert-icon-size);
@@ -95,33 +90,84 @@
95
90
  vertical-align: -0.125em;
96
91
  }
97
92
 
93
+ /* Dismiss — compact close button for inline alerts. Transparent at rest with
94
+ a reduced opacity glyph so it stays secondary inside an already-colored
95
+ alert; hover reinforces it via the shared overlay. Uses the shared
96
+ --icon-close mask, so no icon font or text glyph is required in the markup.
97
+ Standard alerts place it as a direct trailing child; admonitions place it
98
+ inside .alert-title (see below). */
99
+ .alert-dismiss {
100
+ --alert-dismiss-size: 1.75rem;
101
+ --alert-dismiss-icon-size: 0.875rem;
102
+
103
+ display: inline-grid;
104
+ place-items: center;
105
+ inline-size: var(--alert-dismiss-size);
106
+ block-size: var(--alert-dismiss-size);
107
+ flex: none;
108
+ padding: 0;
109
+ border: 0;
110
+ border-radius: var(--radius);
111
+ background: transparent;
112
+ color: currentColor;
113
+ opacity: 0.65;
114
+ cursor: pointer;
115
+ }
116
+
117
+ .alert-dismiss::before {
118
+ content: "";
119
+ inline-size: var(--alert-dismiss-icon-size);
120
+ block-size: var(--alert-dismiss-icon-size);
121
+ background: currentColor;
122
+ -webkit-mask: var(--icon-close) center / contain no-repeat;
123
+ mask: var(--icon-close) center / contain no-repeat;
124
+ }
125
+
126
+ @media (hover: hover) {
127
+ .alert-dismiss:hover {
128
+ background: var(--hover-overlay);
129
+ opacity: 1;
130
+ }
131
+ }
132
+
133
+ /* Grid anatomy — activated by a leading icon, a trailing dismiss button, or
134
+ both. Icon and dismiss are pinned to the outer columns; anything else (the
135
+ message content) flows into the remaining text column. */
98
136
  @supports selector(.alert:has(> *)) {
99
- .alert:has(> :first-child[aria-hidden="true"]) {
137
+ .alert:has(> .alert-icon, > .alert-dismiss) {
100
138
  display: grid;
101
- grid-template-columns: auto minmax(0, 1fr);
139
+ grid-template-columns: minmax(0, 1fr);
102
140
  align-items: var(--items-align, start);
103
- column-gap: var(--space-3);
141
+ column-gap: var(--space-30);
104
142
  }
105
143
 
106
- .alert:has(> :first-child[aria-hidden="true"]) > :first-child {
144
+ .alert:has(> .alert-icon) {
145
+ grid-template-columns: auto minmax(0, 1fr);
146
+ }
147
+
148
+ .alert:has(> .alert-icon) > .alert-icon {
107
149
  grid-column: 1;
108
150
  grid-row: 1 / -1;
109
151
  display: inline-grid;
110
152
  place-items: center;
111
- inline-size: var(--alert-icon-size);
112
- block-size: var(--alert-icon-size);
113
153
  margin-block-start: 0.125em;
114
- font-size: var(--alert-icon-size);
115
- line-height: 1;
116
154
  }
117
155
 
118
- .alert:has(> :first-child[aria-hidden="true"]) > :first-child:is(svg, i) {
119
- inline-size: var(--alert-icon-size);
120
- block-size: var(--alert-icon-size);
156
+ .alert:has(> .alert-icon) > :not(.alert-icon, .alert-dismiss) {
157
+ grid-column: 2;
121
158
  }
122
159
 
123
- .alert:has(> :first-child[aria-hidden="true"]) > :not(:first-child) {
124
- grid-column: 2;
160
+ .alert:has(> .alert-dismiss) {
161
+ grid-template-columns: minmax(0, 1fr) auto;
162
+ }
163
+
164
+ .alert:has(> .alert-icon):has(> .alert-dismiss) {
165
+ grid-template-columns: auto minmax(0, 1fr) auto;
166
+ }
167
+
168
+ .alert:has(> .alert-dismiss) > .alert-dismiss {
169
+ grid-column: -2 / -1;
170
+ grid-row: 1;
125
171
  }
126
172
  }
127
173
 
@@ -135,3 +181,70 @@
135
181
  readable color, and text on a Highlight surface would trigger Chromium's
136
182
  Canvas backplate (unreadable) — so .solid intentionally falls back to the
137
183
  default look. */
184
+
185
+ /* Callout — neutral surface with a thick accent border on the leading edge.
186
+ The thickness follows the shared --alert-border-inline-start-width hook. */
187
+ .alert.callout {
188
+ --alert-bg: var(--surface-subtle);
189
+ --alert-fg: var(--text);
190
+ border: 0;
191
+ border-inline-start: var(--alert-border-inline-start-width, 4px) solid
192
+ var(--alert-border-inline-start-color, var(--intent, var(--neutral)));
193
+ }
194
+
195
+ /* Admonition — structured static box with a tinted title bar and a body
196
+ that rests on the page surface. Not collapsible — use .accordion inside
197
+ the body if the content needs to be expandable. */
198
+ .alert.admonition {
199
+ padding: 0;
200
+ display: block;
201
+ overflow: clip;
202
+ border-color: var(--intent, var(--border));
203
+ }
204
+
205
+ .alert.admonition > .alert-title {
206
+ display: flex;
207
+ align-items: center;
208
+ gap: var(--space-20);
209
+ padding-block: var(--alert-pad-block);
210
+ padding-inline: var(--alert-pad-inline);
211
+ border-start-start-radius: calc(var(--alert-radius, var(--radius-lg)) - var(--border-width));
212
+ border-start-end-radius: calc(var(--alert-radius, var(--radius-lg)) - var(--border-width));
213
+ }
214
+
215
+ /* Dismiss inside an admonition title bar rides the inline end. */
216
+ .alert.admonition > .alert-title > .alert-dismiss {
217
+ margin-inline-start: auto;
218
+ }
219
+
220
+ @supports (color: color-mix(in oklch, red, white)) {
221
+ .alert.admonition > .alert-title {
222
+ background: color-mix(
223
+ in oklch,
224
+ var(--surface) var(--soft-bg-mix),
225
+ var(--intent, var(--neutral))
226
+ );
227
+ }
228
+ }
229
+
230
+ @supports not (color: color-mix(in oklch, red, white)) {
231
+ .alert.admonition > .alert-title {
232
+ background: var(--intent, var(--neutral));
233
+ color: var(--intent-fg, var(--neutral-fg));
234
+ }
235
+ }
236
+
237
+ .alert.admonition > .alert-body {
238
+ padding-block: var(--alert-pad-block);
239
+ padding-inline: var(--alert-pad-inline);
240
+ background: var(--surface);
241
+ color: var(--text);
242
+ }
243
+
244
+ .alert.admonition > .alert-body > :first-child {
245
+ margin-block-start: 0;
246
+ }
247
+
248
+ .alert.admonition > .alert-body > :last-child {
249
+ margin-block-end: 0;
250
+ }
@@ -4,18 +4,26 @@
4
4
  * The visual box stays the same across semantic elements. Grouping and status
5
5
  * dots (an empty .badge child) compose around the avatar without changing its
6
6
  * meaning.
7
+ *
8
+ * Public hooks:
9
+ * --avatar-size
10
+ * --avatar-radius
11
+ * --avatar-font-size
12
+ * --avatar-stack-size
13
+ * --avatar-stack-font-size
14
+ * --avatar-stack-overlap
15
+ * --avatar-stack-ring
16
+ *
17
+ * Internal:
18
+ * --avatar-bg
19
+ * --avatar-fg
20
+ * --avatar-status-translate-x
7
21
  */
8
22
  /* Intent boundary — inherited intents do not tint this component.
9
23
  Local intent classes still win because :where() has zero specificity. */
10
24
  :where(.avatar) {
11
- /* @sync intent-boundary */
12
25
  --intent: initial;
13
26
  --intent-fg: initial;
14
- --ui-bg: initial;
15
- --ui-fg: initial;
16
- --ui-border: initial;
17
- --ui-hover-bg: initial;
18
- /* @sync intent-boundary */
19
27
  }
20
28
 
21
29
  .avatar {
@@ -107,10 +115,8 @@ button.avatar {
107
115
  transition: opacity var(--duration);
108
116
  }
109
117
 
110
- @media (hover: hover) {
111
- :where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):hover::after {
112
- opacity: 1;
113
- }
118
+ :where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):hover::after {
119
+ opacity: 1;
114
120
  }
115
121
 
116
122
  :where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):active {
@@ -1,36 +1,49 @@
1
1
  /*
2
2
  * Badges for compact labels, counts, and state hints.
3
3
  *
4
- * Solid by default. Shared .soft/.outline variants stay available for quieter
5
- * tags without adding a badge-specific color API.
4
+ * Soft by default. .solid for counters and notification badges. The dot
5
+ * (.badge:empty) stays solid regardless — a soft dot is pointless.
6
+ * Shared .outline variant stays available.
7
+ *
8
+ * Public hooks:
9
+ * --badge-size
10
+ * --badge-pad-x
11
+ * --badge-font-size
12
+ * --badge-dot-size
13
+ * --badge-radius
14
+ *
15
+ * Internal:
16
+ * --badge-bg
17
+ * --badge-fg
18
+ * --badge-border
6
19
  */
7
20
  /* Intent boundary — inherited intents do not tint this component.
8
21
  Local intent classes still win because :where() has zero specificity. */
9
22
  :where(.badge) {
10
- /* @sync intent-boundary */
11
23
  --intent: initial;
12
24
  --intent-fg: initial;
13
25
  --ui-bg: initial;
14
26
  --ui-fg: initial;
15
27
  --ui-border: initial;
16
28
  --ui-hover-bg: initial;
17
- /* @sync intent-boundary */
18
29
  }
19
30
 
20
31
  .badge {
21
- --badge-size: var(--variant-compact-size, 1.5rem);
32
+ --badge-size: var(--density-compact-size);
22
33
  --badge-pad-x: 0.55em;
23
- --badge-font-size: var(--variant-compact-font-size, 0.8125rem);
34
+ --badge-font-size: 0.8125rem;
24
35
  --badge-dot-size: 0.5rem;
25
- --badge-bg: var(--ui-bg, var(--intent, var(--neutral)));
26
- --badge-fg: var(--ui-fg, var(--intent-fg, var(--neutral-fg)));
27
- --badge-border: var(--ui-border, transparent);
28
- --badge-radius: 999px;
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));
39
+ --badge-radius: var(--radius-full);
29
40
 
30
41
  display: inline-flex;
31
42
  align-items: center;
32
43
  justify-content: center;
33
44
  gap: 0.35em;
45
+ inline-size: fit-content;
46
+ max-inline-size: 100%;
34
47
  min-block-size: var(--badge-size);
35
48
  padding-inline: var(--badge-pad-x);
36
49
  border: var(--border-width) solid var(--badge-border);
@@ -49,8 +62,31 @@
49
62
  box-shadow var(--duration);
50
63
  }
51
64
 
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. */
67
+ @supports (color: color-mix(in oklch, red, white)) {
68
+ .badge:where(:not(.solid, .outline)) {
69
+ /* @sync soft-recipe */
70
+ --badge-bg: color-mix(
71
+ in oklch,
72
+ var(--surface) var(--soft-bg-mix),
73
+ var(--intent, var(--neutral))
74
+ );
75
+ --badge-fg: var(--intent, var(--text));
76
+ --badge-border: color-mix(
77
+ in oklch,
78
+ var(--surface) var(--soft-border-mix),
79
+ var(--intent, var(--neutral))
80
+ );
81
+ /* @sync soft-recipe */
82
+ }
83
+ }
84
+
52
85
  .badge:empty {
53
86
  --badge-size: var(--badge-dot-size);
87
+ --badge-bg: var(--intent, var(--neutral));
88
+ --badge-fg: var(--intent-fg, var(--neutral-fg));
89
+ --badge-border: transparent;
54
90
  padding: 0;
55
91
  border: 0;
56
92
  inline-size: var(--badge-size);
@@ -76,28 +112,44 @@
76
112
  cursor: pointer;
77
113
  }
78
114
 
79
- @media (hover: hover) {
80
- .badge > button:not(:disabled):hover {
81
- background: var(--hover-overlay);
82
- }
115
+ .badge > button:not(:disabled):hover {
116
+ background: var(--hover-overlay);
117
+ }
118
+
119
+ /* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
120
+ Declared outside :hover like button.css for the same reason. */
121
+ :is(a.badge, button.badge).outline {
122
+ --ui-hover-bg: var(--surface-subtle);
83
123
  }
84
124
 
85
- /* .solid/.soft/.outline are shared via variants.css, including the
86
- .soft/.outline hover recipe (--ui-hover-bg). The --badge-* fallbacks make
87
- .badge solid by default. */
88
- @media (hover: hover) {
89
- :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"])):hover {
90
- background: var(--ui-hover-bg, var(--badge-bg));
125
+ @supports (color: color-mix(in oklch, red, white)) {
126
+ :is(a.badge, button.badge):is(.solid, :empty) {
127
+ --ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
128
+ }
129
+
130
+ :is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
131
+ --ui-hover-bg: color-mix(
132
+ in oklch,
133
+ var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
134
+ var(--intent, var(--neutral))
135
+ );
91
136
  }
92
137
 
93
- :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"], .outline)):hover {
94
- box-shadow: inset 0 0 0 999px var(--hover-overlay);
138
+ :is(a.badge, button.badge).outline {
139
+ --ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
95
140
  }
96
141
  }
97
142
 
143
+ :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"])):hover {
144
+ background: var(--ui-hover-bg, var(--badge-bg));
145
+ }
146
+
147
+ :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"], .outline)):hover {
148
+ box-shadow: inset 0 0 0 999px var(--hover-overlay);
149
+ }
150
+
98
151
  button.badge {
99
152
  cursor: pointer;
100
- font: inherit;
101
153
  }
102
154
 
103
155
  button.badge:disabled,
@@ -111,27 +163,18 @@ a.badge[aria-disabled="true"] {
111
163
  }
112
164
 
113
165
  @media (forced-colors: active) {
114
- .badge {
115
- border-color: ButtonText;
116
- background: Canvas;
117
- color: CanvasText;
118
- box-shadow: none;
119
- }
120
-
121
166
  .badge:empty {
122
167
  background: ButtonText;
123
168
  }
124
169
 
125
- @media (hover: hover) {
126
- :where(a.badge, button.badge):hover:not(:where(:disabled, [aria-disabled="true"])) {
127
- outline: var(--border-width) solid ButtonText;
128
- outline-offset: 2px;
129
- }
170
+ :where(a.badge, button.badge):hover:not(:where(:disabled, [aria-disabled="true"])) {
171
+ outline: var(--border-width) solid ButtonText;
172
+ outline-offset: 2px;
173
+ }
130
174
 
131
- .badge > button:not(:disabled):hover {
132
- outline: var(--border-width) solid currentColor;
133
- outline-offset: 1px;
134
- }
175
+ .badge > button:not(:disabled):hover {
176
+ outline: var(--border-width) solid currentColor;
177
+ outline-offset: 1px;
135
178
  }
136
179
 
137
180
  button.badge:disabled,
@@ -8,7 +8,7 @@
8
8
  display: flex;
9
9
  flex-wrap: wrap;
10
10
  align-items: center;
11
- gap: var(--space-1);
11
+ gap: var(--space-10);
12
12
  margin: 0;
13
13
  padding: 0;
14
14
  color: var(--text-subtle);
@@ -19,7 +19,7 @@
19
19
  .breadcrumb li {
20
20
  display: inline-flex;
21
21
  align-items: center;
22
- gap: var(--space-1);
22
+ gap: var(--space-10);
23
23
  }
24
24
 
25
25
  .breadcrumb li + li::before {
@@ -32,25 +32,17 @@
32
32
  text-decoration: none;
33
33
  }
34
34
 
35
- @media (hover: hover) {
36
- .breadcrumb
37
- li:not(:where([aria-current]:not([aria-current="false"])))
38
- > a:hover:not(:where([aria-current]:not([aria-current="false"]))) {
39
- color: var(--text);
40
- text-decoration: underline;
41
- }
35
+ .breadcrumb a:not([aria-current]):hover,
36
+ .breadcrumb a[aria-current="false"]:hover {
37
+ color: var(--text);
38
+ text-decoration: underline;
42
39
  }
43
40
 
44
41
  .breadcrumb a:focus-visible {
45
42
  border-radius: var(--radius-sm);
46
43
  }
47
44
 
48
- .breadcrumb a:where([aria-current]:not([aria-current="false"])) {
49
- color: var(--text);
50
- font-weight: var(--font-weight-medium);
51
- }
52
-
53
- .breadcrumb li:where([aria-current]:not([aria-current="false"])) > :where(a, span) {
45
+ .breadcrumb :where(a, span):where([aria-current]:not([aria-current="false"])) {
54
46
  color: var(--text);
55
47
  font-weight: var(--font-weight-medium);
56
48
  }
@@ -4,6 +4,12 @@
4
4
  * A non-button element with aria-busy="true" and a direct last-child .spinner
5
5
  * shows that spinner as a centered overlay over a faded surface. Buttons keep
6
6
  * their spinners inline (e.g. .btn[aria-busy] > .spinner stays inline-flex).
7
+ *
8
+ * Public hooks:
9
+ * --busy-overlay-bg
10
+ *
11
+ * Internal:
12
+ * --spinner-size (overlay copy of the shared spinner hook)
7
13
  */
8
14
  [aria-busy] > .spinner:last-child {
9
15
  display: none;
@@ -28,6 +34,7 @@
28
34
  }
29
35
 
30
36
  [aria-busy="true"]:not(.btn) > .spinner:last-child {
37
+ --spinner-size: 2rem;
31
38
  position: absolute;
32
39
  inset: 0;
33
40
  z-index: 2;