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
@@ -9,19 +9,35 @@
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
+ *
22
+ * Internal:
23
+ * --btn-bg
24
+ * --btn-fg
25
+ * --btn-border
26
+ * --btn-focus-ring-color
27
+ * --btn-border-block-end-color
28
+ * --btn-border-block-end-width
29
+ * --spinner-width
12
30
  */
13
31
 
14
32
  /* Intent boundary — inherited intents do not tint this component.
15
33
  Local intent classes still win because :where() has zero specificity. */
16
34
  :where(.btn) {
17
- /* @sync intent-boundary */
18
35
  --intent: initial;
19
36
  --intent-fg: initial;
20
37
  --ui-bg: initial;
21
38
  --ui-fg: initial;
22
39
  --ui-border: initial;
23
40
  --ui-hover-bg: initial;
24
- /* @sync intent-boundary */
25
41
  }
26
42
 
27
43
  .btn {
@@ -33,18 +49,17 @@
33
49
  --btn-border: var(--ui-border, var(--btn-default-border, transparent));
34
50
  --btn-radius: var(--radius);
35
51
  --btn-focus-color: var(--intent, var(--focus));
36
- --btn-focus-outline-width: calc(var(--border-width) * 2);
37
- --btn-hover-bg: var(--ui-hover-bg, var(--btn-bg));
38
52
  --btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
39
53
 
54
+ /* Positioning context for indicators, overlays, and grouped states. */
40
55
  position: relative;
41
56
  display: inline-flex;
42
57
  align-items: center;
43
58
  justify-content: center;
44
- gap: 0.5em;
59
+ gap: var(--btn-gap, 0.375em);
45
60
  min-block-size: var(--btn-min-size);
46
- padding-block: var(--space-2);
47
- padding-block: max(var(--space-1), calc((var(--btn-min-size) - 1lh) / 2));
61
+ padding-block: var(--space-20);
62
+ padding-block: max(var(--space-10), calc((var(--btn-min-size) - 1lh) / 2));
48
63
  padding-inline: var(--btn-pad-x);
49
64
  border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
50
65
  border-block-end-color: var(
@@ -57,7 +72,7 @@
57
72
  color: var(--btn-fg);
58
73
  font: inherit;
59
74
  font-size: var(--btn-font-size);
60
- font-weight: var(--font-weight-strong);
75
+ font-weight: var(--font-weight-medium);
61
76
  line-height: 1;
62
77
  text-decoration: none;
63
78
  cursor: pointer;
@@ -69,6 +84,11 @@
69
84
  transform var(--duration);
70
85
  }
71
86
 
87
+ .btn.icon-only {
88
+ inline-size: var(--btn-min-size);
89
+ padding-inline: 0;
90
+ }
91
+
72
92
  @supports (color: color-mix(in oklch, red, white)) {
73
93
  .btn {
74
94
  --btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
@@ -93,13 +113,40 @@
93
113
  --btn-hover-overlay: transparent;
94
114
  }
95
115
 
96
- /* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is shared with
97
- .badge in variants.css; the overlay darkening for solid/soft is .btn-specific. */
98
- @media (hover: hover) {
99
- .btn:not(:where(:disabled, [aria-disabled="true"])):hover {
100
- background: var(--btn-hover-bg);
101
- box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
116
+ /* Shared hover recipe — sets --ui-hover-bg consumed by the hover rule below.
117
+ Declared outside :hover: the variables resolve only when the hover rule
118
+ reads them, so the values are inert until interaction. */
119
+ .btn:is(.outline, .ghost) {
120
+ --ui-hover-bg: var(--surface-subtle);
121
+ }
122
+
123
+ @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);
102
126
  }
127
+
128
+ .btn.soft {
129
+ --ui-hover-bg: color-mix(
130
+ in oklch,
131
+ var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
132
+ var(--intent, var(--neutral))
133
+ );
134
+ }
135
+
136
+ .btn.outline {
137
+ --ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
138
+ }
139
+
140
+ .btn.ghost {
141
+ --ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
142
+ }
143
+ }
144
+
145
+ /* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
146
+ the overlay darkening for solid/soft is .btn-specific. */
147
+ .btn:not(:where(:disabled, [aria-disabled="true"])):hover {
148
+ background: var(--ui-hover-bg, var(--btn-bg));
149
+ box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
103
150
  }
104
151
 
105
152
  .btn:not(:where(:disabled, [aria-disabled="true"])):active {
@@ -124,23 +171,21 @@
124
171
  /* .btn.circle needs no local rule: the .circle utility (utilities.css) loads
125
172
  after components and overrides border-radius at equal specificity. */
126
173
 
127
- @media (hover: hover) {
128
- .btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
129
- background: transparent;
130
- color: var(--intent, var(--text));
131
- text-decoration-thickness: 0.12em;
132
- }
174
+ .btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
175
+ background: transparent;
176
+ color: var(--intent, var(--text));
177
+ text-decoration-thickness: 0.12em;
133
178
  }
134
179
 
135
180
  .btn:focus-visible {
136
- outline: var(--btn-focus-outline-width) solid var(--btn-focus-color);
137
- outline-offset: var(--focus-outline-offset);
138
- box-shadow: 0 0 0 3px var(--btn-focus-ring-color, var(--focus-ring));
181
+ outline: 2px solid transparent;
182
+ box-shadow: 0 0 0 var(--focus-ring-width) var(--btn-focus-ring-color, var(--focus-ring));
139
183
  }
140
184
 
141
185
  .btn.link:focus-visible {
142
- outline-color: currentColor;
186
+ outline: var(--focus-outline);
143
187
  outline-offset: 0.2em;
188
+ box-shadow: none;
144
189
  }
145
190
 
146
191
  .btn:disabled {
@@ -164,17 +209,18 @@ a.btn[aria-disabled="true"] {
164
209
  }
165
210
 
166
211
  .btn[aria-busy="true"] > .spinner {
167
- --spinner-size: 1em;
168
212
  --spinner-width: 0.15em;
169
213
  }
170
214
 
171
- /* Toggle */
172
- /* State must outrank the shared .outline/.ghost hover recipes: a pressed
173
- button keeps its intent background and foreground while hovered. */
174
- .btn:is([aria-pressed="true"], [aria-current]:not([aria-current="false"])):not(.link) {
175
- --btn-bg: var(--intent, var(--primary));
176
- --btn-fg: var(--intent-fg, var(--primary-fg));
177
- --btn-border: var(--intent, var(--primary));
215
+ /* Toggle and current state.
216
+ State must outrank the shared .outline/.ghost hover recipes: a pressed or
217
+ current button keeps its intent background and foreground while hovered.
218
+ aria-current="page" is the pagination current-page marker; aria-pressed
219
+ covers toggle buttons. */
220
+ .btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
221
+ --btn-bg: var(--intent, var(--state-selected));
222
+ --btn-fg: var(--intent-fg, var(--state-selected-fg));
223
+ --btn-border: var(--intent, var(--state-selected));
178
224
  --ui-hover-bg: var(--btn-bg);
179
225
  }
180
226
 
@@ -192,18 +238,16 @@ a.btn[aria-disabled="true"] {
192
238
  color: LinkText;
193
239
  }
194
240
 
195
- .btn:is([aria-pressed="true"], [aria-current]:not([aria-current="false"])):not(.link) {
241
+ .btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
196
242
  border-color: Highlight;
197
243
  background: Highlight;
198
244
  color: HighlightText;
199
245
  forced-color-adjust: none;
200
246
  }
201
247
 
202
- @media (hover: hover) {
203
- .btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
204
- outline: var(--border-width) solid ButtonText;
205
- outline-offset: 2px;
206
- }
248
+ .btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
249
+ outline: var(--border-width) solid ButtonText;
250
+ outline-offset: 2px;
207
251
  }
208
252
 
209
253
  .btn:disabled,
@@ -4,28 +4,36 @@
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
+ * --heading
19
+ * --busy-overlay-bg
7
20
  */
8
21
  /* Intent boundary — inherited intents do not tint this component.
9
22
  Local intent classes still win because :where() has zero specificity. */
10
23
  :where(.card) {
11
- /* @sync intent-boundary */
12
- --intent: initial;
13
- --intent-fg: initial;
14
24
  --ui-bg: initial;
15
25
  --ui-fg: initial;
16
26
  --ui-border: initial;
17
- --ui-hover-bg: initial;
18
- /* @sync intent-boundary */
19
27
  }
20
28
 
21
29
  .card {
22
- --card-pad: var(--space-4);
30
+ --card-pad: var(--space-40);
23
31
  --card-radius: var(--radius-lg);
24
32
  --card-max-inline-size: none;
25
33
 
26
34
  max-inline-size: var(--card-max-inline-size);
27
35
  padding: var(--card-pad);
28
- background: var(--ui-bg, var(--surface));
36
+ background: var(--ui-bg, var(--surface-raised));
29
37
  color: var(--ui-fg, var(--text));
30
38
  border: var(--border-width) solid var(--ui-border, var(--border));
31
39
  border-radius: var(--card-radius);
@@ -40,7 +48,7 @@
40
48
  flex-wrap: wrap;
41
49
  align-items: var(--items-align, center);
42
50
  justify-content: space-between;
43
- gap: var(--space-3);
51
+ gap: var(--space-30);
44
52
  min-inline-size: 0;
45
53
  }
46
54
 
@@ -67,16 +75,8 @@
67
75
  .card.subtle {
68
76
  background: var(--surface-subtle);
69
77
  }
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
78
  .card.compact {
79
- --card-pad: var(--space-2);
79
+ --card-pad: var(--space-20);
80
80
  }
81
81
 
82
82
  /* Full-bleed children */
@@ -118,23 +118,3 @@
118
118
  .card > :where(header, footer, figure).bleed {
119
119
  padding: var(--card-pad);
120
120
  }
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,91 +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
- box-shadow: var(--focus-ring-shadow);
190
- }
191
-
192
- :is(.flyout, .flyout :where(li))
193
- > :where(button, a, [role="menuitem"]):is(:disabled, [aria-disabled="true"]) {
194
- cursor: not-allowed;
195
- opacity: var(--disabled-opacity);
196
- }
197
-
198
- :is(.flyout, .flyout :where(li)) > :where(a, [role="menuitem"])[aria-disabled="true"] {
199
- pointer-events: none;
200
- }
201
-
202
- @media (hover: hover) {
203
- :is(.flyout, .flyout :where(li))
204
- > :where(button, a, [role="menuitem"]):hover:not(
205
- :where(:focus-visible, :disabled, [aria-disabled="true"])
206
- ) {
207
- background: var(--surface-subtle);
208
- outline: none;
209
- }
210
- }
211
-
212
- .flyout > hr,
213
- .flyout > li > hr {
214
- inline-size: 100%;
215
- margin: 0;
216
- margin-block: var(--space-1);
217
- border: 0;
218
- border-block-start: var(--border-width) solid var(--border);
219
- }
220
-
221
- @media (forced-colors: active) {
222
- .flyout {
223
- border-color: ButtonText;
224
- background: Canvas;
225
- color: CanvasText;
226
- box-shadow: none;
227
- }
228
-
229
- :is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]):focus-visible {
230
- background: Canvas;
231
- color: CanvasText;
232
- }
233
-
234
- @media (hover: hover) {
235
- :is(.flyout, .flyout :where(li))
236
- > :where(button, a, [role="menuitem"]):hover:not(:where(:disabled, [aria-disabled="true"])) {
237
- outline: var(--border-width) solid ButtonText;
238
- outline-offset: -1px;
239
- background: Canvas;
240
- color: CanvasText;
241
- }
242
- }
243
- }
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,8 +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: var(--focus-outline);
99
- outline-offset: var(--focus-outline-offset);
110
+ outline: 2px solid transparent;
100
111
  box-shadow: var(--focus-ring-shadow);
101
112
  }
102
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;