actual-css 0.2.0 → 0.3.1

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 (214) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/README.md +24 -9
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1532 -1114
  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 +94 -14
  10. package/docs/design-notes/cascade-layer.md +7 -6
  11. package/docs/design-notes/enhancement-contract.md +11 -14
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +2 -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 +37 -1
  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 +210 -0
  25. package/docs/pages/components/card.md +195 -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 +43 -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 +99 -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 +59 -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 +28 -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 +55 -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 +32 -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 +4 -4
  103. package/package.json +21 -12
  104. package/src/css/actual.css +16 -2
  105. package/src/css/base.css +3 -5
  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 +139 -102
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +69 -42
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +6 -0
  113. package/src/css/components/button.css +84 -34
  114. package/src/css/components/card.css +26 -38
  115. package/src/css/components/drawer.css +30 -13
  116. package/src/css/components/flyout.css +39 -109
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -2
  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 +30 -23
  124. package/src/css/components/overline.css +8 -9
  125. package/src/css/components/pagination.css +1 -1
  126. package/src/css/components/progress.css +8 -26
  127. package/src/css/components/skeleton.css +7 -0
  128. package/src/css/components/spinner.css +6 -7
  129. package/src/css/components/status-bar.css +15 -24
  130. package/src/css/components/surface.css +2 -8
  131. package/src/css/components/tab.css +19 -48
  132. package/src/css/components/table.css +7 -5
  133. package/src/css/components/tooltip.css +14 -7
  134. package/src/css/focus.css +14 -38
  135. package/src/css/forms/choice-card.css +13 -14
  136. package/src/css/forms/choice.css +21 -27
  137. package/src/css/forms/control.css +18 -17
  138. package/src/css/forms/custom-select.css +9 -11
  139. package/src/css/forms/form-actions.css +14 -3
  140. package/src/css/forms/form.css +7 -4
  141. package/src/css/forms/index.css +4 -4
  142. package/src/css/forms/input-icon.css +30 -12
  143. package/src/css/forms/native.css +50 -11
  144. package/src/css/forms/range.css +17 -9
  145. package/src/css/forms/select.css +8 -8
  146. package/src/css/forms/switch.css +14 -12
  147. package/src/css/forms/validation.css +32 -5
  148. package/src/css/grid.css +32 -18
  149. package/src/css/intents.css +2 -2
  150. package/src/css/layout.css +45 -2
  151. package/src/css/optional/aura.css +93 -0
  152. package/src/css/optional/chat.css +105 -0
  153. package/src/css/optional/fab.css +104 -0
  154. package/src/css/optional/floating-field.css +87 -0
  155. package/src/css/optional/index.css +5 -0
  156. package/src/css/optional/layout-extra.css +7 -4
  157. package/src/css/optional/otp.css +115 -0
  158. package/src/css/optional/scroll-snap.css +6 -0
  159. package/src/css/optional/scroller.css +13 -7
  160. package/src/css/optional/typography-fluid.css +5 -0
  161. package/src/css/optional/utilities-extra.css +17 -2
  162. package/src/css/prose.css +18 -8
  163. package/src/css/reset.css +0 -10
  164. package/src/css/theme.css +21 -5
  165. package/src/css/themes/cyberpunk.css +1 -1
  166. package/src/css/themes/dim.css +3 -3
  167. package/src/css/themes/edge.css +8 -10
  168. package/src/css/themes/index.css +8 -4
  169. package/src/css/themes/{dark.css → indigo.css} +5 -4
  170. package/src/css/themes/neon.css +5 -5
  171. package/src/css/themes/square.css +0 -3
  172. package/src/css/tokens.css +74 -20
  173. package/src/css/utilities.css +8 -10
  174. package/src/css/variants.css +51 -66
  175. package/src/js/command.js +1 -3
  176. package/src/js/context-menu.js +67 -35
  177. package/src/js/dialog.js +68 -79
  178. package/src/js/dismiss.js +15 -0
  179. package/src/js/enhance.js +54 -23
  180. package/src/js/enhancement-loader.js +301 -0
  181. package/src/js/events.js +3 -0
  182. package/src/js/filter.js +3 -3
  183. package/src/js/floating.js +101 -131
  184. package/src/js/flyout.js +104 -87
  185. package/src/js/focus-group.js +116 -0
  186. package/src/js/focus.js +6 -1
  187. package/src/js/index.js +4 -2
  188. package/src/js/input.js +1 -1
  189. package/src/js/keys.js +27 -0
  190. package/src/js/mask.js +7 -6
  191. package/src/js/menu.js +87 -34
  192. package/src/js/parse-config.js +47 -0
  193. package/src/js/password.js +9 -1
  194. package/src/js/scrollspy.js +12 -4
  195. package/src/js/selectors.js +0 -7
  196. package/src/js/status.js +3 -1
  197. package/src/js/surface.js +187 -136
  198. package/src/js/tab.js +36 -53
  199. package/src/js/tooltip.js +70 -35
  200. package/src/js/validation.js +34 -8
  201. package/dist/actual.js.map +0 -31
  202. package/docs/README.md +0 -28
  203. package/docs/components.md +0 -860
  204. package/docs/design-notes/previous-work.md +0 -5
  205. package/docs/forms.md +0 -1265
  206. package/docs/javascript.md +0 -496
  207. package/docs/layout.md +0 -480
  208. package/docs/patterns.md +0 -504
  209. package/docs/reserved.md +0 -7
  210. package/docs/tokens.md +0 -455
  211. package/docs/ui.md +0 -929
  212. package/docs/utilities.md +0 -384
  213. package/src/css/dialog-fallback.css +0 -56
  214. package/src/js/dialog-fallback.js +0 -163
