actual-css 0.1.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 (129) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/dist/actual.css +4929 -0
  5. package/dist/actual.js +3 -0
  6. package/dist/actual.js.map +31 -0
  7. package/dist/actual.min.css +1 -0
  8. package/docs/README.md +26 -0
  9. package/docs/components.md +860 -0
  10. package/docs/design-notes/accessibility-decisions.md +32 -0
  11. package/docs/design-notes/browser-support.md +93 -0
  12. package/docs/design-notes/cascade-layer.md +52 -0
  13. package/docs/design-notes/css-anchor.md +33 -0
  14. package/docs/design-notes/naming.md +37 -0
  15. package/docs/design-notes/previous-work.md +5 -0
  16. package/docs/design-notes/references.md +324 -0
  17. package/docs/forms.md +1265 -0
  18. package/docs/javascript.md +426 -0
  19. package/docs/layout.md +480 -0
  20. package/docs/mental-model.md +73 -0
  21. package/docs/patterns.md +415 -0
  22. package/docs/reserved.md +7 -0
  23. package/docs/tokens.md +455 -0
  24. package/docs/typography.md +159 -0
  25. package/docs/ui.md +937 -0
  26. package/docs/utilities.md +384 -0
  27. package/llms.txt +17 -0
  28. package/package.json +101 -0
  29. package/src/css/actual.css +17 -0
  30. package/src/css/actual.full.css +2 -0
  31. package/src/css/actual.layer.css +3 -0
  32. package/src/css/base.css +70 -0
  33. package/src/css/components/accordion.css +97 -0
  34. package/src/css/components/actions.css +21 -0
  35. package/src/css/components/alert.css +137 -0
  36. package/src/css/components/avatar.css +199 -0
  37. package/src/css/components/badge.css +143 -0
  38. package/src/css/components/breadcrumb.css +56 -0
  39. package/src/css/components/busy.css +44 -0
  40. package/src/css/components/button.css +216 -0
  41. package/src/css/components/card.css +140 -0
  42. package/src/css/components/drawer.css +122 -0
  43. package/src/css/components/flyout.css +243 -0
  44. package/src/css/components/index.css +41 -0
  45. package/src/css/components/join.css +106 -0
  46. package/src/css/components/key.css +25 -0
  47. package/src/css/components/meter.css +81 -0
  48. package/src/css/components/modal.css +162 -0
  49. package/src/css/components/navbar.css +98 -0
  50. package/src/css/components/overline.css +49 -0
  51. package/src/css/components/pagination.css +30 -0
  52. package/src/css/components/progress.css +126 -0
  53. package/src/css/components/scrollspy.css +27 -0
  54. package/src/css/components/skeleton.css +67 -0
  55. package/src/css/components/spinner.css +60 -0
  56. package/src/css/components/status-bar.css +82 -0
  57. package/src/css/components/surface.css +48 -0
  58. package/src/css/components/tab.css +128 -0
  59. package/src/css/components/table.css +64 -0
  60. package/src/css/components/tooltip.css +107 -0
  61. package/src/css/dialog-fallback.css +56 -0
  62. package/src/css/focus.css +13 -0
  63. package/src/css/forms/choice-card.css +158 -0
  64. package/src/css/forms/choice.css +196 -0
  65. package/src/css/forms/control.css +106 -0
  66. package/src/css/forms/custom-select.css +93 -0
  67. package/src/css/forms/form-actions.css +43 -0
  68. package/src/css/forms/form.css +57 -0
  69. package/src/css/forms/index.css +15 -0
  70. package/src/css/forms/input-icon.css +36 -0
  71. package/src/css/forms/native.css +68 -0
  72. package/src/css/forms/select.css +36 -0
  73. package/src/css/forms/switch.css +127 -0
  74. package/src/css/forms/validation.css +63 -0
  75. package/src/css/grid.css +95 -0
  76. package/src/css/intents.css +33 -0
  77. package/src/css/layout.css +80 -0
  78. package/src/css/optional/index.css +5 -0
  79. package/src/css/optional/layout-extra.css +55 -0
  80. package/src/css/optional/scroll-snap.css +51 -0
  81. package/src/css/optional/scroller.css +51 -0
  82. package/src/css/optional/typography-fluid.css +48 -0
  83. package/src/css/optional/utilities-extra.css +289 -0
  84. package/src/css/print.css +47 -0
  85. package/src/css/prose.css +187 -0
  86. package/src/css/reset.css +56 -0
  87. package/src/css/theme.css +186 -0
  88. package/src/css/themes/corporate.css +60 -0
  89. package/src/css/themes/cyberpunk.css +60 -0
  90. package/src/css/themes/dark.css +32 -0
  91. package/src/css/themes/dim.css +33 -0
  92. package/src/css/themes/edge.css +143 -0
  93. package/src/css/themes/forest.css +65 -0
  94. package/src/css/themes/gradient.css +97 -0
  95. package/src/css/themes/index.css +43 -0
  96. package/src/css/themes/lavender.css +66 -0
  97. package/src/css/themes/mono.css +60 -0
  98. package/src/css/themes/neon.css +66 -0
  99. package/src/css/themes/ocean.css +60 -0
  100. package/src/css/themes/petrol.css +64 -0
  101. package/src/css/themes/spruce.css +64 -0
  102. package/src/css/themes/square.css +68 -0
  103. package/src/css/themes/sunset.css +66 -0
  104. package/src/css/tokens.css +122 -0
  105. package/src/css/utilities.css +162 -0
  106. package/src/css/variants.css +118 -0
  107. package/src/js/command.js +165 -0
  108. package/src/js/context-menu.js +291 -0
  109. package/src/js/dialog-fallback.js +163 -0
  110. package/src/js/dialog.js +503 -0
  111. package/src/js/enhance.js +208 -0
  112. package/src/js/events.js +22 -0
  113. package/src/js/filter.js +122 -0
  114. package/src/js/floating.js +462 -0
  115. package/src/js/flyout.js +187 -0
  116. package/src/js/focus.js +35 -0
  117. package/src/js/index.js +26 -0
  118. package/src/js/input.js +47 -0
  119. package/src/js/keys.js +30 -0
  120. package/src/js/mask.js +184 -0
  121. package/src/js/menu.js +88 -0
  122. package/src/js/password.js +98 -0
  123. package/src/js/scrollspy.js +121 -0
  124. package/src/js/selectors.js +59 -0
  125. package/src/js/status.js +158 -0
  126. package/src/js/surface.js +358 -0
  127. package/src/js/tab.js +173 -0
  128. package/src/js/tooltip.js +374 -0
  129. package/src/js/validation.js +419 -0
