actual-css 0.6.0 → 0.8.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 (143) hide show
  1. package/CHANGELOG.md +214 -1
  2. package/README.md +13 -11
  3. package/components.json +668 -0
  4. package/dist/actual.css +166 -37
  5. package/dist/actual.full.css +1005 -550
  6. package/dist/actual.full.js +1 -1
  7. package/dist/actual.full.min.css +1 -1
  8. package/dist/actual.js +1 -1
  9. package/dist/actual.min.css +1 -1
  10. package/docs/design-notes/accessibility-decisions.md +33 -0
  11. package/docs/design-notes/browser-support.md +82 -25
  12. package/docs/design-notes/column-layout.md +3 -3
  13. package/docs/design-notes/css-anchor.md +75 -21
  14. package/docs/design-notes/enhancement-contract.md +14 -6
  15. package/docs/design-notes/grid.md +4 -4
  16. package/docs/design-notes/naming.md +6 -1
  17. package/docs/design-notes/platform-alignment.md +99 -2
  18. package/docs/design-notes/popover-manual-poc.md +328 -0
  19. package/docs/design-notes/surface-reparenting.md +131 -0
  20. package/docs/design-notes/tooltip-coordinate-space.md +132 -0
  21. package/docs/design-notes/widget-primitives.md +9 -6
  22. package/docs/pages/components/accordion.md +41 -2
  23. package/docs/pages/components/alert.md +80 -30
  24. package/docs/pages/components/app-navigation.md +6 -0
  25. package/docs/pages/components/avatar.md +6 -6
  26. package/docs/pages/components/badge.md +101 -16
  27. package/docs/pages/components/button.md +16 -8
  28. package/docs/pages/components/card.md +10 -10
  29. package/docs/pages/components/dialog.md +16 -6
  30. package/docs/pages/components/fab.md +4 -3
  31. package/docs/pages/components/list.md +42 -4
  32. package/docs/pages/components/navbar.md +17 -1
  33. package/docs/pages/components/pagination.md +2 -2
  34. package/docs/pages/components/steps.md +19 -0
  35. package/docs/pages/components/table.md +3 -2
  36. package/docs/pages/enhancements/flyout.md +144 -24
  37. package/docs/pages/enhancements/overview.md +25 -0
  38. package/docs/pages/enhancements/status-bar.md +20 -0
  39. package/docs/pages/enhancements/tabs.md +45 -37
  40. package/docs/pages/enhancements/tooltip.md +40 -2
  41. package/docs/pages/examples/overview.md +38 -5
  42. package/docs/pages/forms/choice-cards.md +6 -2
  43. package/docs/pages/forms/combobox.md +1 -1
  44. package/docs/pages/forms/floating-field.md +4 -3
  45. package/docs/pages/forms/joined-controls.md +22 -0
  46. package/docs/pages/forms/otp.md +2 -1
  47. package/docs/pages/forms/overview.md +1 -1
  48. package/docs/pages/forms/states-and-sizes.md +16 -18
  49. package/docs/pages/forms/sticky-actions.md +2 -2
  50. package/docs/pages/forms/switch.md +1 -1
  51. package/docs/pages/foundations/philosophy.md +67 -22
  52. package/docs/pages/foundations/theming.md +10 -2
  53. package/docs/pages/foundations/tokens.md +118 -21
  54. package/docs/pages/foundations/typography.md +35 -14
  55. package/docs/pages/guides/introduction.md +11 -9
  56. package/docs/pages/guides/progressive-enhancement.md +136 -49
  57. package/docs/pages/guides/tailwind.md +3 -2
  58. package/docs/pages/layout/media.md +1 -1
  59. package/docs/pages/layout/overview.md +11 -1
  60. package/docs/pages/layout/scroll-snap.md +3 -3
  61. package/docs/pages/layout/stack.md +17 -9
  62. package/docs/pages/patterns/icon-input.md +4 -2
  63. package/docs/pages/patterns/icon-slot.md +2 -0
  64. package/docs/pages/patterns/intent-nesting.md +1 -1
  65. package/docs/pages/utilities/overview.md +23 -2
  66. package/docs/pages/utilities/sizing-wrapping.md +1 -1
  67. package/docs/pages/utilities/spacing.md +10 -8
  68. package/llms.txt +2 -1
  69. package/package.json +19 -8
  70. package/{scripts/reserved-classes.json → reserved-classes.json} +4 -6
  71. package/src/css/components/accordion.css +42 -3
  72. package/src/css/components/alert.css +25 -13
  73. package/src/css/components/app-nav.css +18 -8
  74. package/src/css/components/avatar.css +13 -1
  75. package/src/css/components/badge.css +78 -12
  76. package/src/css/components/breadcrumb.css +2 -3
  77. package/src/css/components/button.css +9 -16
  78. package/src/css/components/card.css +9 -2
  79. package/src/css/components/chat.css +1 -0
  80. package/src/css/components/data-list.css +1 -1
  81. package/src/css/components/drawer.css +79 -25
  82. package/src/css/components/fab.css +0 -6
  83. package/src/css/components/flyout.css +46 -63
  84. package/src/css/components/index.css +0 -1
  85. package/src/css/components/indicator.css +21 -0
  86. package/src/css/components/join.css +7 -0
  87. package/src/css/components/key.css +10 -0
  88. package/src/css/components/list.css +40 -4
  89. package/src/css/components/menu.css +18 -11
  90. package/src/css/components/modal.css +48 -33
  91. package/src/css/components/navbar.css +37 -4
  92. package/src/css/components/pagination.css +1 -0
  93. package/src/css/components/spinner.css +6 -6
  94. package/src/css/components/status-bar.css +0 -5
  95. package/src/css/components/steps.css +45 -59
  96. package/src/css/components/tab.css +29 -9
  97. package/src/css/components/table.css +5 -5
  98. package/src/css/components/tooltip.css +45 -13
  99. package/src/css/core/base.css +23 -1
  100. package/src/css/core/intents.css +17 -6
  101. package/src/css/core/theme.css +27 -0
  102. package/src/css/core/tokens.css +35 -15
  103. package/src/css/core/variants.css +64 -15
  104. package/src/css/forms/choice-card.css +4 -4
  105. package/src/css/forms/choice.css +6 -3
  106. package/src/css/forms/control.css +2 -1
  107. package/src/css/forms/floating-field.css +3 -3
  108. package/src/css/forms/form.css +4 -3
  109. package/src/css/forms/input-icon.css +20 -14
  110. package/src/css/forms/native.css +6 -1
  111. package/src/css/forms/otp.css +9 -1
  112. package/src/css/forms/range.css +2 -0
  113. package/src/css/forms/select.css +29 -9
  114. package/src/css/forms/switch.css +19 -5
  115. package/src/css/layout/app-layout.css +1 -1
  116. package/src/css/layout/grid.css +50 -76
  117. package/src/css/layout/scroll-snap.css +11 -7
  118. package/src/css/layout/scroller.css +3 -4
  119. package/src/css/layout/stack.css +2 -2
  120. package/src/css/typography/fluid.css +4 -3
  121. package/src/css/typography/prose.css +6 -5
  122. package/src/css/utilities/base.css +2 -1
  123. package/src/css/utilities/extra.css +20 -17
  124. package/src/js/context-menu.js +27 -26
  125. package/src/js/dialog.js +2 -1
  126. package/src/js/dismiss.js +5 -0
  127. package/src/js/enhance.js +34 -2
  128. package/src/js/enhancement-loader.js +36 -43
  129. package/src/js/events.js +0 -3
  130. package/src/js/filter.js +0 -1
  131. package/src/js/floating-space.js +42 -0
  132. package/src/js/flyout.js +9 -44
  133. package/src/js/focus-group.js +7 -0
  134. package/src/js/focus.js +6 -0
  135. package/src/js/menu.js +10 -1
  136. package/src/js/selectors.js +5 -9
  137. package/src/js/status.js +44 -7
  138. package/src/js/surface.js +106 -132
  139. package/src/js/tab.js +6 -1
  140. package/src/js/tooltip.js +152 -53
  141. package/src/js/validation.js +1 -1
  142. package/src/css/components/surface.css +0 -49
  143. package/src/js/floating.js +0 -432