@@ -9,19 +9,40 @@
9
9
  * Disabled state uses the native disabled attribute for buttons and inputs.
10
10
  * Anchors have no native disabled state, so a.btn uses aria-disabled="true"
11
11
  * for disabled styling and activation guards.
12
+ *
13
+ * Public hooks:
14
+ * --btn-font-size
15
+ * --btn-min-size
16
+ * --btn-pad-x
17
+ * --btn-radius
18
+ * --btn-focus-color
19
+ * --btn-gap
20
+ * --btn-hover-overlay
21
+ * --btn-default-bg
22
+ * --btn-default-fg
23
+ * --btn-default-border
24
+ * --btn-border-color
25
+ *
26
+ * Internal:
27
+ * --btn-bg
28
+ * --btn-fg
29
+ * --btn-border
30
+ * --btn-default-hover-bg
31
+ * --btn-focus-ring-color
32
+ * --btn-border-block-end-color
33
+ * --btn-border-block-end-width
34
+ * --spinner-width
12
35
  */
13
36
 
14
37
  /* Intent boundary — inherited intents do not tint this component.
15
38
  Local intent classes still win because :where() has zero specificity. */
16
39
  :where(.btn) {
17
- /* @sync intent-boundary */
18
40
  --intent: initial;
19
41
  --intent-fg: initial;
20
42
  --ui-bg: initial;
21
43
  --ui-fg: initial;
22
44
  --ui-border: initial;
23
45
  --ui-hover-bg: initial;
24
- /* @sync intent-boundary */
25
46
  }
26
47
 