@@ -0,0 +1,216 @@
1
+ /*
2
+ * Buttons for actions and button-like links.
3
+ *
4
+ * .btn is primarily for actionable elements: <button>, <a href>,
5
+ * input[type="button"|"submit"|"reset"].
6
+ *
7
+ * Keeps shared intent, variant, and size behavior with a solid default.
8
+ *
9
+ * Disabled state uses the native disabled attribute for buttons and inputs.
10
+ * Anchors have no native disabled state, so a.btn uses aria-disabled="true"
11
+ * for disabled styling and activation guards.
12
+ */
13
+
14
+ /* Intent boundary — inherited intents do not tint this component.
15
+ Local intent classes still win because :where() has zero specificity. */
16
+ :where(.btn) {
17
+ /* @sync intent-boundary */
18
+ --intent: initial;
19
+ --intent-fg: initial;
20
+ --ui-bg: initial;
21
+ --ui-fg: initial;
22
+ --ui-border: initial;
23
+ --ui-hover-bg: initial;
24
+ /* @sync intent-boundary */
25
+ }
26
+
27
+ .btn {
28
+ --btn-font-size: var(--control-font-size);
29
+ --btn-min-size: var(--control-size);
30
+ --btn-pad-x: var(--control-pad-x, 1em);
31
+ --btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
32
+ --btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
33
+ --btn-border: var(--ui-border, var(--btn-default-border, transparent));
34
+ --btn-radius: var(--radius);
35
+ --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
+ --btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
39
+
40
+ position: relative;
41
+ display: inline-flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ gap: 0.5em;
45
+ 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));
48
+ padding-inline: var(--btn-pad-x);
49
+ border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
50
+ border-block-end-color: var(
51
+ --btn-border-block-end-color,
52
+ var(--btn-border-color, var(--btn-border))
53
+ );
54
+ border-block-end-width: var(--btn-border-block-end-width, var(--border-width));
55
+ border-radius: var(--btn-radius);
56
+ background: var(--btn-bg);
57
+ color: var(--btn-fg);
58
+ font: inherit;
59
+ font-size: var(--btn-font-size);
60
+ font-weight: var(--font-weight-strong);
61
+ line-height: 1;
62
+ text-decoration: none;
63
+ cursor: pointer;
64
+ transition:
65
+ background-color var(--duration),
66
+ border-color var(--duration),
67
+ color var(--duration),
68
+ box-shadow var(--duration),
69
+ transform var(--duration);
70
+ }
71
+
72
+ @supports (color: color-mix(in oklch, red, white)) {
73
+ .btn {
74
+ --btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
75
+ }
76
+ }
77
+
78
+ /* .solid/.soft/.outline are shared via variants.css. The baseline --btn-*
79
+ fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
80
+ .btn.ghost {
81
+ --ui-bg: transparent;
82
+ --ui-fg: var(--intent, var(--text));
83
+ --ui-border: transparent;
84
+ }
85
+
86
+ /* Solid/soft get the overlay darkening on hover; outline/ghost/link stay flat.
87
+ Routed through a variable instead of a :not(.outline, .ghost, .link)
88
+ exclusion so the hover rule below only has to state the interactive
89
+ element list once. */
90
+ .btn.outline,
91
+ .btn.ghost,
92
+ .btn.link {
93
+ --btn-hover-overlay: transparent;
94
+ }
95
+
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);
102
+ }
103
+ }
104
+
105
+ .btn:not(:where(:disabled, [aria-disabled="true"])):active {
106
+ transform: translateY(1px);
107
+ }
108
+
109
+ .btn.link {
110
+ --ui-bg: transparent;
111
+ --ui-fg: var(--intent, var(--text));
112
+ --ui-border: transparent;
113
+ --btn-radius: var(--radius-sm);
114
+ --btn-border-block-end-color: transparent;
115
+ --btn-border-block-end-width: var(--border-width);
116
+
117
+ min-block-size: auto;
118
+ padding-inline: 0;
119
+ border-color: transparent;
120
+ text-decoration: underline;
121
+ text-underline-offset: 0.18em;
122
+ }
123
+
124
+ /* .btn.circle needs no local rule: the .circle utility (utilities.css) loads
125
+ after components and overrides border-radius at equal specificity. */
126
+
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
+ }
133
+ }
134
+
135
+ .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));
139
+ }
140
+
141
+ .btn.link:focus-visible {
142
+ outline-color: currentColor;
143
+ outline-offset: 0.2em;
144
+ }
145
+
146
+ .btn:disabled {
147
+ cursor: not-allowed;
148
+ opacity: var(--disabled-opacity);
149
+ }
150
+
151
+ /* Anchors have no native disabled state.
152
+ a.btn[aria-disabled="true"] only provides disabled styling and blocks pointer
153
+ activation. For a truly disabled link, remove href or prevent activation in
154
+ app code. */
155
+ a.btn[aria-disabled="true"] {
156
+ cursor: not-allowed;
157
+ opacity: var(--disabled-opacity);
158
+ pointer-events: none;
159
+ }
160
+
161
+ /* Busy */
162
+ .btn[aria-busy="true"]:not(:disabled) {
163
+ cursor: progress;
164
+ }
165
+
166
+ .btn[aria-busy="true"] > .spinner {
167
+ --spinner-size: 1em;
168
+ --spinner-width: 0.15em;
169
+ }
170
+
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));
178
+ --ui-hover-bg: var(--btn-bg);
179
+ }
180
+
181
+ @media (forced-colors: active) {
182
+ .btn {
183
+ border-color: ButtonText;
184
+ background: ButtonFace;
185
+ color: ButtonText;
186
+ box-shadow: none;
187
+ }
188
+
189
+ .btn.link {
190
+ border-color: transparent;
191
+ background: Canvas;
192
+ color: LinkText;
193
+ }
194
+
195
+ .btn:is([aria-pressed="true"], [aria-current]:not([aria-current="false"])):not(.link) {
196
+ border-color: Highlight;
197
+ background: Highlight;
198
+ color: HighlightText;
199
+ forced-color-adjust: none;
200
+ }
201
+
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
+ }
207
+ }
208
+
209
+ .btn:disabled,
210
+ a.btn[aria-disabled="true"] {
211
+ border-color: GrayText;
212
+ background: Canvas;
213
+ color: GrayText;
214
+ forced-color-adjust: auto;
215
+ }
216
+ }
@@ -0,0 +1,140 @@
1
+ /*
2
+ * Cards provide a neutral raised surface for grouped content.
3
+ *
4
+ * Surface, border, radius, padding, density, and elevation stay local here.
5
+ * Direct header/footer children are structural slots. Page layout remains the
6
+ * job of markup and layout primitives outside the card.
7
+ */
8
+ /* Intent boundary — inherited intents do not tint this component.
9
+ Local intent classes still win because :where() has zero specificity. */
10
+ :where(.card) {
11
+ /* @sync intent-boundary */
12
+ --intent: initial;
13
+ --intent-fg: initial;
14
+ --ui-bg: initial;
15
+ --ui-fg: initial;
16
+ --ui-border: initial;
17
+ --ui-hover-bg: initial;
18
+ /* @sync intent-boundary */
19
+ }
20
+
21
+ .card {
22
+ --card-pad: var(--space-4);
23
+ --card-radius: var(--radius-lg);
24
+ --card-max-inline-size: none;
25
+
26
+ max-inline-size: var(--card-max-inline-size);
27
+ padding: var(--card-pad);
28
+ background: var(--ui-bg, var(--surface));
29
+ color: var(--ui-fg, var(--text));
30
+ border: var(--border-width) solid var(--ui-border, var(--border));
31
+ border-radius: var(--card-radius);
32
+ }
33
+
34
+ .card > * {
35
+ min-inline-size: 0;
36
+ }
37
+
38
+ .card > :where(header, footer) {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ align-items: var(--items-align, center);
42
+ justify-content: space-between;
43
+ gap: var(--space-3);
44
+ min-inline-size: 0;
45
+ }
46
+
47
+ .card > :where(header, footer) > * {
48
+ min-inline-size: 0;
49
+ margin-block: 0;
50
+ }
51
+
52
+ .card > header {
53
+ align-items: var(--items-align, flex-start);
54
+ }
55
+
56
+ .card > :first-child {
57
+ margin-block-start: 0;
58
+ }
59
+
60
+ .card > :last-child {
61
+ margin-block-end: 0;
62
+ }
63
+
64
+ .card.raised {
65
+ box-shadow: var(--shadow);
66
+ }
67
+ .card.subtle {
68
+ background: var(--surface-subtle);
69
+ }
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
+ .card.compact {
79
+ --card-pad: var(--space-2);
80
+ }
81
+
82
+ /* Full-bleed children */
83
+ .card > .bleed {
84
+ margin-inline: calc(var(--card-pad) * -1);
85
+ max-inline-size: none;
86
+ }
87
+
88
+ .card > .bleed:first-child {
89
+ margin-block-start: calc(var(--card-pad) * -1);
90
+ border-start-start-radius: inherit;
91
+ border-start-end-radius: inherit;
92
+ }
93
+
94
+ .card > .bleed:last-child {
95
+ margin-block-end: calc(var(--card-pad) * -1);
96
+ border-end-start-radius: inherit;
97
+ border-end-end-radius: inherit;
98
+ }
99
+
100
+ /* Media bleed */
101
+ .card > :where(img, picture, video, canvas, svg).bleed {
102
+ display: block;
103
+ inline-size: calc(100% + var(--card-pad) * 2);
104
+ max-inline-size: none;
105
+ }
106
+
107
+ .card > picture.bleed {
108
+ overflow: hidden;
109
+ }
110
+
111
+ .card > picture.bleed > img {
112
+ display: block;
113
+ inline-size: 100%;
114
+ block-size: auto;
115
+ }
116
+
117
+ /* Padded structural bleed */
118
+ .card > :where(header, footer, figure).bleed {
119
+ padding: var(--card-pad);
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
+ }
@@ -0,0 +1,122 @@
1
+ /*
2
+ * Drawer — modal side-sheet built on the native dialog element.
3
+ *
4
+ * Top-layer rendering, ::backdrop, Escape, and modal focus come from the
5
+ * native dialog. CSS owns progressive enter/exit motion where browsers support
6
+ * discrete display/overlay transitions. The drawer is a layout shell only —
7
+ * navigation content inside uses .nav-link and .accordion, which carry their
8
+ * own link/summary styling.
9
+ */
10
+ dialog.drawer {
11
+ --drawer-size: 20rem;
12
+ --drawer-pad: var(--space-4);
13
+ --drawer-closed-translate: -100%;
14
+
15
+ inline-size: min(calc(var(--viewport-inline) * 0.9), var(--drawer-size));
16
+ block-size: var(--viewport-block);
17
+ max-inline-size: none;
18
+ max-block-size: var(--viewport-block);
19
+ margin: 0;
20
+ padding: var(--drawer-pad);
21
+ border: 0;
22
+ border-inline-end: var(--border-width) solid var(--border);
23
+ border-radius: 0;
24
+ background: var(--surface-raised);
25
+ color: var(--text);
26
+ box-shadow: var(--shadow);
27
+ opacity: 0;
28
+ transform: translateX(var(--drawer-closed-translate));
29
+ transition:
30
+ opacity var(--duration-slow),
31
+ transform var(--duration-slow);
32
+ }
33
+
34
+ dialog.drawer[open] {
35
+ display: grid;
36
+ grid-template-rows: auto minmax(0, 1fr) auto;
37
+ gap: var(--space-4);
38
+ opacity: 1;
39
+ transform: translateX(0);
40
+ }
41
+
42
+ /* Backdrop color sourced from components/surface.css so it matches .modal
43
+ and .surface-backdrop across the dialog family. */
44
+ dialog.drawer::backdrop {
45
+ background: var(--backdrop-fill);
46
+ opacity: 0;
47
+ transition: opacity var(--duration-slow);
48
+ }
49
+
50
+ dialog.drawer[data-side="end"] {
51
+ --drawer-closed-translate: 100%;
52
+
53
+ margin-inline-start: auto;
54
+ margin-inline-end: 0;
55
+ border-inline-end: 0;
56
+ border-inline-start: var(--border-width) solid var(--border);
57
+ }
58
+
59
+ dialog.drawer[open]::backdrop {
60
+ opacity: 1;
61
+ }
62
+
63
+ @starting-style {
64
+ dialog.drawer[open] {
65
+ opacity: 0;
66
+ transform: translateX(var(--drawer-closed-translate));
67
+ }
68
+
69
+ dialog.drawer[open]::backdrop {
70
+ opacity: 0;
71
+ }
72
+ }
73
+
74
+ /* Scrollable navigation region inside the drawer. */
75
+ dialog.drawer nav {
76
+ min-block-size: 0;
77
+ overflow-y: auto;
78
+ }
79
+
80
+ @supports (transition-behavior: allow-discrete) {
81
+ dialog.drawer {
82
+ transition:
83
+ opacity var(--duration-slow),
84
+ transform var(--duration-slow),
85
+ overlay var(--duration-slow) allow-discrete,
86
+ display var(--duration-slow) allow-discrete;
87
+ }
88
+
89
+ dialog.drawer::backdrop {
90
+ transition:
91
+ opacity var(--duration-slow),
92
+ overlay var(--duration-slow) allow-discrete,
93
+ display var(--duration-slow) allow-discrete;
94
+ }
95
+ }
96
+
97
+ /* RTL: swap closed transform directions so the drawer slides from the correct
98
+ inline side. */
99
+ [dir="rtl"] dialog.drawer {
100
+ --drawer-closed-translate: 100%;
101
+ }
102
+
103
+ [dir="rtl"] dialog.drawer[data-side="end"] {
104
+ --drawer-closed-translate: -100%;
105
+ }
106
+
107
+ @media (forced-colors: active) {
108
+ dialog.drawer {
109
+ border-inline-end-color: ButtonText;
110
+ background: Canvas;
111
+ color: CanvasText;
112
+ box-shadow: none;
113
+ }
114
+
115
+ dialog.drawer[data-side="end"] {
116
+ border-inline-start-color: ButtonText;
117
+ }
118
+
119
+ dialog.drawer::backdrop {
120
+ background: Canvas;
121
+ }
122
+ }
@@ -0,0 +1,243 @@
1
+ /*
2
+ * Flyout — floating panel for actions and links.
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.
7
+ *
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"].
12
+ * --flyout-trigger-width is set by surface.js while an open flyout is positioned.
13
+ */
14
+
15
+ .flyout-trigger {
16
+ position: relative;
17
+ display: inline-block;
18
+ }
19
+
20
+ /* Stretch: for a flyout trigger that must span its container — e.g. the
21
+ last row of a full-width sidebar nav list. Stretches both the
22
+ positioning wrapper and its trigger control. Named for the effect
23
+ (stretch to fill), not the display value, to avoid reading like a
24
+ generic display-block utility. */
25
+ .flyout-trigger.stretch {
26
+ display: block;
27
+ inline-size: 100%;
28
+ }
29
+
30
+ .flyout-trigger.stretch > [aria-haspopup="menu"] {
31
+ inline-size: 100%;
32
+ }
33
+
34
+ .flyout {
35
+ z-index: var(--z-menu);
36
+ /* No-JS fallback: below the trigger inside the relative .flyout-trigger.
37
+ surface.js switches an open flyout to position: fixed with measured
38
+ coordinates; this only covers a flyout revealed without the runtime. */
39
+ position: absolute;
40
+ inset-block-start: calc(100% + var(--space-1));
41
+ inset-inline-start: 0;
42
+ display: grid;
43
+ gap: var(--space-1);
44
+ inline-size: var(--flyout-inline-size, max-content);
45
+ 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)))
48
+ );
49
+ max-inline-size: min(
50
+ var(--flyout-max-inline-size, 20rem),
51
+ calc(var(--viewport-inline) - var(--space-6))
52
+ );
53
+ max-block-size: min(24rem, var(--available-height, calc(var(--viewport-block) - var(--space-6))));
54
+ overflow: auto;
55
+ margin: 0;
56
+ padding: var(--space-1);
57
+ border: var(--border-width) solid var(--border);
58
+ border-radius: var(--radius);
59
+ background: var(--surface-raised);
60
+ box-shadow: var(--shadow-popout);
61
+ list-style: none;
62
+ opacity: 1;
63
+ scale: 1;
64
+ 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);
68
+ }
69
+
70
+ .flyout.is-open:not(.is-sheet) {
71
+ inset: auto;
72
+ }
73
+
74
+ @starting-style {
75
+ .flyout.is-open {
76
+ opacity: 0;
77
+ scale: 0.98;
78
+ }
79
+ }
80
+
81
+ /* Bottom-sheet presentation. surface.js toggles .is-sheet when the flyout is
82
+ opened on coarse pointer / narrow viewports. */
83
+ .flyout.is-sheet {
84
+ position: fixed;
85
+ inset-inline: 0;
86
+ inset-block-start: auto;
87
+ inset-block-end: 0;
88
+ inline-size: auto;
89
+ min-inline-size: 0;
90
+ max-inline-size: none;
91
+ max-block-size: min(calc(var(--viewport-block) * 0.75), 32rem);
92
+ overflow: auto;
93
+ 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));
96
+ border-end-start-radius: 0;
97
+ border-end-end-radius: 0;
98
+ }
99
+
100
+ .flyout[hidden] {
101
+ display: none;
102
+ opacity: 0;
103
+ scale: 0.98;
104
+ }
105
+
106
+ .flyout.is-sheet[hidden] {
107
+ scale: 1;
108
+ translate: 0 var(--space-3);
109
+ }
110
+
111
+ @starting-style {
112
+ .flyout.is-sheet.is-open {
113
+ scale: 1;
114
+ translate: 0 var(--space-3);
115
+ }
116
+ }
117
+
118
+ @supports (transition-behavior: allow-discrete) {
119
+ .flyout {
120
+ 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;
125
+ }
126
+ }
127
+
128
+ .flyout > li {
129
+ margin: 0;
130
+ }
131
+
132
+ .flyout > :where(section, footer) {
133
+ min-inline-size: 0;
134
+ padding: var(--space-2);
135
+ }
136
+
137
+ .flyout > section > :where(h1, h2, h3, h4, h5, h6) {
138
+ margin: 0;
139
+ color: var(--text-muted);
140
+ font-size: var(--font-size-xs);
141
+ font-weight: var(--font-weight-strong);
142
+ }
143
+
144
+ .flyout > section > ul {
145
+ display: grid;
146
+ gap: var(--space-1);
147
+ margin: var(--space-1) 0 0;
148
+ padding: 0;
149
+ list-style: none;
150
+ }
151
+
152
+ .flyout > section > ul > li {
153
+ margin: 0;
154
+ }
155
+
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
+ }