@@ -62,8 +62,18 @@ dialog.drawer[open] {
62
62
  transform var(--drawer-motion-duration) var(--ease-enter);
63
63
  }
64
64
 
65
- /* Backdrop color sourced from components/surface.css so it matches .modal
66
- and .surface-backdrop across the dialog family. */
65
+ /* A form is the drawer's content wrapper when actions need method="dialog".
66
+ Span the drawer tracks so the form can keep the header at the top, let the
67
+ body absorb the available height, and keep the footer at the bottom. */
68
+ dialog.drawer > form {
69
+ display: grid;
70
+ grid-row: 1 / -1;
71
+ grid-template-rows: auto minmax(0, 1fr) auto;
72
+ gap: var(--space-40);
73
+ min-block-size: 100%;
74
+ }
75
+
76
+ /* The shared backdrop token keeps drawer and modal scrims consistent. */
67
77
  dialog.drawer::backdrop {
68
78
  background: var(--backdrop-fill);
69
79
  opacity: 0;
@@ -128,33 +138,33 @@ dialog.drawer nav {
128
138
  }
129
139
 
130
140
  /* Safari before 16.4 does not support :dir(); keep the ancestor-attribute
131
- path as the Minimal-tier fallback. */
141
+ path as the Degraded-tier fallback. The :dir() rules are dropped on their
142
+ own there, so they need no wrapper. :dir() follows inherited HTML
143
+ directionality and handles nested direction changes; the explicit ltr rules
144
+ neutralize an outer RTL fallback selector. Each specificity group keeps the
145
+ fallback before the :dir() rules. */
132
146
  [dir="rtl"] dialog.drawer {
133
147
  --drawer-closed-translate: 100%;
134
148
  }
135
149
 
136
- [dir="rtl"] dialog.drawer[data-side="end"] {
150
+ dialog.drawer:dir(ltr) {
137
151
  --drawer-closed-translate: -100%;
138
152
  }
139
153
 
140
- /* :dir() follows inherited HTML directionality and handles nested direction
141
- changes. Explicit ltr rules neutralize an outer RTL fallback selector. */
142
- @supports selector(:dir(rtl)) {
143
- dialog.drawer:dir(ltr) {
144
- --drawer-closed-translate: -100%;
145
- }
154
+ dialog.drawer:dir(rtl) {
155
+ --drawer-closed-translate: 100%;
156
+ }
146
157
 
147
- dialog.drawer[data-side="end"]:dir(ltr) {
148
- --drawer-closed-translate: 100%;
149
- }
158
+ [dir="rtl"] dialog.drawer[data-side="end"] {
159
+ --drawer-closed-translate: -100%;
160
+ }
150
161
 
151
- dialog.drawer:dir(rtl) {
152
- --drawer-closed-translate: 100%;
153
- }
162
+ dialog.drawer[data-side="end"]:dir(ltr) {
163
+ --drawer-closed-translate: 100%;
164
+ }
154
165
 
155
- dialog.drawer[data-side="end"]:dir(rtl) {
156
- --drawer-closed-translate: -100%;
157
- }
166
+ dialog.drawer[data-side="end"]:dir(rtl) {
167
+ --drawer-closed-translate: -100%;
158
168
  }
159
169
 
160
170
  @media (prefers-reduced-motion: no-preference) {
@@ -163,15 +173,59 @@ dialog.drawer nav {
163
173
  }
164
174
  }
165
175
 
166
- /* The close button shares the modal's interior placement (modal.css): absolute
167
- at the panel's top inline-end, out of the content flow. The header reserves
168
- inline room so the title never runs under it. */
176
+ /* The header reserves inline room so the title never runs under the close
177
+ control. */
169
178
  dialog.drawer > header,
170
179
  dialog.drawer > form > header {
171
180
  padding-inline-end: calc(var(--control-size) + var(--space-30));
172
181
  }
173
- @media print {
174
- dialog.drawer[open] {
175
- display: none !important;
182
+
183
+ /* Close control, owned here so a drawer-only build does not depend on
184
+ modal.css. Mirrors the modal's .dialog-close placement: absolute at the
185
+ panel's top inline-end, out of the content flow. */
186
+ .drawer-close {
187
+ --drawer-close-icon-size: 1rem;
188
+
189
+ display: inline-grid;
190
+ place-items: center;
191
+ padding: 0;
192
+ border: 0;
193
+ border-radius: var(--radius);
194
+ color: var(--text-muted);
195
+ cursor: pointer;
196
+
197
+ position: absolute;
198
+ z-index: 1;
199
+ inset-block-start: var(--space-20);
200
+ inset-inline-end: var(--space-20);
201
+
202
+ inline-size: var(--control-size);
203
+ block-size: var(--control-size);
204
+
205
+ background: var(--surface-subtle);
206
+ }
207
+
208
+ .drawer-close::before {
209
+ content: "";
210
+ inline-size: var(--drawer-close-icon-size);
211
+ block-size: var(--drawer-close-icon-size);
212
+ background: currentColor;
213
+ -webkit-mask: var(--icon-close) center / contain no-repeat;
214
+ mask: var(--icon-close) center / contain no-repeat;
215
+ }
216
+
217
+ @media (hover: hover) {
218
+ .drawer-close:hover {
219
+ box-shadow: inset 0 0 0 999px var(--hover-overlay);
220
+ color: var(--text);
176
221
  }
177
222
  }
223
+
224
+ .drawer-close:active {
225
+ box-shadow: inset 0 0 0 999px var(--hover-overlay);
226
+ }
227
+
228
+ .drawer-close:focus-visible {
229
+ outline: 2px solid transparent;
230
+ box-shadow: var(--focus-ring-shadow);
231
+ }
@@ -126,9 +126,3 @@
126
126
  opacity: 1;
127
127
  transform: none;
128
128
  }
129
-
130
- @media print {
131
- .fab {
132
- display: none;
133
- }
134
- }
@@ -1,14 +1,12 @@
1
1
  /*
2
2
  * Flyout — floating panel for actions and links.
3
3
  *
4
- * JavaScript owns positioning (position: fixed + left/top), state
5
- * (hidden + .is-open), focus, and keyboard behavior. CSS owns the surfaced
6
- * panel appearance, item styles, and animation.
4
+ * JavaScript promotes the panel to the top layer and chooses viewport or
5
+ * document coordinates from its anchor. It also owns state (.is-open), focus,
6
+ * and keyboard behavior. CSS owns the surfaced panel appearance, item styles,
7
+ * and animation.
7
8
  *
8
- * Runtime markers (managed by surface.js): .is-open marks an open flyout,
9
- * .is-sheet marks "auto" resolved to bottom-sheet presentation on coarse
10
- * pointers. Authors opt into mobile behavior with
11
- * [data-flyout-mobile="sheet|auto|none|anchored"].
9
+ * Runtime marker (managed by surface.js): .is-open marks an open flyout.
12
10
  * --surface-anchor-width is set by surface.js while an open flyout is positioned.
13
11
  *
14
12
  * Public hooks:
@@ -17,7 +15,7 @@
17
15
  *
18
16
  * Internal:
19
17
  * --menu-item-size (derived from --control-size for menu rows)
20
- * --available-height (measured by floating.js)
18
+ * --available-height (measured by the positioner)
21
19
  * --surface-anchor-width (measured by surface.js)
22
20
  */
23
21
 
@@ -44,8 +42,8 @@
44
42
  --menu-item-size: var(--control-size);
45
43
  z-index: var(--z-menu);
46
44
  /* No-JS fallback: below the trigger inside the relative .flyout-trigger.
47
- surface.js switches an open flyout to position: fixed with measured
48
- coordinates; this only covers a flyout revealed without the runtime. */
45
+ surface.js replaces this with the anchor's coupled position/coordinate
46
+ mode; this only covers a flyout revealed without the runtime. */
49
47
  position: absolute;
50
48
  inset-block-start: calc(100% + var(--space-10));
51
49
  inset-inline-start: 0;
@@ -73,73 +71,63 @@
73
71
  box-shadow: var(--shadow-popout);
74
72
  list-style: none;
75
73
  opacity: 1;
76
- /* Anchored flyouts fade in and out; scaling around the center would detach
77
- the anchored edge from its trigger on wide panels. The sheet presentation
78
- below adds its own translate motion. */
74
+ /* Flyouts fade in and out; scaling around the center would detach the
75
+ anchored edge from its trigger on wide panels. */
79
76
  transition: opacity var(--duration-fast) ease;
80
77
  }
81
78
 
82
- .flyout.is-open:not(.is-sheet) {
83
- inset: auto;
84
- }
85
-
86
- @starting-style {
87
- .flyout.is-open {
88
- opacity: 0;
89
- }
90
- }
91
-
92
- /* Bottom-sheet presentation. surface.js toggles .is-sheet when the flyout is
93
- opened on coarse pointer / narrow viewports. */
94
- .flyout.is-sheet {
95
- --menu-item-size: var(--control-size-lg);
79
+ /* A popover enters the top layer with UA geometry that conflicts with anchored
80
+ positioning. surface.js sets popover="manual" on every surface it manages,
81
+ so this is the transport's own geometry rather than an opt-in. Kept scoped to
82
+ Actual surfaces so bare [popover] elements retain their platform
83
+ presentation. */
84
+ .flyout[popover] {
96
85
  position: fixed;
97
- inset-inline: 0;
98
- inset-block-start: auto;
99
- inset-block-end: 0;
100
- inline-size: auto;
101
- min-inline-size: 0;
102
- max-inline-size: none;
103
- max-block-size: min(calc(var(--viewport-block) * 0.75), 32rem);
104
- overflow: auto;
105
- grid-template-columns: 1fr;
106
- padding: max(var(--space-30), env(safe-area-inset-top)) var(--space-30)
107
- max(var(--space-30), env(safe-area-inset-bottom));
108
- border-end-start-radius: 0;
109
- border-end-end-radius: 0;
110
- /* Sheet motion is a directional slide from the viewport edge; it replaces
111
- the base fade-only recipe. */
112
- transition:
113
- opacity var(--duration-fast) ease,
114
- translate var(--duration-fast) ease;
86
+ inset: auto;
87
+ margin: 0;
115
88
  }
116
89
 
117
- .flyout[hidden] {
90
+ /* A presentation class may own its *visible* display type, but must not
91
+ override the platform's hidden state. The UA closes a popover with
92
+ display: none, and an author declaration beats the UA origin whatever the
93
+ specificity — so .flyout's own display: grid would leave a closed popover
94
+ painted at its static position. The opacity is the exit target: with nothing
95
+ to animate, the overlay/display allow-discrete transition below would hold a
96
+ fully opaque panel in the top layer and then snap it away. */
97
+ .flyout[popover]:not(:popover-open) {
118
98
  display: none;
119
99
  opacity: 0;
120
100
  }
121
101
 
122
- .flyout.is-sheet[hidden] {
123
- translate: 0 var(--space-30);
102
+ /* Both lifecycles in one rule: .is-open for a surface.js panel, :popover-open
103
+ for one an author drives natively with popovertarget. */
104
+ .flyout:is(.is-open, :popover-open) {
105
+ inset: auto;
124
106
  }
125
107
 
108
+ /* @starting-style is above the floor (Safari 17.5), so where it is missing the
109
+ entry simply has no animated origin and the panel appears at full opacity. */
126
110
  @starting-style {
127
- .flyout.is-sheet.is-open {
128
- translate: 0 var(--space-30);
111
+ .flyout:is(.is-open, :popover-open) {
112
+ opacity: 0;
129
113
  }
130
114
  }
131
115
 
116
+ .flyout[hidden] {
117
+ display: none;
118
+ opacity: 0;
119
+ }
120
+
121
+ /* display keeps the exit transition alive for the [hidden] path; overlay does
122
+ the same for the native popover path, where hidePopover() would otherwise
123
+ drop the panel out of the top layer on the first frame and let a parent
124
+ overflow or a dialog clip the fade. An engine that cannot transition either
125
+ property ignores it and the exit is instant. */
132
126
  @supports (transition-behavior: allow-discrete) {
133
127
  .flyout {
134
128
  transition:
135
129
  opacity var(--duration-fast) ease,
136
- display var(--duration-fast) allow-discrete;
137
- }
138
-
139
- .flyout.is-sheet {
140
- transition:
141
- opacity var(--duration-fast) ease,
142
- translate var(--duration-fast) ease,
130
+ overlay var(--duration-fast) allow-discrete,
143
131
  display var(--duration-fast) allow-discrete;
144
132
  }
145
133
  }
@@ -157,7 +145,7 @@
157
145
  margin: 0;
158
146
  color: var(--text-muted);
159
147
  font-size: var(--font-size-xs);
160
- font-weight: var(--font-weight-strong);
148
+ font-weight: var(--font-weight-semibold);
161
149
  }
162
150
 
163
151
  .flyout > section > ul {
@@ -175,8 +163,3 @@
175
163
  /* Vertical dividers — .menu-separator (menu.css) is the canonical way to
176
164
  separate menu items. Flyout panels that are not menus use auto grid gap
177
165
  declared on .flyout itself. */
178
- @media print {
179
- .flyout {
180
- display: none !important;
181
- }
182
- }
@@ -6,7 +6,6 @@
6
6
 
7
7
  /* Surfaces */
8
8
  @import "./card.css";
9
- @import "./surface.css";
10
9
  @import "./modal.css";
11
10
  @import "./drawer.css";
12
11
 
@@ -38,10 +38,31 @@
38
38
  inset-block-end: 0;
39
39
  }
40
40
 
41
+ /* Safari before 16.4 has no :dir(); the ancestor-attribute path is the
42
+ Degraded-tier fallback and the :dir() rules are dropped on their own there.
43
+ :dir() follows inherited directionality, so the explicit ltr rules
44
+ neutralize an outer RTL fallback for a nested direction change. The rules
45
+ stay grouped by specificity so the fallback never follows the :dir() rules. */
46
+ [dir="rtl"] .indicator-item {
47
+ --indicator-translate-x: -50%;
48
+ }
49
+
50
+ .indicator-item:dir(ltr) {
51
+ --indicator-translate-x: 50%;
52
+ }
53
+
41
54
  .indicator-item:dir(rtl) {
42
55
  --indicator-translate-x: -50%;
43
56
  }
44
57
 
58
+ [dir="rtl"] .indicator-item.start {
59
+ --indicator-translate-x: 50%;
60
+ }
61
+
62
+ .indicator-item.start:dir(ltr) {
63
+ --indicator-translate-x: -50%;
64
+ }
65
+
45
66
  .indicator-item.start:dir(rtl) {
46
67
  --indicator-translate-x: 50%;
47
68
  }
@@ -63,6 +63,13 @@
63
63
  border-end-start-radius: var(--join-es);
64
64
  }
65
65
 
66
+ /* A square icon button has an explicit block size, which otherwise prevents
67
+ align-items: stretch from matching a taller labelled sibling. The button's
68
+ existing min-block-size keeps the standalone square-size invariant intact. */
69
+ .join > .btn.icon-only {
70
+ block-size: auto;
71
+ }
72
+
66
73
  .join > :first-child {
67
74
  --join-ss: var(--join-radius);
68
75
  --join-es: var(--join-radius);
@@ -27,3 +27,13 @@
27
27
  vertical-align: 0.08em;
28
28
  }
29
29
  /* @sync key-recipe */
30
+
31
+ /* .key has a compact typographic default of its own, so its local size roles
32
+ step around that default instead of inheriting the generic mapping. */
33
+ .key.sm {
34
+ font-size: var(--font-size-xs);
35
+ }
36
+
37
+ .key.lg {
38
+ font-size: var(--font-size-md);
39
+ }
@@ -18,6 +18,7 @@
18
18
  --list-divider: var(--border-width) solid var(--border);
19
19
 
20
20
  display: grid;
21
+ grid-template-columns: minmax(0, 1fr);
21
22
  gap: 0;
22
23
  margin: 0;
23
24
  padding: 0;
@@ -29,10 +30,17 @@
29
30
  margin: 0;
30
31
  }
31
32
 
33
+ /*
34
+ * Flex, not a three-track grid: the leading and trailing regions are optional,
35
+ * and a fixed `auto minmax(0, 1fr) auto` template gives an absent region a
36
+ * track anyway — the content of a row with no leading region lands in the
37
+ * leading track and the trailing control inherits the flexible one, which is
38
+ * the opposite of the intent. Flex drops both the track and its gap when a
39
+ * region is not there, so all four shapes work off one rule.
40
+ */
32
41
  .list > :where(.list-item),
33
42
  .list > li > .list-item {
34
- display: grid;
35
- grid-template-columns: auto minmax(0, 1fr) auto;
43
+ display: flex;
36
44
  /* Regions align to the first line, so leading, title, and trailing controls
37
45
  share one horizontal line while supporting text wraps beneath. */
38
46
  align-items: start;
@@ -48,6 +56,7 @@
48
56
  }
49
57
 
50
58
  a.list-item {
59
+ position: relative;
51
60
  color: inherit;
52
61
  text-decoration: none;
53
62
  }
@@ -56,7 +65,31 @@ a.list-item:hover {
56
65
  background: var(--surface-subtle);
57
66
  }
58
67
 
68
+ /* Current row in a navigational list (master/detail). Generic by design: the
69
+ visual treatment only cares that the row is current, so presence is the
70
+ contract — the attribute is set on the current row and removed everywhere
71
+ else, never serialized as aria-current="false". The value (page, location,
72
+ …) stays free for assistive technology.
73
+ Calm by design: the row keeps its normal text metrics and color on a subtle
74
+ surface, and the selected accent lives on an inset inline-start trait only.
75
+ The trait is a real border, so it survives forced colors without a repaint
76
+ block. The positioning context lives on every row link, so becoming current
77
+ never moves an app-positioned decoration to a new containing block. */
78
+ a.list-item[aria-current] {
79
+ background: var(--surface-subtle);
80
+ }
81
+
82
+ a.list-item[aria-current]::before {
83
+ content: "";
84
+ position: absolute;
85
+ inset-block: var(--list-item-pad-block);
86
+ inset-inline-start: 0;
87
+ border-inline-start: 2px solid var(--state-selected);
88
+ }
89
+
90
+ /* The content region is the one that flexes, whichever position it holds. */
59
91
  .list-item-content {
92
+ flex: 1 1 0;
60
93
  min-inline-size: 0;
61
94
  }
62
95
 
@@ -64,16 +97,19 @@ a.list-item:hover {
64
97
  .list-item-text {
65
98
  display: block;
66
99
  margin: 0;
100
+ overflow-wrap: anywhere;
67
101
  }
68
102
 
69
103
  .list-item-text {
70
104
  color: var(--text-muted);
71
105
  font-size: var(--font-size-sm);
72
- overflow-wrap: anywhere;
73
106
  }
74
107
 
108
+ /* The auto margin keeps the trailing region at the row's end even in a row
109
+ with no content region to absorb the free space. */
75
110
  .list-item-trailing {
76
- justify-self: end;
111
+ flex: none;
112
+ margin-inline-start: auto;
77
113
  min-inline-size: 0;
78
114
  }
79
115
 
@@ -44,6 +44,7 @@
44
44
  background: transparent;
45
45
  color: var(--intent, var(--text));
46
46
  font: inherit;
47
+ font-size: var(--control-font-size);
47
48
  text-align: start;
48
49
  text-decoration: none;
49
50
  white-space: nowrap;
@@ -75,30 +76,36 @@
75
76
  /* Checkable menu items are one command with one accessible state. Their
76
77
  indicator is generated so authors do not add a second interactive input. */
77
78
  .menu-item:is([role="menuitemcheckbox"], [role="menuitemradio"])::before {
78
- display: grid;
79
+ content: "";
80
+ display: block;
79
81
  flex: 0 0 var(--menu-item-icon-size);
80
82
  inline-size: var(--menu-item-icon-size);
81
- place-items: center;
82
- }
83
-
84
- .menu-item[role="menuitemcheckbox"]::before {
85
- content: "";
83
+ block-size: var(--menu-item-icon-size);
86
84
  }
87
85
 
88
86
  .menu-item[role="menuitemcheckbox"][aria-checked="true"]::before {
89
- content: "✓";
87
+ background: currentColor;
88
+ -webkit-mask: var(--icon-check) center / contain no-repeat;
89
+ mask: var(--icon-check) center / contain no-repeat;
90
90
  }
91
91
 
92
92
  .menu-item[role="menuitemcheckbox"][aria-checked="mixed"]::before {
93
- content: "−";
93
+ background:
94
+ linear-gradient(currentColor 0 0) center / 60% max(var(--border-width), 0.125em) no-repeat;
94
95
  }
95
96
 
96
97
  .menu-item[role="menuitemradio"]::before {
97
- content: "○";
98
+ background: radial-gradient(circle, transparent 0 46%, currentColor 48% 58%, transparent 60%);
98
99
  }
99
100
 
100
101
  .menu-item[role="menuitemradio"][aria-checked="true"]::before {
101
- content: "●";
102
+ background: radial-gradient(
103
+ circle,
104
+ currentColor 0 25%,
105
+ transparent 27% 46%,
106
+ currentColor 48% 58%,
107
+ transparent 60%
108
+ );
102
109
  }
103
110
 
104
111
  .menu-item:focus-visible {
@@ -118,7 +125,7 @@
118
125
  padding: var(--space-20);
119
126
  color: var(--text-muted);
120
127
  font-size: var(--font-size-xs);
121
- font-weight: var(--font-weight-strong);
128
+ font-weight: var(--font-weight-semibold);
122
129
  }
123
130
 
124
131
  .menu-separator {