27
48
  .btn {
@@ -31,19 +52,20 @@
31
52
  --btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
32
53
  --btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
33
54
  --btn-border: var(--ui-border, var(--btn-default-border, transparent));
55
+ --btn-default-hover-bg: var(--btn-bg);
34
56
  --btn-radius: var(--radius);
35
57
  --btn-focus-color: var(--intent, var(--focus));
36
- --btn-hover-bg: var(--ui-hover-bg, var(--btn-bg));
37
58
  --btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
38
59
 
60
+ /* Positioning context for indicators, overlays, and grouped states. */
39
61
  position: relative;
40
62
  display: inline-flex;
41
63
  align-items: center;
42
64
  justify-content: center;
43
- gap: 0.375em;
65
+ gap: var(--btn-gap, 0.375em);
44
66
  min-block-size: var(--btn-min-size);
45
- padding-block: var(--space-2);
46
- padding-block: max(var(--space-1), calc((var(--btn-min-size) - 1lh) / 2));
67
+ padding-block: var(--space-20);
68
+ padding-block: max(var(--space-10), calc((var(--btn-min-size) - 1lh) / 2));
47
69
  padding-inline: var(--btn-pad-x);
48
70
  border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
49
71
  border-block-end-color: var(
@@ -68,6 +90,11 @@
68
90
  transform var(--duration);
69
91
  }
70
92
 
93
+ .btn.icon-only {
94
+ inline-size: var(--btn-min-size);
95
+ padding-inline: 0;
96
+ }
97
+
71
98
  @supports (color: color-mix(in oklch, red, white)) {
72
99
  .btn {
73
100
  --btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
@@ -78,7 +105,7 @@
78
105
  fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
79
106
  .btn.ghost {
80
107
  --ui-bg: transparent;
81
- --ui-fg: var(--intent, var(--text));
108
+ --ui-fg: var(--intent, currentColor);
82
109
  --ui-border: transparent;
83
110
  }
84
111
 
@@ -92,22 +119,46 @@
92
119
  --btn-hover-overlay: transparent;
93
120
  }
94
121
 
95
- /* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is shared with
96
- .badge in variants.css; the overlay darkening for solid/soft is .btn-specific. */
97
- @media (hover: hover) {
98
- .btn:not(:where(:disabled, [aria-disabled="true"])):hover {
99
- background: var(--btn-hover-bg);
100
- box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
122
+ /* Shared hover recipe — sets --ui-hover-bg consumed by the hover rule below.
123
+ Declared outside :hover: the variables resolve only when the hover rule
124
+ reads them, so the values are inert until interaction. */
125
+ .btn:is(.outline, .ghost) {
126
+ --ui-hover-bg: transparent;
127
+ }
128
+
129
+ @supports (color: color-mix(in oklch, red, white)) {
130
+ .btn {
131
+ --btn-default-hover-bg: color-mix(in oklch, var(--btn-bg) 88%, black);
132
+ }
133
+
134
+ .btn.soft {
135
+ --ui-hover-bg: color-mix(
136
+ in oklch,
137
+ var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
138
+ var(--intent, var(--neutral))
139
+ );
140
+ }
141
+
142
+ .btn.outline,
143
+ .btn.ghost {
144
+ --ui-hover-bg: color-mix(in oklch, currentColor 10%, transparent);
101
145
  }
102
146
  }
103
147
 
148
+ /* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
149
+ the overlay darkening for solid/soft is .btn-specific. */
150
+ .btn:not(:where(:disabled, [aria-disabled="true"])):hover {
151
+ background: var(--ui-hover-bg, var(--btn-default-hover-bg));
152
+ box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
153
+ }
154
+
104
155
  .btn:not(:where(:disabled, [aria-disabled="true"])):active {
105
156
  transform: translateY(1px);
106
157
  }
107
158
 
108
159
  .btn.link {
109
160
  --ui-bg: transparent;
110
- --ui-fg: var(--intent, var(--text));
161
+ --ui-fg: var(--intent, currentColor);
111
162
  --ui-border: transparent;
112
163
  --btn-radius: var(--radius-sm);
113
164
  --btn-border-block-end-color: transparent;
@@ -115,6 +166,8 @@
115
166
 
116
167
  min-block-size: auto;
117
168
  padding-inline: 0;
169
+ inline-size: fit-content;
170
+ max-inline-size: 100%;
118
171
  border-color: transparent;
119
172
  text-decoration: underline;
120
173
  text-underline-offset: 0.18em;
@@ -123,16 +176,13 @@
123
176
  /* .btn.circle needs no local rule: the .circle utility (utilities.css) loads
124
177
  after components and overrides border-radius at equal specificity. */
125
178
 
126
- @media (hover: hover) {
127
- .btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
128
- background: transparent;
129
- color: var(--intent, var(--text));
130
- text-decoration-thickness: 0.12em;
131
- }
179
+ .btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
180
+ background: transparent;
181
+ text-decoration-thickness: 0.12em;
132
182
  }
133
183
 
134
184
  .btn:focus-visible {
135
- outline: none;
185
+ outline: 2px solid transparent;
136
186
  box-shadow: 0 0 0 var(--focus-ring-width) var(--btn-focus-ring-color, var(--focus-ring));
137
187
  }
138
188
 
@@ -166,13 +216,15 @@ a.btn[aria-disabled="true"] {
166
216
  --spinner-width: 0.15em;
167
217
  }
168
218
 
169
- /* Toggle */
170
- /* State must outrank the shared .outline/.ghost hover recipes: a pressed
171
- button keeps its intent background and foreground while hovered. */
172
- .btn:is([aria-pressed="true"], [aria-current]:not([aria-current="false"])):not(.link) {
173
- --btn-bg: var(--intent, var(--primary));
174
- --btn-fg: var(--intent-fg, var(--primary-fg));
175
- --btn-border: var(--intent, var(--primary));
219
+ /* Toggle and current state.
220
+ State must outrank the shared .outline/.ghost hover recipes: a pressed or
221
+ current button keeps its intent background and foreground while hovered.
222
+ aria-current="page" is the pagination current-page marker; aria-pressed
223
+ covers toggle buttons. */
224
+ .btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
225
+ --btn-bg: var(--intent, var(--state-selected));
226
+ --btn-fg: var(--intent-fg, var(--state-selected-fg));
227
+ --btn-border: var(--intent, var(--state-selected));
176
228
  --ui-hover-bg: var(--btn-bg);
177
229
  }
178
230
 
@@ -190,18 +242,16 @@ a.btn[aria-disabled="true"] {
190
242
  color: LinkText;
191
243
  }
192
244
 
193
- .btn:is([aria-pressed="true"], [aria-current]:not([aria-current="false"])):not(.link) {
245
+ .btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
194
246
  border-color: Highlight;
195
247
  background: Highlight;
196
248
  color: HighlightText;
197
249
  forced-color-adjust: none;
198
250
  }
199
251
 
200
- @media (hover: hover) {
201
- .btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
202
- outline: var(--border-width) solid ButtonText;
203
- outline-offset: 2px;
204
- }
252
+ .btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
253
+ outline: var(--border-width) solid ButtonText;
254
+ outline-offset: 2px;
205
255
  }
206
256
 
207
257
  .btn:disabled,
@@ -4,29 +4,45 @@
4
4
  * Surface, border, radius, padding, density, and elevation stay local here.
5
5
  * Direct header/footer children are structural slots. Page layout remains the
6
6
  * job of markup and layout primitives outside the card.
7
+ *
8
+ * Public hooks:
9
+ * --card-pad
10
+ * --card-radius
11
+ * --card-max-inline-size
12
+ *
13
+ * Child contract:
14
+ * Direct .bleed children escape --card-pad (negative inset, wider media);
15
+ * edge children inherit the card corners. .bleed must be a direct child.
16
+ *
17
+ * Internal:
18
+ * --card-bg
19
+ * --card-fg
20
+ * --heading
21
+ * --busy-overlay-bg
7
22
  */
8
23
  /* Intent boundary — inherited intents do not tint this component.
9
24
  Local intent classes still win because :where() has zero specificity. */
10
25
  :where(.card) {
11
- /* @sync intent-boundary */
12
- --intent: initial;
13
- --intent-fg: initial;
14
26
  --ui-bg: initial;
15
27
  --ui-fg: initial;
16
28
  --ui-border: initial;
17
- --ui-hover-bg: initial;
18
- /* @sync intent-boundary */
19
29
  }
20
30
 
21
31
  .card {
22
- --card-pad: var(--space-4);
32
+ --card-pad: var(--space-40);
23
33
  --card-radius: var(--radius-lg);
24
34
  --card-max-inline-size: none;
35
+ --card-bg: var(--ui-bg, var(--surface-raised));
36
+ --card-fg: var(--ui-fg, var(--text));
37
+
38
+ /* Keep contextual descendants tied to the surface this card actually owns. */
39
+ --heading: var(--card-fg);
40
+ --busy-overlay-bg: var(--card-bg);
25
41
 
26
42
  max-inline-size: var(--card-max-inline-size);
27
43
  padding: var(--card-pad);
28
- background: var(--ui-bg, var(--surface));
29
- color: var(--ui-fg, var(--text));
44
+ background: var(--card-bg);
45
+ color: var(--card-fg);
30
46
  border: var(--border-width) solid var(--ui-border, var(--border));
31
47
  border-radius: var(--card-radius);
32
48
  }
@@ -40,7 +56,7 @@
40
56
  flex-wrap: wrap;
41
57
  align-items: var(--items-align, center);
42
58
  justify-content: space-between;
43
- gap: var(--space-3);
59
+ gap: var(--space-30);
44
60
  min-inline-size: 0;
45
61
  }
46
62
 
@@ -67,16 +83,8 @@
67
83
  .card.subtle {
68
84
  background: var(--surface-subtle);
69
85
  }
70
- .card.inverted {
71
- background: var(--surface-solid);
72
- color: var(--surface);
73
- border-color: transparent;
74
- --heading: var(--surface);
75
- --busy-overlay-bg: var(--surface-solid);
76
- }
77
-
78
86
  .card.compact {
79
- --card-pad: var(--space-2);
87
+ --card-pad: var(--space-20);
80
88
  }
81
89
 
82
90
  /* Full-bleed children */
@@ -118,23 +126,3 @@
118
126
  .card > :where(header, footer, figure).bleed {
119
127
  padding: var(--card-pad);
120
128
  }
121
-
122
- @media (forced-colors: active) {
123
- .card {
124
- border-color: ButtonText;
125
- background: Canvas;
126
- color: CanvasText;
127
- }
128
-
129
- .card.raised {
130
- box-shadow: none;
131
- }
132
-
133
- .card.inverted {
134
- --heading: CanvasText;
135
-
136
- border-color: ButtonText;
137
- background: Canvas;
138
- color: CanvasText;
139
- }
140
- }
@@ -6,16 +6,30 @@
6
6
  * discrete display/overlay transitions. The drawer is a layout shell only —
7
7
  * navigation content inside uses .nav-link and .accordion, which carry their
8
8
  * own link/summary styling.
9
+ *
10
+ * Public hooks:
11
+ * --drawer-size
12
+ * --drawer-pad
13
+ *
14
+ * Internal:
15
+ * --drawer-closed-translate (side/RTL positioning state)
9
16
  */
10
17
  dialog.drawer {
11
18
  --drawer-size: 20rem;
12
- --drawer-pad: var(--space-4);
19
+ --drawer-pad: var(--space-40);
13
20
  --drawer-closed-translate: -100%;
14
21
 
22
+ /* Keep the surface anchored to the viewport in the top layer. This also
23
+ provides the containing block for the absolutely positioned close button.
24
+ A percentage height resolves against the fixed containing viewport, while
25
+ dynamic viewport units can be stale in embedded previews. */
26
+ position: fixed;
27
+ inset-block-start: 0;
28
+
15
29
  inline-size: min(calc(var(--viewport-inline) * 0.9), var(--drawer-size));
16
- block-size: var(--viewport-block);
30
+ block-size: 100%;
17
31
  max-inline-size: none;
18
- max-block-size: var(--viewport-block);
32
+ max-block-size: 100%;
19
33
  margin: 0;
20
34
  padding: var(--drawer-pad);
21
35
  border: 0;
@@ -34,7 +48,7 @@ dialog.drawer {
34
48
  dialog.drawer[open] {
35
49
  display: grid;
36
50
  grid-template-rows: auto minmax(0, 1fr) auto;
37
- gap: var(--space-4);
51
+ gap: var(--space-40);
38
52
  opacity: 1;
39
53
  transform: translateX(0);
40
54
  }
@@ -105,18 +119,21 @@ dialog.drawer nav {
105
119
  }
106
120
 
107
121
  @media (forced-colors: active) {
108
- dialog.drawer {
109
- border-inline-end-color: ButtonText;
122
+ dialog.drawer::backdrop {
110
123
  background: Canvas;
111
- color: CanvasText;
112
- box-shadow: none;
113
124
  }
125
+ }
114
126
 
115
- dialog.drawer[data-side="end"] {
116
- border-inline-start-color: ButtonText;
127
+ @media (prefers-reduced-motion: no-preference) {
128
+ dialog.drawer.is-static {
129
+ animation: actual-dialog-static var(--duration-slow) var(--ease);
117
130
  }
131
+ }
118
132
 
119
- dialog.drawer::backdrop {
120
- background: Canvas;
121
- }
133
+ /* The close button shares the modal's interior placement (modal.css): absolute
134
+ at the panel's top inline-end, out of the content flow. The header reserves
135
+ inline room so the title never runs under it. */
136
+ dialog.drawer > header,
137
+ dialog.drawer > form > header {
138
+ padding-inline-end: calc(var(--control-size) + var(--space-30));
122
139
  }
@@ -9,7 +9,16 @@
9
9
  * .is-sheet marks "auto" resolved to bottom-sheet presentation on coarse
10
10
  * pointers. Authors opt into mobile behavior with
11
11
  * [data-flyout-mobile="sheet|auto|none|anchored"].
12
- * --flyout-trigger-width is set by surface.js while an open flyout is positioned.
12
+ * --surface-anchor-width is set by surface.js while an open flyout is positioned.
13
+ *
14
+ * Public hooks:
15
+ * --flyout-inline-size
16
+ * --flyout-max-inline-size
17
+ *
18
+ * Internal:
19
+ * --menu-item-size (derived from --control-size for menu rows)
20
+ * --available-height (measured by floating.js)
21
+ * --surface-anchor-width (measured by surface.js)
13
22
  */
14
23
 
15
24
  .flyout-trigger {
@@ -32,28 +41,32 @@
32
41
  }
33
42
 
34
43
  .flyout {
44
+ --menu-item-size: var(--control-size);
35
45
  z-index: var(--z-menu);
36
46
  /* No-JS fallback: below the trigger inside the relative .flyout-trigger.
37
47
  surface.js switches an open flyout to position: fixed with measured
38
48
  coordinates; this only covers a flyout revealed without the runtime. */
39
49
  position: absolute;
40
- inset-block-start: calc(100% + var(--space-1));
50
+ inset-block-start: calc(100% + var(--space-10));
41
51
  inset-inline-start: 0;
42
52
  display: grid;
43
- gap: var(--space-1);
53
+ gap: var(--space-10);
44
54
  inline-size: var(--flyout-inline-size, max-content);
45
55
  min-inline-size: min(
46
- var(--flyout-trigger-width, 0px),
47
- min(var(--flyout-max-inline-size, 20rem), calc(var(--viewport-inline) - var(--space-6)))
56
+ var(--surface-anchor-width, 0px),
57
+ min(var(--flyout-max-inline-size, 20rem), calc(var(--viewport-inline) - var(--space-60)))
48
58
  );
49
59
  max-inline-size: min(
50
60
  var(--flyout-max-inline-size, 20rem),
51
- calc(var(--viewport-inline) - var(--space-6))
61
+ calc(var(--viewport-inline) - var(--space-60))
62
+ );
63
+ max-block-size: min(
64
+ 24rem,
65
+ var(--available-height, calc(var(--viewport-block) - var(--space-60)))
52
66
  );
53
- max-block-size: min(24rem, var(--available-height, calc(var(--viewport-block) - var(--space-6))));
54
67
  overflow: auto;
55
68
  margin: 0;
56
- padding: var(--space-1);
69
+ padding: var(--space-10);
57
70
  border: var(--border-width) solid var(--border);
58
71
  border-radius: var(--radius);
59
72
  background: var(--surface-raised);
@@ -62,9 +75,9 @@
62
75
  opacity: 1;
63
76
  scale: 1;
64
77
  transition:
65
- opacity var(--duration-fast, var(--duration)) var(--ease, ease),
66
- scale var(--duration-fast, var(--duration)) var(--ease, ease),
67
- translate var(--duration-fast, var(--duration)) var(--ease, ease);
78
+ opacity var(--duration-fast) var(--ease),
79
+ scale var(--duration-fast) var(--ease),
80
+ translate var(--duration-fast) var(--ease);
68
81
  }
69
82
 
70
83
  .flyout.is-open:not(.is-sheet) {
@@ -81,6 +94,7 @@
81
94
  /* Bottom-sheet presentation. surface.js toggles .is-sheet when the flyout is
82
95
  opened on coarse pointer / narrow viewports. */
83
96
  .flyout.is-sheet {
97
+ --menu-item-size: var(--control-size-lg);
84
98
  position: fixed;
85
99
  inset-inline: 0;
86
100
  inset-block-start: auto;
@@ -91,8 +105,8 @@
91
105
  max-block-size: min(calc(var(--viewport-block) * 0.75), 32rem);
92
106
  overflow: auto;
93
107
  grid-template-columns: 1fr;
94
- padding: max(var(--space-3), env(safe-area-inset-top)) var(--space-3)
95
- max(var(--space-3), env(safe-area-inset-bottom));
108
+ padding: max(var(--space-30), env(safe-area-inset-top)) var(--space-30)
109
+ max(var(--space-30), env(safe-area-inset-bottom));
96
110
  border-end-start-radius: 0;
97
111
  border-end-end-radius: 0;
98
112
  }
@@ -105,23 +119,23 @@
105
119
 
106
120
  .flyout.is-sheet[hidden] {
107
121
  scale: 1;
108
- translate: 0 var(--space-3);
122
+ translate: 0 var(--space-30);
109
123
  }
110
124
 
111
125
  @starting-style {
112
126
  .flyout.is-sheet.is-open {
113
127
  scale: 1;
114
- translate: 0 var(--space-3);
128
+ translate: 0 var(--space-30);
115
129
  }
116
130
  }
117
131
 
118
132
  @supports (transition-behavior: allow-discrete) {
119
133
  .flyout {
120
134
  transition:
121
- opacity var(--duration-fast, var(--duration)) var(--ease, ease),
122
- scale var(--duration-fast, var(--duration)) var(--ease, ease),
123
- translate var(--duration-fast, var(--duration)) var(--ease, ease),
124
- display var(--duration-fast, var(--duration)) allow-discrete;
135
+ opacity var(--duration-fast) var(--ease),
136
+ scale var(--duration-fast) var(--ease),
137
+ translate var(--duration-fast) var(--ease),
138
+ display var(--duration-fast) allow-discrete;
125
139
  }
126
140
  }
127
141
 
@@ -131,7 +145,7 @@
131
145
 
132
146
  .flyout > :where(section, footer) {
133
147
  min-inline-size: 0;
134
- padding: var(--space-2);
148
+ padding: var(--space-20);
135
149
  }
136
150
 
137
151
  .flyout > section > :where(h1, h2, h3, h4, h5, h6) {
@@ -143,8 +157,8 @@
143
157
 
144
158
  .flyout > section > ul {
145
159
  display: grid;
146
- gap: var(--space-1);
147
- margin: var(--space-1) 0 0;
160
+ gap: var(--space-10);
161
+ margin: var(--space-10) 0 0;
148
162
  padding: 0;
149
163
  list-style: none;
150
164
  }
@@ -153,90 +167,6 @@
153
167
  margin: 0;
154
168
  }
155
169
 
156
- /* Item contract: a button, link, or menuitem placed directly in the flyout
157
- or directly in any of its <li> (bare list, or grouped section > ul > li).
158
- menu.js mirrors this contract in MENU_ITEM_SELECTOR — keep them in sync.
159
- :where(li) keeps specificity at a single class (0,1,0) so consumer
160
- overrides behave the same as a plain .flyout > rule. */
161
- :is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]) {
162
- display: flex;
163
- align-items: center;
164
- justify-content: flex-start;
165
- gap: var(--space-2);
166
- inline-size: 100%;
167
- min-block-size: var(--control-size);
168
- padding-inline: var(--space-2);
169
- border: 0;
170
- border-radius: var(--radius-sm);
171
- background: none;
172
- color: var(--text);
173
- font: inherit;
174
- font-weight: var(--font-weight);
175
- text-decoration: none;
176
- text-align: start;
177
- white-space: nowrap;
178
- cursor: pointer;
179
- }
180
-
181
- :is(.flyout.is-sheet, .flyout.is-sheet :where(li)) > :where(button, a, [role="menuitem"]) {
182
- min-block-size: var(--control-size-lg);
183
- }
184
-
185
- :is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]):focus-visible {
186
- background: var(--surface-subtle);
187
- outline: var(--focus-outline);
188
- outline-offset: var(--focus-outline-offset);
189
- }
190
-
191
- :is(.flyout, .flyout :where(li))
192
- > :where(button, a, [role="menuitem"]):is(:disabled, [aria-disabled="true"]) {
193
- cursor: not-allowed;
194
- opacity: var(--disabled-opacity);
195
- }
196
-
197
- :is(.flyout, .flyout :where(li)) > :where(a, [role="menuitem"])[aria-disabled="true"] {
198
- pointer-events: none;
199
- }
200
-
201
- @media (hover: hover) {
202
- :is(.flyout, .flyout :where(li))
203
- > :where(button, a, [role="menuitem"]):hover:not(
204
- :where(:focus-visible, :disabled, [aria-disabled="true"])
205
- ) {
206
- background: var(--surface-subtle);
207
- outline: none;
208
- }
209
- }
210
-
211
- .flyout > hr,
212
- .flyout > li > hr {
213
- inline-size: 100%;
214
- margin: 0;
215
- margin-block: var(--space-1);
216
- border: 0;
217
- border-block-start: var(--border-width) solid var(--border);
218
- }
219
-
220
- @media (forced-colors: active) {
221
- .flyout {
222
- border-color: ButtonText;
223
- background: Canvas;
224
- color: CanvasText;
225
- box-shadow: none;
226
- }
227
-
228
- :is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]):focus-visible {
229
- background: Canvas;
230
- color: CanvasText;
231
- }
232
-
233
- @media (hover: hover) {
234
- :is(.flyout, .flyout :where(li))
235
- > :where(button, a, [role="menuitem"]):hover:not(:where(:disabled, [aria-disabled="true"])) {
236
- outline: var(--border-width) solid ButtonText;
237
- outline-offset: -1px;
238
- background: Canvas;
239
- color: CanvasText;
240
- }
241
- }
242
- }
170
+ /* Vertical dividers — .menu-separator (menu.css) is the canonical way to
171
+ separate menu items. Flyout panels that are not menus use auto grid gap
172
+ declared on .flyout itself. */
@@ -12,6 +12,7 @@
12
12
 
13
13
  /* Overlays */
14
14
  @import "flyout.css";
15
+ @import "menu.css";
15
16
  @import "tooltip.css";
16
17
 
17
18
  /* Disclosure */
@@ -7,6 +7,18 @@
7
7
  * Cascade note: .join writes border-radius longhands on its direct children.
8
8
  * Keep this file loaded after control/component styles so those corners win at
9
9
  * equal specificity.
10
+ *
11
+ * Public hooks:
12
+ * --join-radius
13
+ *
14
+ * Internal:
15
+ * --join-ss
16
+ * --join-se
17
+ * --join-es
18
+ * --join-ee
19
+ * --control-border
20
+ * --control-bg
21
+ * --control-fg
10
22
  */
11
23
  .join {
12
24
  --join-radius: var(--radius);
@@ -34,7 +46,7 @@
34
46
  display: inline-flex;
35
47
  justify-content: center;
36
48
  min-block-size: var(--control-size);
37
- padding-inline: var(--space-3);
49
+ padding-inline: var(--space-30);
38
50
  border: var(--border-width) solid var(--control-border);
39
51
  border-radius: var(--radius);
40
52
  background: var(--control-bg);
@@ -95,7 +107,7 @@
95
107
  focus, and unsupported :has() browsers retain the control's normal ring. */
96
108
  .join:has(> :is(.input, .textarea, .select):focus-visible) {
97
109
  border-radius: var(--join-radius);
98
- outline: none;
110
+ outline: 2px solid transparent;
99
111
  box-shadow: var(--focus-ring-shadow);
100
112
  }
101
113
 
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * Use .key outside prose. Plain kbd inside .prose gets the same visual
5
5
  * treatment from prose.css so authored content stays convenient.
6
+ *
7
+ * Visual twin of .prose kbd (prose.css) — keep the two recipes in sync.
6
8
  */
7
9
  .key {
8
10
  display: inline-flex;