actual-css 0.2.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.
- package/CHANGELOG.md +38 -1
- package/README.md +24 -9
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1244 -915
- package/dist/actual.js +1 -3
- package/dist/actual.min.css +1 -1
- package/dist/optional.css +1048 -0
- package/dist/optional.min.css +1 -0
- package/docs/design-notes/browser-support.md +94 -14
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +11 -14
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +2 -1
- package/docs/design-notes/platform-alignment.md +492 -0
- package/docs/design-notes/references.md +8 -0
- package/docs/design-notes/widget-primitives.md +37 -1
- package/docs/navigation.json +135 -0
- package/docs/pages/components/accordion.md +80 -0
- package/docs/pages/components/alert.md +203 -0
- package/docs/pages/components/aura.md +35 -0
- package/docs/pages/components/avatar.md +95 -0
- package/docs/pages/components/badge.md +102 -0
- package/docs/pages/components/breadcrumb.md +19 -0
- package/docs/pages/components/button.md +205 -0
- package/docs/pages/components/card.md +183 -0
- package/docs/pages/components/chat.md +50 -0
- package/docs/pages/components/dialog.md +367 -0
- package/docs/pages/components/drawer.md +113 -0
- package/docs/pages/components/fab.md +118 -0
- package/docs/pages/components/key.md +33 -0
- package/docs/pages/components/meter.md +23 -0
- package/docs/pages/components/navbar.md +92 -0
- package/docs/pages/components/pagination.md +55 -0
- package/docs/pages/components/progress.md +29 -0
- package/docs/pages/components/skeleton.md +38 -0
- package/docs/pages/components/spinner.md +93 -0
- package/docs/pages/components/table.md +102 -0
- package/docs/pages/enhancements/flyout.md +391 -0
- package/docs/pages/enhancements/overview.md +36 -0
- package/docs/pages/enhancements/scrollspy.md +71 -0
- package/docs/pages/enhancements/status-bar.md +72 -0
- package/docs/pages/enhancements/tabs.md +80 -0
- package/docs/pages/enhancements/tooltip.md +130 -0
- package/docs/pages/examples/overview.md +41 -0
- package/docs/pages/forms/choice-cards.md +102 -0
- package/docs/pages/forms/controls.md +32 -0
- package/docs/pages/forms/detached-actions.md +59 -0
- package/docs/pages/forms/floating-field.md +88 -0
- package/docs/pages/forms/form-actions.md +56 -0
- package/docs/pages/forms/input-policy.md +91 -0
- package/docs/pages/forms/joined-controls.md +37 -0
- package/docs/pages/forms/labels.md +42 -0
- package/docs/pages/forms/otp.md +50 -0
- package/docs/pages/forms/overview.md +188 -0
- package/docs/pages/forms/password.md +40 -0
- package/docs/pages/forms/range.md +17 -0
- package/docs/pages/forms/states-and-sizes.md +321 -0
- package/docs/pages/forms/sticky-actions.md +187 -0
- package/docs/pages/forms/switch.md +31 -0
- package/docs/pages/forms/validation.md +241 -0
- package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
- package/docs/pages/foundations/theming.md +92 -0
- package/docs/pages/foundations/tokens.md +421 -0
- package/docs/{typography.md → pages/foundations/typography.md} +27 -9
- package/docs/pages/guides/bootstrap.md +438 -0
- package/docs/pages/guides/introduction.md +157 -0
- package/docs/pages/guides/optional-components.md +33 -0
- package/docs/pages/guides/progressive-enhancement.md +686 -0
- package/docs/pages/guides/tailwind.md +455 -0
- package/docs/pages/layout/app-shell.md +25 -0
- package/docs/pages/layout/center.md +29 -0
- package/docs/pages/layout/cluster.md +57 -0
- package/docs/pages/layout/frame.md +30 -0
- package/docs/pages/layout/grid.md +142 -0
- package/docs/pages/layout/list.md +41 -0
- package/docs/pages/layout/media.md +21 -0
- package/docs/pages/layout/overview.md +37 -0
- package/docs/pages/layout/scroll-snap.md +54 -0
- package/docs/pages/layout/sidebar-layout.md +27 -0
- package/docs/pages/layout/stack.md +18 -0
- package/docs/pages/layout/switcher.md +36 -0
- package/docs/pages/layout/topbar.md +23 -0
- package/docs/pages/patterns/actions.md +52 -0
- package/docs/pages/patterns/code-block.md +36 -0
- package/docs/pages/patterns/exclusive-accordion.md +16 -0
- package/docs/pages/patterns/hierarchical-nav.md +26 -0
- package/docs/pages/patterns/icon-input.md +25 -0
- package/docs/pages/patterns/intent-nesting.md +3 -0
- package/docs/pages/patterns/navigation-list.md +235 -0
- package/docs/pages/patterns/overview.md +58 -0
- package/docs/pages/patterns/structured-lists.md +47 -0
- package/docs/pages/utilities/accessibility.md +34 -0
- package/docs/pages/utilities/flex.md +105 -0
- package/docs/pages/utilities/links.md +18 -0
- package/docs/pages/utilities/list.md +31 -0
- package/docs/pages/utilities/overflow.md +43 -0
- package/docs/pages/utilities/overview.md +18 -0
- package/docs/pages/utilities/shape.md +25 -0
- package/docs/pages/utilities/sizing-wrapping.md +115 -0
- package/docs/pages/utilities/spacing.md +79 -0
- package/docs/pages/utilities/text.md +43 -0
- package/llms.txt +4 -4
- package/package.json +20 -12
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -5
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +126 -99
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +55 -35
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +6 -0
- package/src/css/components/button.css +78 -32
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -109
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -2
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +125 -0
- package/src/css/components/meter.css +0 -23
- package/src/css/components/modal.css +162 -7
- package/src/css/components/navbar.css +17 -18
- package/src/css/components/overline.css +0 -7
- package/src/css/components/pagination.css +1 -1
- package/src/css/components/progress.css +8 -26
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +6 -7
- package/src/css/components/status-bar.css +15 -24
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +19 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +14 -38
- package/src/css/forms/choice-card.css +13 -14
- package/src/css/forms/choice.css +21 -27
- package/src/css/forms/control.css +16 -17
- package/src/css/forms/custom-select.css +9 -11
- package/src/css/forms/form-actions.css +14 -3
- package/src/css/forms/form.css +7 -4
- package/src/css/forms/index.css +4 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +50 -11
- package/src/css/forms/range.css +17 -9
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -12
- package/src/css/forms/validation.css +32 -5
- package/src/css/grid.css +19 -18
- package/src/css/intents.css +2 -2
- package/src/css/layout.css +29 -1
- package/src/css/optional/aura.css +93 -0
- package/src/css/optional/chat.css +105 -0
- package/src/css/optional/fab.css +104 -0
- package/src/css/optional/floating-field.css +87 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +7 -4
- package/src/css/optional/otp.css +115 -0
- package/src/css/optional/scroll-snap.css +6 -0
- package/src/css/optional/scroller.css +13 -7
- package/src/css/optional/typography-fluid.css +5 -0
- package/src/css/optional/utilities-extra.css +17 -2
- package/src/css/prose.css +18 -8
- package/src/css/reset.css +0 -10
- package/src/css/theme.css +21 -5
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +8 -10
- package/src/css/themes/index.css +3 -3
- package/src/css/themes/{dark.css → indigo.css} +5 -4
- package/src/css/themes/neon.css +5 -5
- package/src/css/themes/square.css +0 -3
- package/src/css/tokens.css +74 -20
- package/src/css/utilities.css +8 -10
- package/src/css/variants.css +37 -65
- package/src/js/command.js +1 -3
- package/src/js/context-menu.js +67 -35
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +54 -23
- package/src/js/enhancement-loader.js +301 -0
- package/src/js/events.js +3 -0
- package/src/js/filter.js +3 -3
- package/src/js/floating.js +101 -131
- package/src/js/flyout.js +104 -87
- package/src/js/focus-group.js +116 -0
- package/src/js/focus.js +6 -1
- package/src/js/index.js +4 -2
- package/src/js/input.js +1 -1
- package/src/js/keys.js +27 -0
- package/src/js/mask.js +7 -6
- package/src/js/menu.js +87 -34
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +12 -4
- package/src/js/selectors.js +0 -7
- package/src/js/status.js +3 -1
- package/src/js/surface.js +185 -136
- package/src/js/tab.js +36 -53
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +34 -8
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -28
- package/docs/components.md +0 -860
- package/docs/design-notes/previous-work.md +0 -5
- package/docs/forms.md +0 -1265
- package/docs/javascript.md +0 -496
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -504
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -929
- package/docs/utilities.md +0 -384
- package/src/css/dialog-fallback.css +0 -56
- package/src/js/dialog-fallback.js +0 -163
package/dist/actual.css
CHANGED
|
@@ -24,16 +24,6 @@ select {
|
|
|
24
24
|
display: none !important;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/* Legacy dialog fallback only. Modern dialogs keep the browser's native closed
|
|
28
|
-
state so display/overlay transitions are not flattened by the reset.
|
|
29
|
-
The presentation for the dialog-fallback.js shim lives in the separate
|
|
30
|
-
dialog-fallback.css, so it can be dropped from custom bundles. */
|
|
31
|
-
@supports not (selector(dialog:modal)) {
|
|
32
|
-
dialog:not([open]) {
|
|
33
|
-
display: none;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
27
|
:where(img, svg, video) {
|
|
38
28
|
max-inline-size: 100%;
|
|
39
29
|
block-size: auto;
|
|
@@ -55,64 +45,24 @@ select {
|
|
|
55
45
|
}
|
|
56
46
|
}
|
|
57
47
|
|
|
58
|
-
/*
|
|
59
|
-
* Legacy dialog fallback — presentation for the dialog-fallback.js shim
|
|
60
|
-
* (browsers without native <dialog>: Safari <= 15.3, Firefox <= 97).
|
|
61
|
-
*
|
|
62
|
-
* Isolated in its own file so a custom bundle can opt out by dropping this
|
|
63
|
-
* import; the JS side is disabled with data-dialog-fallback="false" on the
|
|
64
|
-
* root element or on a dialog. Must load before component styles so ties
|
|
65
|
-
* such as dialog.modal's padding resolve in the component's favor.
|
|
66
|
-
*
|
|
67
|
-
* The whole file is inert on modern browsers: rules key on the
|
|
68
|
-
* .dialog-fallback marker written by the shim, so browsers that support
|
|
69
|
-
* <dialog> but not :modal are never affected either.
|
|
70
|
-
*/
|
|
71
|
-
@supports not (selector(dialog:modal)) {
|
|
72
|
-
/* Browsers without native <dialog> ship no UA dialog styles at all;
|
|
73
|
-
approximate them. :where() keeps specificity at dialog[open] level so
|
|
74
|
-
component styles such as dialog.modal still override the emulation. */
|
|
75
|
-
dialog[open]:where(.dialog-fallback) {
|
|
76
|
-
position: absolute;
|
|
77
|
-
inset-inline: 0;
|
|
78
|
-
width: -moz-fit-content;
|
|
79
|
-
width: fit-content;
|
|
80
|
-
max-inline-size: calc(100% - 2rem);
|
|
81
|
-
margin: auto;
|
|
82
|
-
padding: var(--space-4, 1em);
|
|
83
|
-
border: var(--border-width, 1px) solid var(--border, currentColor);
|
|
84
|
-
background: var(--surface-raised, #fff);
|
|
85
|
-
color: var(--text, inherit);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* Open modally: fixed near the top (no fit-content in the block axis on
|
|
89
|
-
these browsers, so no vertical centering) and above menus, below
|
|
90
|
-
tooltips. Legacy-only layer, hence no z-index token. The spread shadow is
|
|
91
|
-
the visible backdrop: the dialog is its own stacking context, where a
|
|
92
|
-
negative z-index child paints above the element's background, so the
|
|
93
|
-
dimming cannot live on the ::before. */
|
|
94
|
-
dialog[open].is-fallback-modal {
|
|
95
|
-
position: fixed;
|
|
96
|
-
inset-block: 8vh auto;
|
|
97
|
-
max-block-size: 84vh;
|
|
98
|
-
overflow: auto;
|
|
99
|
-
z-index: 40;
|
|
100
|
-
box-shadow: 0 0 0 100vmax var(--backdrop-fill, rgb(0 0 0 / 35%));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/* Transparent stand-in for ::backdrop, hit-testing only. Clicks on it hit
|
|
104
|
-
the dialog element outside its own box, which dialog.js already treats as
|
|
105
|
-
a backdrop click, and it blocks pointer interaction with the page behind
|
|
106
|
-
the modal. */
|
|
107
|
-
dialog[open].is-fallback-modal::before {
|
|
108
|
-
content: "";
|
|
109
|
-
position: fixed;
|
|
110
|
-
inset: 0;
|
|
111
|
-
z-index: -1;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
48
|
:root {
|
|
49
|
+
/*
|
|
50
|
+
* Global framework tokens.
|
|
51
|
+
*
|
|
52
|
+
* Public: properties in the named groups below are public API (typography,
|
|
53
|
+
* spacing, shape, motion, focus, controls, viewport, z-index, icons) unless
|
|
54
|
+
* a group is explicitly marked Internal or Derived.
|
|
55
|
+
*
|
|
56
|
+
* Derived: "Theme-derived aliases" and "State tokens" reference intent and
|
|
57
|
+
* surface tokens and follow the theme; do not hard-code values into them.
|
|
58
|
+
* Internal: indicator offsets, --bar-height, --disabled-opacity and the
|
|
59
|
+
* like are consumed by components, not authored.
|
|
60
|
+
*
|
|
61
|
+
* Component-local hooks and internal state relays live with their
|
|
62
|
+
* components. See docs/pages/foundations/tokens.md for the public/internal
|
|
63
|
+
* token contract.
|
|
64
|
+
*/
|
|
65
|
+
|
|
116
66
|
/* Typography */
|
|
117
67
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
118
68
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
@@ -130,20 +80,22 @@ select {
|
|
|
130
80
|
--font-size-md: 1rem;
|
|
131
81
|
--font-size-lg: 1.125rem;
|
|
132
82
|
|
|
133
|
-
/* Spacing
|
|
83
|
+
/* Spacing — official steps are named in tens so the scale stays open for
|
|
84
|
+
application themes to intercalate intermediate values (--space-15,
|
|
85
|
+
--space-45…) without colliding with future framework steps. */
|
|
134
86
|
--gap: 0.75rem;
|
|
135
|
-
--
|
|
136
|
-
--
|
|
137
|
-
--space-
|
|
138
|
-
--space-
|
|
139
|
-
--space-
|
|
140
|
-
--space-
|
|
141
|
-
--space-
|
|
87
|
+
--density-space: var(--space-40);
|
|
88
|
+
--density-compact-size: 1.5rem;
|
|
89
|
+
--space-10: 0.25rem;
|
|
90
|
+
--space-20: 0.5rem;
|
|
91
|
+
--space-30: 0.75rem;
|
|
92
|
+
--space-40: 1rem;
|
|
93
|
+
--space-50: 1.5rem;
|
|
94
|
+
--space-60: 2rem;
|
|
142
95
|
|
|
143
96
|
/* Shape */
|
|
144
97
|
--border-width: 1px;
|
|
145
98
|
--radius-full: 9999px;
|
|
146
|
-
--shadow-xs: 0 1px 2px rgb(0 0 0 / 0.04);
|
|
147
99
|
--shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04);
|
|
148
100
|
--shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15);
|
|
149
101
|
|
|
@@ -167,7 +119,6 @@ select {
|
|
|
167
119
|
the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
|
|
168
120
|
dot with the parent surface color. */
|
|
169
121
|
--indicator-offset: 14.65%;
|
|
170
|
-
--indicator-ring: var(--surface);
|
|
171
122
|
|
|
172
123
|
/* Shared backdrop — consumed by modal, drawer, and mobile flyout sheets. */
|
|
173
124
|
--backdrop-color: rgb(0 0 0);
|
|
@@ -178,7 +129,8 @@ select {
|
|
|
178
129
|
--bar-height: 0.65rem;
|
|
179
130
|
|
|
180
131
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
181
|
-
.sm/.lg (variants.css) switch --control-size
|
|
132
|
+
.sm/.lg (variants.css) switch --control-size; --control-font-size stays
|
|
133
|
+
at its baseline because density does not change typography. */
|
|
182
134
|
--control-size-sm: 2rem;
|
|
183
135
|
--control-size-md: 2.375rem;
|
|
184
136
|
--control-size-lg: 2.75rem;
|
|
@@ -186,19 +138,38 @@ select {
|
|
|
186
138
|
--control-pad-x: 1em;
|
|
187
139
|
--control-font-size: var(--font-size-md);
|
|
188
140
|
--disabled-opacity: 0.65;
|
|
141
|
+
}
|
|
189
142
|
|
|
143
|
+
/* Theme-derived aliases. These reference intent/surface tokens, so they must
|
|
144
|
+
be re-declared on every theme boundary: a custom property resolves its
|
|
145
|
+
var() references at computed-value time on the element that declares it,
|
|
146
|
+
so an alias declared only on :root is inherited as an already-resolved
|
|
147
|
+
value and would not follow a [data-theme] island's overridden tokens.
|
|
148
|
+
Themes that want a distinct alias override it explicitly afterwards. */
|
|
149
|
+
:root,
|
|
150
|
+
[data-theme] {
|
|
151
|
+
/* State tokens — semantic colors for selected/pressed/current and disabled.
|
|
152
|
+
Components consume these instead of hard-wiring --primary/--text-subtle
|
|
153
|
+
so forced-colors can override them in one place. */
|
|
154
|
+
--state-selected: var(--primary);
|
|
155
|
+
--state-selected-fg: var(--primary-fg);
|
|
156
|
+
--state-disabled: var(--text-subtle);
|
|
157
|
+
|
|
158
|
+
/* Status dot ring — follows the island's surface. */
|
|
159
|
+
--indicator-ring: var(--surface);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
:root {
|
|
190
163
|
/* Variant tuning — themes may adjust generated soft surfaces without
|
|
191
164
|
replacing the component rules. */
|
|
192
165
|
--soft-bg-mix: 88%;
|
|
193
166
|
--soft-border-mix: 65%;
|
|
194
167
|
--soft-hover-alpha: 12%;
|
|
195
168
|
|
|
196
|
-
/* Shared
|
|
197
|
-
--icon-chevron
|
|
198
|
-
--icon-check
|
|
199
|
-
|
|
200
|
-
The stroke matches the default theme's text-subtle (hue 258 neutral). */
|
|
201
|
-
--icon-chevron-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
169
|
+
/* Shared icon assets. Override these URLs when a strict CSP disallows data:. */
|
|
170
|
+
--icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
171
|
+
--icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
|
|
172
|
+
--icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E");
|
|
202
173
|
|
|
203
174
|
/* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
|
|
204
175
|
Consumed by full-viewport components (app-shell, drawer, modal, flyout).
|
|
@@ -207,13 +178,24 @@ select {
|
|
|
207
178
|
--viewport-block: 100vh;
|
|
208
179
|
--viewport-inline: 100vw;
|
|
209
180
|
|
|
210
|
-
/*
|
|
181
|
+
/* Z-index scale — used ONLY by non-dialog overlays (see the layering
|
|
182
|
+
note below for the full model). Actual CSS reserves multiples of 10 for
|
|
183
|
+
framework layers; applications may use intermediate values for local
|
|
184
|
+
layering. */
|
|
211
185
|
--z-sticky: 10;
|
|
212
186
|
--z-menu: 20;
|
|
213
187
|
--z-tooltip: 50;
|
|
214
188
|
--z-status: 60;
|
|
215
189
|
}
|
|
216
190
|
|
|
191
|
+
@media (forced-colors: active) {
|
|
192
|
+
:root {
|
|
193
|
+
--state-selected: Highlight;
|
|
194
|
+
--state-selected-fg: HighlightText;
|
|
195
|
+
--state-disabled: GrayText;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
217
199
|
@supports (block-size: 100dvh) {
|
|
218
200
|
:root {
|
|
219
201
|
--viewport-block: 100dvh;
|
|
@@ -227,12 +209,17 @@ select {
|
|
|
227
209
|
}
|
|
228
210
|
|
|
229
211
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
230
|
-
:root
|
|
231
|
-
|
|
212
|
+
:root,
|
|
213
|
+
[data-theme] {
|
|
214
|
+
/* Shadows derive from --shadow-color (theme.css) — re-declared on every
|
|
215
|
+
theme boundary so islands follow their own shadow-color. */
|
|
232
216
|
--shadow:
|
|
233
217
|
0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent),
|
|
234
218
|
0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent);
|
|
235
219
|
--shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
:root {
|
|
236
223
|
--backdrop-fill: color-mix(
|
|
237
224
|
in oklch,
|
|
238
225
|
var(--backdrop-color) calc(var(--backdrop-opacity) * 100%),
|
|
@@ -241,6 +228,17 @@ select {
|
|
|
241
228
|
}
|
|
242
229
|
}
|
|
243
230
|
|
|
231
|
+
/*
|
|
232
|
+
* Theme tokens — the palette, surfaces, text, borders, radius and shadow
|
|
233
|
+
* color. Named themes (themes/*.css) override this file; authors can set the
|
|
234
|
+
* same tokens on :root or on [data-theme="…"] islands.
|
|
235
|
+
*
|
|
236
|
+
* Public: Intent colors, Surfaces, Text, Lines and focus, Radius scale,
|
|
237
|
+
* Shadow color, Focus ring derivation.
|
|
238
|
+
* Derived: "Theme-derived aliases", Tone subsystem, Backdrop fill — reference
|
|
239
|
+
* other tokens; hard-coded overrides on a custom theme will break the intent.
|
|
240
|
+
* Internal: --hgroup-gap.
|
|
241
|
+
*/
|
|
244
242
|
:root {
|
|
245
243
|
color-scheme: light dark;
|
|
246
244
|
}
|
|
@@ -308,7 +306,6 @@ select {
|
|
|
308
306
|
/* Pre-color-mix fallback only — superseded below by a derivation from
|
|
309
307
|
--focus so overriding --neutral/--focus never leaves a stale ring. */
|
|
310
308
|
--focus-ring: hsl(258 7% 43% / 0.3);
|
|
311
|
-
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
312
309
|
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
313
310
|
|
|
314
311
|
/* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
|
|
@@ -319,13 +316,19 @@ select {
|
|
|
319
316
|
/* Shadow color — consumed by --shadow / --shadow-popout in tokens.css */
|
|
320
317
|
--shadow-color: hsl(260 25% 10%);
|
|
321
318
|
|
|
322
|
-
/*
|
|
319
|
+
/* hgroup internal gap */
|
|
320
|
+
--hgroup-gap: 0.25em;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* Theme-derived aliases — re-declared on every theme boundary (see tokens.css
|
|
324
|
+
for the custom-property resolution rationale): islands recompute these from
|
|
325
|
+
their own tokens instead of inheriting :root's resolved values. */
|
|
326
|
+
:root,
|
|
327
|
+
[data-theme] {
|
|
323
328
|
--heading: var(--text);
|
|
324
329
|
--selection-bg: var(--primary);
|
|
325
330
|
--selection-fg: var(--primary-fg);
|
|
326
|
-
|
|
327
|
-
/* hgroup internal gap */
|
|
328
|
-
--hgroup-gap: 0.25em;
|
|
331
|
+
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
/* Tone subsystem — fixed-backdrop foreground for components placed over media
|
|
@@ -447,10 +450,8 @@ a {
|
|
|
447
450
|
text-underline-offset: 0.18em;
|
|
448
451
|
}
|
|
449
452
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
text-decoration-thickness: 0.12em;
|
|
453
|
-
}
|
|
453
|
+
a:hover {
|
|
454
|
+
text-decoration-thickness: 0.12em;
|
|
454
455
|
}
|
|
455
456
|
|
|
456
457
|
/* Global heading chrome: color (--heading or inherit), a tight line-height,
|
|
@@ -476,7 +477,7 @@ hgroup > * {
|
|
|
476
477
|
hr {
|
|
477
478
|
border: 0;
|
|
478
479
|
border-block-start: var(--border-width) solid var(--border);
|
|
479
|
-
margin-block: var(--space-
|
|
480
|
+
margin-block: var(--space-60);
|
|
480
481
|
}
|
|
481
482
|
|
|
482
483
|
code,
|
|
@@ -499,8 +500,8 @@ small {
|
|
|
499
500
|
* Intent classes do not style elements directly.
|
|
500
501
|
* They only expose --intent and --intent-fg.
|
|
501
502
|
* Components and variants decide how to consume them. Each component that
|
|
502
|
-
* consumes --intent or --ui-*
|
|
503
|
-
* block that resets
|
|
503
|
+
* consumes --intent or --ui-* opens a local intent boundary in a :where()
|
|
504
|
+
* block that resets exactly the tokens it consumes to initial. Parent intents
|
|
504
505
|
* do not leak into nested components. Local intent classes (.primary, .danger,
|
|
505
506
|
* etc.) still apply because they have higher specificity than :where().
|
|
506
507
|
*/
|
|
@@ -529,6 +530,28 @@ small {
|
|
|
529
530
|
--intent-fg: var(--neutral-fg);
|
|
530
531
|
}
|
|
531
532
|
|
|
533
|
+
/*
|
|
534
|
+
* Core layout primitives: stack, cluster, center, frame, app-shell, media,
|
|
535
|
+
* sidebar-layout. Each primitive owns a single, readable behavior; they are
|
|
536
|
+
* meant to compose in markup.
|
|
537
|
+
*
|
|
538
|
+
* Public hooks:
|
|
539
|
+
* --cluster-align
|
|
540
|
+
* --cluster-justify
|
|
541
|
+
* --cluster-wrap
|
|
542
|
+
* --center-size
|
|
543
|
+
* --center-pad
|
|
544
|
+
* --frame-ratio
|
|
545
|
+
* --sidebar-layout-size
|
|
546
|
+
*
|
|
547
|
+
* Child contracts:
|
|
548
|
+
* .stack owns vertical rhythm: direct-child margins are reset, spacing comes
|
|
549
|
+
* from --gap.
|
|
550
|
+
* .cluster owns inline rhythm: direct-child margins are reset, spacing comes
|
|
551
|
+
* from --gap.
|
|
552
|
+
* .frame sizes direct media children (img/video/iframe/canvas/svg) to fill the
|
|
553
|
+
* frame.
|
|
554
|
+
*/
|
|
532
555
|
.container-query {
|
|
533
556
|
container-type: inline-size;
|
|
534
557
|
}
|
|
@@ -545,12 +568,16 @@ small {
|
|
|
545
568
|
|
|
546
569
|
.cluster {
|
|
547
570
|
display: flex;
|
|
548
|
-
flex-wrap: wrap;
|
|
571
|
+
flex-wrap: var(--cluster-wrap, wrap);
|
|
549
572
|
align-items: var(--cluster-align, center);
|
|
550
573
|
justify-content: var(--cluster-justify, flex-start);
|
|
551
574
|
gap: var(--gap);
|
|
552
575
|
}
|
|
553
576
|
|
|
577
|
+
.cluster > * {
|
|
578
|
+
margin: 0;
|
|
579
|
+
}
|
|
580
|
+
|
|
554
581
|
.center {
|
|
555
582
|
box-sizing: content-box;
|
|
556
583
|
max-inline-size: var(--center-size, 72rem);
|
|
@@ -594,6 +621,8 @@ small {
|
|
|
594
621
|
grid-template-columns: minmax(0, 1fr);
|
|
595
622
|
}
|
|
596
623
|
|
|
624
|
+
/* Container queries are the preferred path; without them the @supports not
|
|
625
|
+
block below restores the same columns at the same viewport breakpoint. */
|
|
597
626
|
@container (width >= 64rem) {
|
|
598
627
|
.container-query .sidebar-layout {
|
|
599
628
|
grid-template-columns: minmax(0, 1fr) var(--sidebar-layout-size, 18rem);
|
|
@@ -610,29 +639,27 @@ small {
|
|
|
610
639
|
}
|
|
611
640
|
}
|
|
612
641
|
|
|
642
|
+
/*
|
|
643
|
+
* Responsive equal-width grids, with fixed-column presets below.
|
|
644
|
+
*
|
|
645
|
+
* Public hooks:
|
|
646
|
+
* --grid-min
|
|
647
|
+
* --grid-columns
|
|
648
|
+
*/
|
|
613
649
|
.grid {
|
|
614
650
|
display: grid;
|
|
615
651
|
gap: var(--gap);
|
|
616
|
-
grid-template-columns:
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
* .grid-6: 6 cols
|
|
628
|
-
* When placed inside .container-query, the column count collapses
|
|
629
|
-
* on narrow wrappers. The intermediate 2-col step applies to
|
|
630
|
-
* .grid-3/.grid-4; .grid-6 starts at 2 columns because it is meant
|
|
631
|
-
* for compact items that do not need a full single-column stack.
|
|
632
|
-
* .grid-2: 1 col -> 2 cols
|
|
633
|
-
* .grid-3: 1 col -> 2 cols -> 3 cols
|
|
634
|
-
* .grid-4: 1 col -> 2 cols -> 4 cols
|
|
635
|
-
* .grid-6: 2 cols -> 3 cols -> 6 cols */
|
|
652
|
+
grid-template-columns: var(
|
|
653
|
+
--grid-columns,
|
|
654
|
+
repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr))
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/* Fixed-column grids at their full count by default: .grid-2/3/4/6 stay at
|
|
659
|
+
2/3/4/6 columns unless wrapped in .container-query, which collapses the
|
|
660
|
+
column count on narrow wrappers (a 2-col intermediate step for
|
|
661
|
+
.grid-3/.grid-4; .grid-6 starts at 2 columns because it is meant for
|
|
662
|
+
compact items that do not need a full single-column stack). */
|
|
636
663
|
.grid-2,
|
|
637
664
|
.grid-3,
|
|
638
665
|
.grid-4,
|
|
@@ -654,6 +681,9 @@ small {
|
|
|
654
681
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
655
682
|
}
|
|
656
683
|
|
|
684
|
+
/* Container queries are the preferred responsive path; browsers without them
|
|
685
|
+
ignore these rules and get the equivalent @media rules from the
|
|
686
|
+
@supports not (container-type) block below — same columns, same breakpoints. */
|
|
657
687
|
@container (width < 28rem) {
|
|
658
688
|
.container-query .grid-2,
|
|
659
689
|
.container-query .grid-3,
|
|
@@ -710,15 +740,20 @@ small {
|
|
|
710
740
|
* Cross-cutting variant token mapping.
|
|
711
741
|
*
|
|
712
742
|
* Variant scope: .solid/.soft/.outline set --ui-* for components that opt into
|
|
713
|
-
* shared variants.
|
|
714
|
-
*
|
|
743
|
+
* shared variants. .inverted is a surface modifier, not a variant: it also
|
|
744
|
+
* paints the element directly (background/color/border-color) so it works on
|
|
745
|
+
* any block, competing with component surface treatments (.card.subtle).
|
|
746
|
+
* Component-specific defaults (.btn solid-by-default, .badge
|
|
747
|
+
* soft-by-default, .alert soft-by-default) live with their components:
|
|
715
748
|
* components/button.css — .btn default solid + .btn.ghost + .btn.link
|
|
716
|
-
* components/badge.css — .badge default
|
|
717
|
-
* components/alert.css — .alert default
|
|
749
|
+
* components/badge.css — .badge default soft
|
|
750
|
+
* components/alert.css — .alert default soft (consumes --ui-* directly)
|
|
718
751
|
*
|
|
719
|
-
* .sm/.lg set shared
|
|
720
|
-
*
|
|
721
|
-
*
|
|
752
|
+
* .sm/.lg set shared density tokens: spacing rhythm and geometry only.
|
|
753
|
+
* Density never changes typography — controls keep their font-size and
|
|
754
|
+
* content components keep theirs. Controls consume --control-size; non-control
|
|
755
|
+
* components may consume --density-* when the density context should affect
|
|
756
|
+
* their local rhythm. Compact components may consume --density-compact-size.
|
|
722
757
|
* Components with bespoke sizing (spinner, avatar) define local .sm/.lg;
|
|
723
758
|
* applying .sm/.lg to card, dialog, accordion, navbar, table is
|
|
724
759
|
* a no-op.
|
|
@@ -744,27 +779,34 @@ small {
|
|
|
744
779
|
--ui-border: var(--intent, var(--border));
|
|
745
780
|
}
|
|
746
781
|
|
|
782
|
+
/* Inverse surface treatment. Components (e.g. .card) compose through --ui-*;
|
|
783
|
+
the direct background/color/border-color also make .inverted useful on a
|
|
784
|
+
generic element. --heading is set so global headings (var(--heading, inherit))
|
|
785
|
+
do not inherit an outside heading color onto the dark surface. */
|
|
786
|
+
.inverted {
|
|
787
|
+
--ui-bg: var(--surface-solid);
|
|
788
|
+
--ui-fg: var(--surface);
|
|
789
|
+
--ui-border: transparent;
|
|
790
|
+
|
|
791
|
+
--heading: var(--surface);
|
|
792
|
+
--busy-overlay-bg: var(--surface-solid);
|
|
793
|
+
|
|
794
|
+
background: var(--ui-bg);
|
|
795
|
+
color: var(--ui-fg);
|
|
796
|
+
border-color: var(--ui-border);
|
|
797
|
+
}
|
|
798
|
+
|
|
747
799
|
.sm {
|
|
748
|
-
--gap: var(--space-
|
|
749
|
-
--
|
|
800
|
+
--gap: var(--space-20);
|
|
801
|
+
--density-space: var(--space-20);
|
|
750
802
|
--control-size: var(--control-size-sm);
|
|
751
|
-
--
|
|
752
|
-
--variant-font-size: var(--font-size-sm);
|
|
753
|
-
--variant-icon-size: 1rem;
|
|
754
|
-
--variant-pad-block: var(--space-3);
|
|
755
|
-
--variant-compact-size: 1.25rem;
|
|
756
|
-
--variant-compact-font-size: 0.75rem;
|
|
803
|
+
--density-compact-size: 1.25rem;
|
|
757
804
|
}
|
|
758
805
|
.lg {
|
|
759
|
-
--gap: var(--space-
|
|
760
|
-
--
|
|
806
|
+
--gap: var(--space-50);
|
|
807
|
+
--density-space: var(--space-50);
|
|
761
808
|
--control-size: var(--control-size-lg);
|
|
762
|
-
--
|
|
763
|
-
--variant-font-size: var(--font-size-lg);
|
|
764
|
-
--variant-icon-size: 1.5rem;
|
|
765
|
-
--variant-pad-block: var(--space-5);
|
|
766
|
-
--variant-compact-size: 1.875rem;
|
|
767
|
-
--variant-compact-font-size: var(--font-size-sm);
|
|
809
|
+
--density-compact-size: 1.875rem;
|
|
768
810
|
}
|
|
769
811
|
|
|
770
812
|
/* Modern color enhancement. Baseline variant values above remain usable
|
|
@@ -782,54 +824,25 @@ small {
|
|
|
782
824
|
}
|
|
783
825
|
}
|
|
784
826
|
|
|
785
|
-
/* Interactive hover
|
|
786
|
-
|
|
787
|
-
rules, so these values are inert until hovered. :is() carries real
|
|
788
|
-
specificity so the components' :where() intent boundaries
|
|
789
|
-
(--ui-hover-bg: initial) cannot override the recipe. The solid/soft overlay
|
|
790
|
-
darkening lives in the consuming components. */
|
|
791
|
-
:is(.btn, a.badge, button.badge):is(.outline, .ghost) {
|
|
792
|
-
--ui-hover-bg: var(--surface-subtle);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
796
|
-
/* Solid has no literal class requirement: bare .btn/.badge default to solid,
|
|
797
|
-
so this must match anything that isn't one of the other variants rather
|
|
798
|
-
than an explicit .solid class. Stacks with the box-shadow overlay the
|
|
799
|
-
consuming components apply on top for extra depth. */
|
|
800
|
-
:is(.btn, a.badge, button.badge):not(:where(.soft, .outline, .ghost, .link)) {
|
|
801
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
/* Same recipe as the resting .soft bg with the surface share reduced by
|
|
805
|
-
--soft-hover-alpha: stays opaque, so it reads on any backdrop instead of
|
|
806
|
-
letting the container show through on hover. */
|
|
807
|
-
:is(.btn, a.badge, button.badge):is(.soft) {
|
|
808
|
-
--ui-hover-bg: color-mix(
|
|
809
|
-
in oklch,
|
|
810
|
-
var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
|
|
811
|
-
var(--intent, var(--neutral))
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
/* Mixed against the opaque --surface-subtle rather than transparent: a
|
|
816
|
-
transparent-based tint reads clean on light surfaces but goes muddy and
|
|
817
|
-
low-contrast on dark ones, since the same low alpha lands very
|
|
818
|
-
differently over a light vs. a near-black backdrop. */
|
|
819
|
-
:is(.btn, a.badge, button.badge):is(.outline) {
|
|
820
|
-
--ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
:is(.btn, a.badge, button.badge):is(.ghost) {
|
|
824
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
+
/* Interactive hover recipes live in the components that consume them:
|
|
828
|
+
components/button.css and components/badge.css. */
|
|
827
829
|
|
|
828
830
|
/*
|
|
829
831
|
* Prose is the opt-in rich text scope for authored content.
|
|
830
832
|
*
|
|
831
833
|
* Global element styles stay mild so app UI is not shaped accidentally. Page
|
|
832
834
|
* layout, forms, cards, navigation, and app tables remain outside this scope.
|
|
835
|
+
*
|
|
836
|
+
* Public hooks:
|
|
837
|
+
* --prose-size
|
|
838
|
+
* --prose-line-height
|
|
839
|
+
* --prose-measure
|
|
840
|
+
* --prose-flow
|
|
841
|
+
* --prose-heading-line-height
|
|
842
|
+
* --prose-h1
|
|
843
|
+
* --prose-h2
|
|
844
|
+
* --prose-h3
|
|
845
|
+
* --prose-h4
|
|
833
846
|
*/
|
|
834
847
|
|
|
835
848
|
.prose {
|
|
@@ -893,15 +906,13 @@ small {
|
|
|
893
906
|
text-underline-offset: 0.18em;
|
|
894
907
|
}
|
|
895
908
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
}
|
|
909
|
+
.prose a:hover {
|
|
910
|
+
text-decoration-thickness: 0.12em;
|
|
911
|
+
}
|
|
900
912
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
913
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
914
|
+
.prose a:hover {
|
|
915
|
+
color: color-mix(in oklch, var(--primary), var(--text) 15%);
|
|
905
916
|
}
|
|
906
917
|
}
|
|
907
918
|
|
|
@@ -921,6 +932,7 @@ small {
|
|
|
921
932
|
font-size: 0.875em;
|
|
922
933
|
}
|
|
923
934
|
|
|
935
|
+
/* Visual twin of .key (components/key.css) — keep the two recipes in sync. */
|
|
924
936
|
.prose kbd {
|
|
925
937
|
display: inline-flex;
|
|
926
938
|
align-items: center;
|
|
@@ -1025,19 +1037,35 @@ small {
|
|
|
1025
1037
|
* Disabled state uses the native disabled attribute for buttons and inputs.
|
|
1026
1038
|
* Anchors have no native disabled state, so a.btn uses aria-disabled="true"
|
|
1027
1039
|
* for disabled styling and activation guards.
|
|
1040
|
+
*
|
|
1041
|
+
* Public hooks:
|
|
1042
|
+
* --btn-font-size
|
|
1043
|
+
* --btn-min-size
|
|
1044
|
+
* --btn-pad-x
|
|
1045
|
+
* --btn-radius
|
|
1046
|
+
* --btn-focus-color
|
|
1047
|
+
* --btn-gap
|
|
1048
|
+
* --btn-hover-overlay
|
|
1049
|
+
*
|
|
1050
|
+
* Internal:
|
|
1051
|
+
* --btn-bg
|
|
1052
|
+
* --btn-fg
|
|
1053
|
+
* --btn-border
|
|
1054
|
+
* --btn-focus-ring-color
|
|
1055
|
+
* --btn-border-block-end-color
|
|
1056
|
+
* --btn-border-block-end-width
|
|
1057
|
+
* --spinner-width
|
|
1028
1058
|
*/
|
|
1029
1059
|
|
|
1030
1060
|
/* Intent boundary — inherited intents do not tint this component.
|
|
1031
1061
|
Local intent classes still win because :where() has zero specificity. */
|
|
1032
1062
|
:where(.btn) {
|
|
1033
|
-
/* @sync intent-boundary */
|
|
1034
1063
|
--intent: initial;
|
|
1035
1064
|
--intent-fg: initial;
|
|
1036
1065
|
--ui-bg: initial;
|
|
1037
1066
|
--ui-fg: initial;
|
|
1038
1067
|
--ui-border: initial;
|
|
1039
1068
|
--ui-hover-bg: initial;
|
|
1040
|
-
/* @sync intent-boundary */
|
|
1041
1069
|
}
|
|
1042
1070
|
|
|
1043
1071
|
.btn {
|
|
@@ -1049,17 +1077,17 @@ small {
|
|
|
1049
1077
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
1050
1078
|
--btn-radius: var(--radius);
|
|
1051
1079
|
--btn-focus-color: var(--intent, var(--focus));
|
|
1052
|
-
--btn-hover-bg: var(--ui-hover-bg, var(--btn-bg));
|
|
1053
1080
|
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
1054
1081
|
|
|
1082
|
+
/* Positioning context for indicators, overlays, and grouped states. */
|
|
1055
1083
|
position: relative;
|
|
1056
1084
|
display: inline-flex;
|
|
1057
1085
|
align-items: center;
|
|
1058
1086
|
justify-content: center;
|
|
1059
|
-
gap: 0.375em;
|
|
1087
|
+
gap: var(--btn-gap, 0.375em);
|
|
1060
1088
|
min-block-size: var(--btn-min-size);
|
|
1061
|
-
padding-block: var(--space-
|
|
1062
|
-
padding-block: max(var(--space-
|
|
1089
|
+
padding-block: var(--space-20);
|
|
1090
|
+
padding-block: max(var(--space-10), calc((var(--btn-min-size) - 1lh) / 2));
|
|
1063
1091
|
padding-inline: var(--btn-pad-x);
|
|
1064
1092
|
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
1065
1093
|
border-block-end-color: var(
|
|
@@ -1084,6 +1112,11 @@ small {
|
|
|
1084
1112
|
transform var(--duration);
|
|
1085
1113
|
}
|
|
1086
1114
|
|
|
1115
|
+
.btn.icon-only {
|
|
1116
|
+
inline-size: var(--btn-min-size);
|
|
1117
|
+
padding-inline: 0;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1087
1120
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
1088
1121
|
.btn {
|
|
1089
1122
|
--btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
|
|
@@ -1108,13 +1141,40 @@ small {
|
|
|
1108
1141
|
--btn-hover-overlay: transparent;
|
|
1109
1142
|
}
|
|
1110
1143
|
|
|
1111
|
-
/*
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1144
|
+
/* Shared hover recipe — sets --ui-hover-bg consumed by the hover rule below.
|
|
1145
|
+
Declared outside :hover: the variables resolve only when the hover rule
|
|
1146
|
+
reads them, so the values are inert until interaction. */
|
|
1147
|
+
.btn:is(.outline, .ghost) {
|
|
1148
|
+
--ui-hover-bg: var(--surface-subtle);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
1152
|
+
.btn:not(:where(.soft, .outline, .ghost, .link)) {
|
|
1153
|
+
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.btn.soft {
|
|
1157
|
+
--ui-hover-bg: color-mix(
|
|
1158
|
+
in oklch,
|
|
1159
|
+
var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
|
|
1160
|
+
var(--intent, var(--neutral))
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
.btn.outline {
|
|
1165
|
+
--ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
|
|
1117
1166
|
}
|
|
1167
|
+
|
|
1168
|
+
.btn.ghost {
|
|
1169
|
+
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
|
|
1174
|
+
the overlay darkening for solid/soft is .btn-specific. */
|
|
1175
|
+
.btn:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
1176
|
+
background: var(--ui-hover-bg, var(--btn-bg));
|
|
1177
|
+
box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
|
|
1118
1178
|
}
|
|
1119
1179
|
|
|
1120
1180
|
.btn:not(:where(:disabled, [aria-disabled="true"])):active {
|
|
@@ -1139,16 +1199,14 @@ small {
|
|
|
1139
1199
|
/* .btn.circle needs no local rule: the .circle utility (utilities.css) loads
|
|
1140
1200
|
after components and overrides border-radius at equal specificity. */
|
|
1141
1201
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
text-decoration-thickness: 0.12em;
|
|
1147
|
-
}
|
|
1202
|
+
.btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
1203
|
+
background: transparent;
|
|
1204
|
+
color: var(--intent, var(--text));
|
|
1205
|
+
text-decoration-thickness: 0.12em;
|
|
1148
1206
|
}
|
|
1149
1207
|
|
|
1150
1208
|
.btn:focus-visible {
|
|
1151
|
-
outline:
|
|
1209
|
+
outline: 2px solid transparent;
|
|
1152
1210
|
box-shadow: 0 0 0 var(--focus-ring-width) var(--btn-focus-ring-color, var(--focus-ring));
|
|
1153
1211
|
}
|
|
1154
1212
|
|
|
@@ -1182,13 +1240,15 @@ a.btn[aria-disabled="true"] {
|
|
|
1182
1240
|
--spinner-width: 0.15em;
|
|
1183
1241
|
}
|
|
1184
1242
|
|
|
1185
|
-
/* Toggle
|
|
1186
|
-
|
|
1187
|
-
button keeps its intent background and foreground while hovered.
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
--btn-
|
|
1243
|
+
/* Toggle and current state.
|
|
1244
|
+
State must outrank the shared .outline/.ghost hover recipes: a pressed or
|
|
1245
|
+
current button keeps its intent background and foreground while hovered.
|
|
1246
|
+
aria-current="page" is the pagination current-page marker; aria-pressed
|
|
1247
|
+
covers toggle buttons. */
|
|
1248
|
+
.btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
|
|
1249
|
+
--btn-bg: var(--intent, var(--state-selected));
|
|
1250
|
+
--btn-fg: var(--intent-fg, var(--state-selected-fg));
|
|
1251
|
+
--btn-border: var(--intent, var(--state-selected));
|
|
1192
1252
|
--ui-hover-bg: var(--btn-bg);
|
|
1193
1253
|
}
|
|
1194
1254
|
|
|
@@ -1206,18 +1266,16 @@ a.btn[aria-disabled="true"] {
|
|
|
1206
1266
|
color: LinkText;
|
|
1207
1267
|
}
|
|
1208
1268
|
|
|
1209
|
-
.btn:is([aria-pressed="true"], [aria-current
|
|
1269
|
+
.btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
|
|
1210
1270
|
border-color: Highlight;
|
|
1211
1271
|
background: Highlight;
|
|
1212
1272
|
color: HighlightText;
|
|
1213
1273
|
forced-color-adjust: none;
|
|
1214
1274
|
}
|
|
1215
1275
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
outline-offset: 2px;
|
|
1220
|
-
}
|
|
1276
|
+
.btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
|
|
1277
|
+
outline: var(--border-width) solid ButtonText;
|
|
1278
|
+
outline-offset: 2px;
|
|
1221
1279
|
}
|
|
1222
1280
|
|
|
1223
1281
|
.btn:disabled,
|
|
@@ -1235,34 +1293,46 @@ a.btn[aria-disabled="true"] {
|
|
|
1235
1293
|
* Soft by default. .solid for counters and notification badges. The dot
|
|
1236
1294
|
* (.badge:empty) stays solid regardless — a soft dot is pointless.
|
|
1237
1295
|
* Shared .outline variant stays available.
|
|
1296
|
+
*
|
|
1297
|
+
* Public hooks:
|
|
1298
|
+
* --badge-size
|
|
1299
|
+
* --badge-pad-x
|
|
1300
|
+
* --badge-font-size
|
|
1301
|
+
* --badge-dot-size
|
|
1302
|
+
* --badge-radius
|
|
1303
|
+
*
|
|
1304
|
+
* Internal:
|
|
1305
|
+
* --badge-bg
|
|
1306
|
+
* --badge-fg
|
|
1307
|
+
* --badge-border
|
|
1238
1308
|
*/
|
|
1239
1309
|
/* Intent boundary — inherited intents do not tint this component.
|
|
1240
1310
|
Local intent classes still win because :where() has zero specificity. */
|
|
1241
1311
|
:where(.badge) {
|
|
1242
|
-
/* @sync intent-boundary */
|
|
1243
1312
|
--intent: initial;
|
|
1244
1313
|
--intent-fg: initial;
|
|
1245
1314
|
--ui-bg: initial;
|
|
1246
1315
|
--ui-fg: initial;
|
|
1247
1316
|
--ui-border: initial;
|
|
1248
1317
|
--ui-hover-bg: initial;
|
|
1249
|
-
/* @sync intent-boundary */
|
|
1250
1318
|
}
|
|
1251
1319
|
|
|
1252
1320
|
.badge {
|
|
1253
|
-
--badge-size: var(--
|
|
1321
|
+
--badge-size: var(--density-compact-size);
|
|
1254
1322
|
--badge-pad-x: 0.55em;
|
|
1255
|
-
--badge-font-size:
|
|
1323
|
+
--badge-font-size: 0.8125rem;
|
|
1256
1324
|
--badge-dot-size: 0.5rem;
|
|
1257
|
-
--badge-bg: var(--ui-bg,
|
|
1325
|
+
--badge-bg: var(--ui-bg, var(--surface-subtle));
|
|
1258
1326
|
--badge-fg: var(--ui-fg, var(--intent, var(--text)));
|
|
1259
|
-
--badge-border: var(--ui-border, var(--
|
|
1260
|
-
--badge-radius:
|
|
1327
|
+
--badge-border: var(--ui-border, var(--border));
|
|
1328
|
+
--badge-radius: var(--radius-full);
|
|
1261
1329
|
|
|
1262
1330
|
display: inline-flex;
|
|
1263
1331
|
align-items: center;
|
|
1264
1332
|
justify-content: center;
|
|
1265
1333
|
gap: 0.35em;
|
|
1334
|
+
inline-size: fit-content;
|
|
1335
|
+
max-inline-size: 100%;
|
|
1266
1336
|
min-block-size: var(--badge-size);
|
|
1267
1337
|
padding-inline: var(--badge-pad-x);
|
|
1268
1338
|
border: var(--border-width) solid var(--badge-border);
|
|
@@ -1331,27 +1401,44 @@ a.btn[aria-disabled="true"] {
|
|
|
1331
1401
|
cursor: pointer;
|
|
1332
1402
|
}
|
|
1333
1403
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
background: var(--hover-overlay);
|
|
1337
|
-
}
|
|
1404
|
+
.badge > button:not(:disabled):hover {
|
|
1405
|
+
background: var(--hover-overlay);
|
|
1338
1406
|
}
|
|
1339
1407
|
|
|
1340
|
-
/*
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
:
|
|
1344
|
-
|
|
1408
|
+
/* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
|
|
1409
|
+
Declared outside :hover like button.css for the same reason. */
|
|
1410
|
+
:is(a.badge, button.badge).outline {
|
|
1411
|
+
--ui-hover-bg: var(--surface-subtle);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
1415
|
+
:is(a.badge, button.badge):is(.solid, :empty) {
|
|
1416
|
+
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
1345
1417
|
}
|
|
1346
1418
|
|
|
1347
|
-
:
|
|
1348
|
-
|
|
1419
|
+
:is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
|
|
1420
|
+
--ui-hover-bg: color-mix(
|
|
1421
|
+
in oklch,
|
|
1422
|
+
var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
|
|
1423
|
+
var(--intent, var(--neutral))
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
:is(a.badge, button.badge).outline {
|
|
1428
|
+
--ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
|
|
1349
1429
|
}
|
|
1350
1430
|
}
|
|
1351
1431
|
|
|
1432
|
+
:where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
1433
|
+
background: var(--ui-hover-bg, var(--badge-bg));
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
:where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"], .outline)):hover {
|
|
1437
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1352
1440
|
button.badge {
|
|
1353
1441
|
cursor: pointer;
|
|
1354
|
-
font: inherit;
|
|
1355
1442
|
}
|
|
1356
1443
|
|
|
1357
1444
|
button.badge:disabled,
|
|
@@ -1365,27 +1452,18 @@ a.badge[aria-disabled="true"] {
|
|
|
1365
1452
|
}
|
|
1366
1453
|
|
|
1367
1454
|
@media (forced-colors: active) {
|
|
1368
|
-
.badge {
|
|
1369
|
-
border-color: ButtonText;
|
|
1370
|
-
background: Canvas;
|
|
1371
|
-
color: CanvasText;
|
|
1372
|
-
box-shadow: none;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
1455
|
.badge:empty {
|
|
1376
1456
|
background: ButtonText;
|
|
1377
1457
|
}
|
|
1378
1458
|
|
|
1379
|
-
|
|
1380
|
-
:
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1459
|
+
:where(a.badge, button.badge):hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
1460
|
+
outline: var(--border-width) solid ButtonText;
|
|
1461
|
+
outline-offset: 2px;
|
|
1462
|
+
}
|
|
1384
1463
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
}
|
|
1464
|
+
.badge > button:not(:disabled):hover {
|
|
1465
|
+
outline: var(--border-width) solid currentColor;
|
|
1466
|
+
outline-offset: 1px;
|
|
1389
1467
|
}
|
|
1390
1468
|
|
|
1391
1469
|
button.badge:disabled,
|
|
@@ -1405,42 +1483,48 @@ a.badge[aria-disabled="true"] {
|
|
|
1405
1483
|
* --alert-border-inline-start-color/-width are unset extension points, mirroring
|
|
1406
1484
|
* --btn-border-block-end-* (button.css), for themes that give alerts a
|
|
1407
1485
|
* callout-style colored flag on the leading edge instead of a uniform border.
|
|
1486
|
+
*
|
|
1487
|
+
* Public hooks:
|
|
1488
|
+
* --alert-pad-block
|
|
1489
|
+
* --alert-pad-inline
|
|
1490
|
+
* --alert-font-size
|
|
1491
|
+
* --alert-radius
|
|
1492
|
+
* --alert-icon-size
|
|
1493
|
+
* --alert-dismiss-size
|
|
1494
|
+
*
|
|
1495
|
+
* Internal:
|
|
1496
|
+
* --alert-bg
|
|
1497
|
+
* --alert-fg
|
|
1498
|
+
* --alert-border
|
|
1499
|
+
* --alert-dismiss-icon-size
|
|
1408
1500
|
*/
|
|
1409
1501
|
/* Intent boundary — inherited intents do not tint this component.
|
|
1410
1502
|
Local intent classes still win because :where() has zero specificity. */
|
|
1411
1503
|
:where(.alert) {
|
|
1412
|
-
/* @sync intent-boundary */
|
|
1413
1504
|
--intent: initial;
|
|
1414
1505
|
--intent-fg: initial;
|
|
1415
1506
|
--ui-bg: initial;
|
|
1416
1507
|
--ui-fg: initial;
|
|
1417
1508
|
--ui-border: initial;
|
|
1418
|
-
--ui-hover-bg: initial;
|
|
1419
|
-
/* @sync intent-boundary */
|
|
1420
1509
|
}
|
|
1421
1510
|
|
|
1422
1511
|
.alert {
|
|
1423
|
-
--alert-pad-block: var(--
|
|
1424
|
-
--alert-pad-inline: var(--space-
|
|
1425
|
-
--alert-font-size: var(--
|
|
1426
|
-
--alert-icon-size:
|
|
1427
|
-
--alert-bg: var(--surface-subtle);
|
|
1428
|
-
--alert-
|
|
1429
|
-
--
|
|
1430
|
-
--ui-border: var(--alert-border);
|
|
1431
|
-
--ui-fg: var(--intent, var(--text));
|
|
1512
|
+
--alert-pad-block: var(--space-30);
|
|
1513
|
+
--alert-pad-inline: var(--space-40);
|
|
1514
|
+
--alert-font-size: var(--font-size-md);
|
|
1515
|
+
--alert-icon-size: 1.25rem;
|
|
1516
|
+
--alert-bg: var(--ui-bg, var(--surface-subtle));
|
|
1517
|
+
--alert-fg: var(--ui-fg, var(--intent, var(--text)));
|
|
1518
|
+
--alert-border: var(--ui-border, var(--border));
|
|
1432
1519
|
|
|
1433
1520
|
padding-block: var(--alert-pad-block);
|
|
1434
1521
|
padding-inline: var(--alert-pad-inline);
|
|
1435
|
-
background: var(--
|
|
1436
|
-
color: var(--
|
|
1437
|
-
border: var(--border-width) solid var(--
|
|
1438
|
-
border-inline-start-color: var(
|
|
1439
|
-
--alert-border-inline-start-color,
|
|
1440
|
-
var(--ui-border, var(--border))
|
|
1441
|
-
);
|
|
1522
|
+
background: var(--alert-bg);
|
|
1523
|
+
color: var(--alert-fg);
|
|
1524
|
+
border: var(--border-width) solid var(--alert-border);
|
|
1525
|
+
border-inline-start-color: var(--alert-border-inline-start-color, var(--alert-border));
|
|
1442
1526
|
border-inline-start-width: var(--alert-border-inline-start-width, var(--border-width));
|
|
1443
|
-
border-radius: var(--radius-lg);
|
|
1527
|
+
border-radius: var(--alert-radius, var(--radius-lg));
|
|
1444
1528
|
font-size: var(--alert-font-size);
|
|
1445
1529
|
}
|
|
1446
1530
|
|
|
@@ -1456,19 +1540,8 @@ a.badge[aria-disabled="true"] {
|
|
|
1456
1540
|
margin-block-end: 0;
|
|
1457
1541
|
}
|
|
1458
1542
|
|
|
1459
|
-
/* Alerts are soft by default.
|
|
1460
|
-
.alert.
|
|
1461
|
-
--ui-bg: var(--intent, var(--neutral));
|
|
1462
|
-
--ui-fg: var(--intent-fg, var(--neutral-fg));
|
|
1463
|
-
--ui-border: transparent;
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
.alert.outline {
|
|
1467
|
-
--ui-bg: transparent;
|
|
1468
|
-
--ui-fg: var(--intent, var(--text));
|
|
1469
|
-
--ui-border: var(--intent, var(--border));
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1543
|
+
/* Alerts are soft by default. .solid and .outline need no local rules — the
|
|
1544
|
+
shared variant plumbing in variants.css sets --ui-* that alert consumes. */
|
|
1472
1545
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
1473
1546
|
.alert:where(:not(.solid, .outline, .callout)) {
|
|
1474
1547
|
/* @sync soft-recipe */
|
|
@@ -1482,8 +1555,8 @@ a.badge[aria-disabled="true"] {
|
|
|
1482
1555
|
}
|
|
1483
1556
|
}
|
|
1484
1557
|
|
|
1485
|
-
/* Decorative leading icon */
|
|
1486
|
-
.alert >
|
|
1558
|
+
/* Decorative leading icon — use .alert-icon on the icon element. */
|
|
1559
|
+
.alert > .alert-icon {
|
|
1487
1560
|
display: inline-block;
|
|
1488
1561
|
flex: none;
|
|
1489
1562
|
inline-size: var(--alert-icon-size);
|
|
@@ -1493,33 +1566,84 @@ a.badge[aria-disabled="true"] {
|
|
|
1493
1566
|
vertical-align: -0.125em;
|
|
1494
1567
|
}
|
|
1495
1568
|
|
|
1569
|
+
/* Dismiss — compact close button for inline alerts. Transparent at rest with
|
|
1570
|
+
a reduced opacity glyph so it stays secondary inside an already-colored
|
|
1571
|
+
alert; hover reinforces it via the shared overlay. Uses the shared
|
|
1572
|
+
--icon-close mask, so no icon font or text glyph is required in the markup.
|
|
1573
|
+
Standard alerts place it as a direct trailing child; admonitions place it
|
|
1574
|
+
inside .alert-title (see below). */
|
|
1575
|
+
.alert-dismiss {
|
|
1576
|
+
--alert-dismiss-size: 1.75rem;
|
|
1577
|
+
--alert-dismiss-icon-size: 0.875rem;
|
|
1578
|
+
|
|
1579
|
+
display: inline-grid;
|
|
1580
|
+
place-items: center;
|
|
1581
|
+
inline-size: var(--alert-dismiss-size);
|
|
1582
|
+
block-size: var(--alert-dismiss-size);
|
|
1583
|
+
flex: none;
|
|
1584
|
+
padding: 0;
|
|
1585
|
+
border: 0;
|
|
1586
|
+
border-radius: var(--radius);
|
|
1587
|
+
background: transparent;
|
|
1588
|
+
color: currentColor;
|
|
1589
|
+
opacity: 0.65;
|
|
1590
|
+
cursor: pointer;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
.alert-dismiss::before {
|
|
1594
|
+
content: "";
|
|
1595
|
+
inline-size: var(--alert-dismiss-icon-size);
|
|
1596
|
+
block-size: var(--alert-dismiss-icon-size);
|
|
1597
|
+
background: currentColor;
|
|
1598
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
1599
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
@media (hover: hover) {
|
|
1603
|
+
.alert-dismiss:hover {
|
|
1604
|
+
background: var(--hover-overlay);
|
|
1605
|
+
opacity: 1;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/* Grid anatomy — activated by a leading icon, a trailing dismiss button, or
|
|
1610
|
+
both. Icon and dismiss are pinned to the outer columns; anything else (the
|
|
1611
|
+
message content) flows into the remaining text column. */
|
|
1496
1612
|
@supports selector(.alert:has(> *)) {
|
|
1497
|
-
.alert:has(>
|
|
1613
|
+
.alert:has(> .alert-icon, > .alert-dismiss) {
|
|
1498
1614
|
display: grid;
|
|
1499
|
-
grid-template-columns:
|
|
1615
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1500
1616
|
align-items: var(--items-align, start);
|
|
1501
|
-
column-gap: var(--space-
|
|
1617
|
+
column-gap: var(--space-30);
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
.alert:has(> .alert-icon) {
|
|
1621
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1502
1622
|
}
|
|
1503
1623
|
|
|
1504
|
-
.alert:has(>
|
|
1624
|
+
.alert:has(> .alert-icon) > .alert-icon {
|
|
1505
1625
|
grid-column: 1;
|
|
1506
1626
|
grid-row: 1 / -1;
|
|
1507
1627
|
display: inline-grid;
|
|
1508
1628
|
place-items: center;
|
|
1509
|
-
inline-size: var(--alert-icon-size);
|
|
1510
|
-
block-size: var(--alert-icon-size);
|
|
1511
1629
|
margin-block-start: 0.125em;
|
|
1512
|
-
font-size: var(--alert-icon-size);
|
|
1513
|
-
line-height: 1;
|
|
1514
1630
|
}
|
|
1515
1631
|
|
|
1516
|
-
.alert:has(>
|
|
1517
|
-
|
|
1518
|
-
block-size: var(--alert-icon-size);
|
|
1632
|
+
.alert:has(> .alert-icon) > :not(.alert-icon, .alert-dismiss) {
|
|
1633
|
+
grid-column: 2;
|
|
1519
1634
|
}
|
|
1520
1635
|
|
|
1521
|
-
.alert:has(>
|
|
1522
|
-
grid-
|
|
1636
|
+
.alert:has(> .alert-dismiss) {
|
|
1637
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
.alert:has(> .alert-icon):has(> .alert-dismiss) {
|
|
1641
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
.alert:has(> .alert-dismiss) > .alert-dismiss {
|
|
1645
|
+
grid-column: -2 / -1;
|
|
1646
|
+
grid-row: 1;
|
|
1523
1647
|
}
|
|
1524
1648
|
}
|
|
1525
1649
|
|
|
@@ -1534,100 +1658,84 @@ a.badge[aria-disabled="true"] {
|
|
|
1534
1658
|
Canvas backplate (unreadable) — so .solid intentionally falls back to the
|
|
1535
1659
|
default look. */
|
|
1536
1660
|
|
|
1537
|
-
/* Callout — neutral surface with
|
|
1538
|
-
The
|
|
1539
|
-
outer border-radius, so it never needs its own radius. */
|
|
1661
|
+
/* Callout — neutral surface with a thick accent border on the leading edge.
|
|
1662
|
+
The thickness follows the shared --alert-border-inline-start-width hook. */
|
|
1540
1663
|
.alert.callout {
|
|
1541
1664
|
--alert-bg: var(--surface-subtle);
|
|
1542
|
-
--
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
padding-inline-start: calc(var(--alert-pad-inline) + var(--space-3));
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
.alert.callout::before {
|
|
1550
|
-
content: "";
|
|
1551
|
-
position: absolute;
|
|
1552
|
-
inset-block: var(--space-2);
|
|
1553
|
-
inset-inline-start: var(--space-2);
|
|
1554
|
-
inline-size: 0.25rem;
|
|
1555
|
-
border-radius: var(--radius-full);
|
|
1556
|
-
background: var(--intent, var(--neutral));
|
|
1665
|
+
--alert-fg: var(--text);
|
|
1666
|
+
border: 0;
|
|
1667
|
+
border-inline-start: var(--alert-border-inline-start-width, 4px) solid
|
|
1668
|
+
var(--alert-border-inline-start-color, var(--intent, var(--neutral)));
|
|
1557
1669
|
}
|
|
1558
1670
|
|
|
1559
|
-
/* Admonition — structured
|
|
1560
|
-
|
|
1671
|
+
/* Admonition — structured static box with a tinted title bar and a body
|
|
1672
|
+
that rests on the page surface. Not collapsible — use .accordion inside
|
|
1673
|
+
the body if the content needs to be expandable. */
|
|
1561
1674
|
.alert.admonition {
|
|
1562
1675
|
padding: 0;
|
|
1563
|
-
gap: 0;
|
|
1564
1676
|
display: block;
|
|
1565
1677
|
overflow: clip;
|
|
1678
|
+
border-color: var(--intent, var(--border));
|
|
1566
1679
|
}
|
|
1567
1680
|
|
|
1568
|
-
.alert-title {
|
|
1681
|
+
.alert.admonition > .alert-title {
|
|
1682
|
+
display: flex;
|
|
1683
|
+
align-items: center;
|
|
1684
|
+
gap: var(--space-20);
|
|
1569
1685
|
padding-block: var(--alert-pad-block);
|
|
1570
1686
|
padding-inline: var(--alert-pad-inline);
|
|
1687
|
+
border-start-start-radius: calc(var(--alert-radius, var(--radius-lg)) - var(--border-width));
|
|
1688
|
+
border-start-end-radius: calc(var(--alert-radius, var(--radius-lg)) - var(--border-width));
|
|
1689
|
+
}
|
|
1571
1690
|
|
|
1572
|
-
|
|
1691
|
+
/* Dismiss inside an admonition title bar rides the inline end. */
|
|
1692
|
+
.alert.admonition > .alert-title > .alert-dismiss {
|
|
1693
|
+
margin-inline-start: auto;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
1697
|
+
.alert.admonition > .alert-title {
|
|
1573
1698
|
background: color-mix(
|
|
1574
1699
|
in oklch,
|
|
1575
1700
|
var(--surface) var(--soft-bg-mix),
|
|
1576
1701
|
var(--intent, var(--neutral))
|
|
1577
1702
|
);
|
|
1578
1703
|
}
|
|
1704
|
+
}
|
|
1579
1705
|
|
|
1580
|
-
|
|
1706
|
+
@supports not (color: color-mix(in oklch, red, white)) {
|
|
1707
|
+
.alert.admonition > .alert-title {
|
|
1581
1708
|
background: var(--intent, var(--neutral));
|
|
1709
|
+
color: var(--intent-fg, var(--neutral-fg));
|
|
1582
1710
|
}
|
|
1583
|
-
|
|
1584
|
-
border-start-start-radius: calc(var(--radius-lg) - var(--border-width));
|
|
1585
|
-
border-start-end-radius: calc(var(--radius-lg) - var(--border-width));
|
|
1586
1711
|
}
|
|
1587
1712
|
|
|
1588
|
-
.alert-
|
|
1589
|
-
cursor: pointer;
|
|
1590
|
-
user-select: none;
|
|
1591
|
-
list-style: none;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
.alert-title:where(summary)::-webkit-details-marker {
|
|
1595
|
-
display: none;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
.alert-title[aria-hidden] {
|
|
1599
|
-
display: inline-flex;
|
|
1600
|
-
gap: var(--space-2);
|
|
1601
|
-
align-items: center;
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
.alert-body {
|
|
1713
|
+
.alert.admonition > .alert-body {
|
|
1605
1714
|
padding-block: var(--alert-pad-block);
|
|
1606
1715
|
padding-inline: var(--alert-pad-inline);
|
|
1716
|
+
background: var(--surface);
|
|
1607
1717
|
color: var(--text);
|
|
1608
1718
|
}
|
|
1609
1719
|
|
|
1610
|
-
.alert-body > :first-child {
|
|
1720
|
+
.alert.admonition > .alert-body > :first-child {
|
|
1611
1721
|
margin-block-start: 0;
|
|
1612
1722
|
}
|
|
1613
1723
|
|
|
1614
|
-
.alert-body > :last-child {
|
|
1724
|
+
.alert.admonition > .alert-body > :last-child {
|
|
1615
1725
|
margin-block-end: 0;
|
|
1616
1726
|
}
|
|
1617
1727
|
|
|
1618
|
-
details.alert.admonition[open] .alert-title {
|
|
1619
|
-
border-end-start-radius: 0;
|
|
1620
|
-
border-end-end-radius: 0;
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
1728
|
/*
|
|
1624
1729
|
* Actions — a horizontal action-button cluster.
|
|
1625
1730
|
*
|
|
1626
1731
|
* Composes with .btn (or any inline element) and feeds layout via the shared
|
|
1627
1732
|
* --gap token.
|
|
1733
|
+
*
|
|
1734
|
+
* Internal:
|
|
1735
|
+
* --gap (local value for the shared gap token)
|
|
1628
1736
|
*/
|
|
1629
1737
|
.actions {
|
|
1630
|
-
--gap: var(--space-
|
|
1738
|
+
--gap: var(--space-20);
|
|
1631
1739
|
|
|
1632
1740
|
display: flex;
|
|
1633
1741
|
flex-wrap: wrap;
|
|
@@ -1650,28 +1758,36 @@ details.alert.admonition[open] .alert-title {
|
|
|
1650
1758
|
* Surface, border, radius, padding, density, and elevation stay local here.
|
|
1651
1759
|
* Direct header/footer children are structural slots. Page layout remains the
|
|
1652
1760
|
* job of markup and layout primitives outside the card.
|
|
1761
|
+
*
|
|
1762
|
+
* Public hooks:
|
|
1763
|
+
* --card-pad
|
|
1764
|
+
* --card-radius
|
|
1765
|
+
* --card-max-inline-size
|
|
1766
|
+
*
|
|
1767
|
+
* Child contract:
|
|
1768
|
+
* Direct .bleed children escape --card-pad (negative inset, wider media);
|
|
1769
|
+
* edge children inherit the card corners. .bleed must be a direct child.
|
|
1770
|
+
*
|
|
1771
|
+
* Internal:
|
|
1772
|
+
* --heading
|
|
1773
|
+
* --busy-overlay-bg
|
|
1653
1774
|
*/
|
|
1654
1775
|
/* Intent boundary — inherited intents do not tint this component.
|
|
1655
1776
|
Local intent classes still win because :where() has zero specificity. */
|
|
1656
1777
|
:where(.card) {
|
|
1657
|
-
/* @sync intent-boundary */
|
|
1658
|
-
--intent: initial;
|
|
1659
|
-
--intent-fg: initial;
|
|
1660
1778
|
--ui-bg: initial;
|
|
1661
1779
|
--ui-fg: initial;
|
|
1662
1780
|
--ui-border: initial;
|
|
1663
|
-
--ui-hover-bg: initial;
|
|
1664
|
-
/* @sync intent-boundary */
|
|
1665
1781
|
}
|
|
1666
1782
|
|
|
1667
1783
|
.card {
|
|
1668
|
-
--card-pad: var(--space-
|
|
1784
|
+
--card-pad: var(--space-40);
|
|
1669
1785
|
--card-radius: var(--radius-lg);
|
|
1670
1786
|
--card-max-inline-size: none;
|
|
1671
1787
|
|
|
1672
1788
|
max-inline-size: var(--card-max-inline-size);
|
|
1673
1789
|
padding: var(--card-pad);
|
|
1674
|
-
background: var(--ui-bg, var(--surface));
|
|
1790
|
+
background: var(--ui-bg, var(--surface-raised));
|
|
1675
1791
|
color: var(--ui-fg, var(--text));
|
|
1676
1792
|
border: var(--border-width) solid var(--ui-border, var(--border));
|
|
1677
1793
|
border-radius: var(--card-radius);
|
|
@@ -1686,7 +1802,7 @@ details.alert.admonition[open] .alert-title {
|
|
|
1686
1802
|
flex-wrap: wrap;
|
|
1687
1803
|
align-items: var(--items-align, center);
|
|
1688
1804
|
justify-content: space-between;
|
|
1689
|
-
gap: var(--space-
|
|
1805
|
+
gap: var(--space-30);
|
|
1690
1806
|
min-inline-size: 0;
|
|
1691
1807
|
}
|
|
1692
1808
|
|
|
@@ -1713,16 +1829,8 @@ details.alert.admonition[open] .alert-title {
|
|
|
1713
1829
|
.card.subtle {
|
|
1714
1830
|
background: var(--surface-subtle);
|
|
1715
1831
|
}
|
|
1716
|
-
.card.inverted {
|
|
1717
|
-
background: var(--surface-solid);
|
|
1718
|
-
color: var(--surface);
|
|
1719
|
-
border-color: transparent;
|
|
1720
|
-
--heading: var(--surface);
|
|
1721
|
-
--busy-overlay-bg: var(--surface-solid);
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
1832
|
.card.compact {
|
|
1725
|
-
--card-pad: var(--space-
|
|
1833
|
+
--card-pad: var(--space-20);
|
|
1726
1834
|
}
|
|
1727
1835
|
|
|
1728
1836
|
/* Full-bleed children */
|
|
@@ -1765,26 +1873,6 @@ details.alert.admonition[open] .alert-title {
|
|
|
1765
1873
|
padding: var(--card-pad);
|
|
1766
1874
|
}
|
|
1767
1875
|
|
|
1768
|
-
@media (forced-colors: active) {
|
|
1769
|
-
.card {
|
|
1770
|
-
border-color: ButtonText;
|
|
1771
|
-
background: Canvas;
|
|
1772
|
-
color: CanvasText;
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
.card.raised {
|
|
1776
|
-
box-shadow: none;
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
.card.inverted {
|
|
1780
|
-
--heading: CanvasText;
|
|
1781
|
-
|
|
1782
|
-
border-color: ButtonText;
|
|
1783
|
-
background: Canvas;
|
|
1784
|
-
color: CanvasText;
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
1876
|
/*
|
|
1789
1877
|
* Surface — shared modal scrim and backdrop for flyout and drawer.
|
|
1790
1878
|
*
|
|
@@ -1806,7 +1894,7 @@ details.alert.admonition[open] .alert-title {
|
|
|
1806
1894
|
z-index: calc(var(--z-menu) - 1);
|
|
1807
1895
|
background: var(--backdrop-fill);
|
|
1808
1896
|
opacity: 1;
|
|
1809
|
-
transition: opacity var(--duration) var(--ease
|
|
1897
|
+
transition: opacity var(--duration) var(--ease);
|
|
1810
1898
|
}
|
|
1811
1899
|
|
|
1812
1900
|
.surface-backdrop[hidden] {
|
|
@@ -1823,28 +1911,40 @@ details.alert.admonition[open] .alert-title {
|
|
|
1823
1911
|
@supports (transition-behavior: allow-discrete) {
|
|
1824
1912
|
.surface-backdrop {
|
|
1825
1913
|
transition:
|
|
1826
|
-
opacity var(--duration) var(--ease
|
|
1914
|
+
opacity var(--duration) var(--ease),
|
|
1827
1915
|
display var(--duration) allow-discrete;
|
|
1828
1916
|
}
|
|
1829
1917
|
}
|
|
1830
1918
|
|
|
1831
|
-
@media (forced-colors: active) {
|
|
1832
|
-
.surface-backdrop {
|
|
1833
|
-
background: Canvas;
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
1919
|
/*
|
|
1838
1920
|
* Modal shell styling for native dialog elements.
|
|
1839
1921
|
*
|
|
1840
1922
|
* Spacing, radius, transitions, and focus treatment come from shared tokens.
|
|
1841
1923
|
* Opening behavior and intent-specific decisions stay outside this component.
|
|
1924
|
+
*
|
|
1925
|
+
* Public hooks:
|
|
1926
|
+
* --modal-size
|
|
1927
|
+
* --dialog-viewport-gap
|
|
1928
|
+
* --dialog-icon-size
|
|
1929
|
+
* --dialog-icon-glyph-size
|
|
1930
|
+
*
|
|
1931
|
+
* Internal:
|
|
1932
|
+
* --dialog-close-icon-size
|
|
1933
|
+
* --dialog-viewport-gap-2x
|
|
1842
1934
|
*/
|
|
1843
1935
|
dialog.modal {
|
|
1844
1936
|
--modal-size: 32rem;
|
|
1937
|
+
--dialog-viewport-gap: var(--space-40);
|
|
1938
|
+
/* calc() multiplication is not in the supported browser range, so the
|
|
1939
|
+
doubled gap is spelled out with an additive calc. */
|
|
1940
|
+
--dialog-viewport-gap-2x: calc(var(--dialog-viewport-gap) + var(--dialog-viewport-gap));
|
|
1845
1941
|
|
|
1846
|
-
|
|
1847
|
-
|
|
1942
|
+
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
1943
|
+
provides the containing block for the absolutely positioned close button. */
|
|
1944
|
+
position: fixed;
|
|
1945
|
+
|
|
1946
|
+
max-inline-size: min(calc(100% - var(--dialog-viewport-gap-2x)), var(--modal-size));
|
|
1947
|
+
max-block-size: calc(var(--viewport-block) - var(--dialog-viewport-gap-2x));
|
|
1848
1948
|
border: var(--border-width) solid var(--border);
|
|
1849
1949
|
border-radius: var(--radius-lg);
|
|
1850
1950
|
background: var(--surface-raised);
|
|
@@ -1868,11 +1968,18 @@ dialog.modal {
|
|
|
1868
1968
|
dialog.modal > form,
|
|
1869
1969
|
dialog.modal > .stack {
|
|
1870
1970
|
display: grid;
|
|
1871
|
-
gap: var(--space-
|
|
1872
|
-
max-block-size: calc(var(--viewport-block) -
|
|
1971
|
+
gap: var(--space-40);
|
|
1972
|
+
max-block-size: calc(var(--viewport-block) - var(--dialog-viewport-gap-2x));
|
|
1873
1973
|
overflow: auto;
|
|
1874
1974
|
overscroll-behavior: contain;
|
|
1875
|
-
padding: var(--space-
|
|
1975
|
+
padding: var(--space-40);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
/* Keep the dialog off the viewport edges on small screens. */
|
|
1979
|
+
@media (max-width: 30rem) {
|
|
1980
|
+
dialog.modal {
|
|
1981
|
+
--dialog-viewport-gap: var(--space-30);
|
|
1982
|
+
}
|
|
1876
1983
|
}
|
|
1877
1984
|
|
|
1878
1985
|
dialog.modal.scrollable > form,
|
|
@@ -1898,6 +2005,13 @@ dialog.modal footer > :last-child {
|
|
|
1898
2005
|
margin-block-end: 0;
|
|
1899
2006
|
}
|
|
1900
2007
|
|
|
2008
|
+
/* The close button is anchored to the dialog at top inline-end, out of the
|
|
2009
|
+
content flow; headers reserve inline room so titles never run under it. */
|
|
2010
|
+
dialog.modal > form > header,
|
|
2011
|
+
dialog.modal > .stack > header {
|
|
2012
|
+
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
2013
|
+
}
|
|
2014
|
+
|
|
1901
2015
|
dialog.modal footer {
|
|
1902
2016
|
display: flex;
|
|
1903
2017
|
flex-wrap: wrap;
|
|
@@ -1905,6 +2019,65 @@ dialog.modal footer {
|
|
|
1905
2019
|
gap: var(--gap);
|
|
1906
2020
|
}
|
|
1907
2021
|
|
|
2022
|
+
/* Confirmation layout — a focused message followed by a visually distinct
|
|
2023
|
+
action band. The media primitive owns icon/content alignment; this variant
|
|
2024
|
+
only removes the standard form inset so both regions can own their padding. */
|
|
2025
|
+
dialog.modal.dialog-confirmation > form {
|
|
2026
|
+
gap: 0;
|
|
2027
|
+
padding: 0;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
dialog.modal.dialog-confirmation > form > .media {
|
|
2031
|
+
gap: var(--space-40);
|
|
2032
|
+
padding: var(--space-50);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
dialog.modal.dialog-confirmation > form > footer {
|
|
2036
|
+
padding: var(--space-40) var(--space-50);
|
|
2037
|
+
border-end-start-radius: calc(var(--radius-lg) - var(--border-width));
|
|
2038
|
+
border-end-end-radius: calc(var(--radius-lg) - var(--border-width));
|
|
2039
|
+
background: var(--surface-subtle);
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
/* Intent boundary — inherited intents do not tint the icon well. A local
|
|
2043
|
+
intent plus .soft opts into the shared soft-variant recipe. */
|
|
2044
|
+
:where(.dialog-icon) {
|
|
2045
|
+
--intent: initial;
|
|
2046
|
+
--intent-fg: initial;
|
|
2047
|
+
--ui-bg: initial;
|
|
2048
|
+
--ui-fg: initial;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
.dialog-icon {
|
|
2052
|
+
--dialog-icon-size: 3rem;
|
|
2053
|
+
--dialog-icon-glyph-size: 1.5rem;
|
|
2054
|
+
|
|
2055
|
+
display: inline-grid;
|
|
2056
|
+
place-items: center;
|
|
2057
|
+
inline-size: var(--dialog-icon-size);
|
|
2058
|
+
block-size: var(--dialog-icon-size);
|
|
2059
|
+
flex: none;
|
|
2060
|
+
border-radius: var(--radius-full);
|
|
2061
|
+
background: var(--ui-bg, var(--surface-subtle));
|
|
2062
|
+
color: var(--ui-fg, var(--intent, var(--text-muted)));
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
.dialog-icon > * {
|
|
2066
|
+
display: inline-grid;
|
|
2067
|
+
place-items: center;
|
|
2068
|
+
inline-size: var(--dialog-icon-glyph-size);
|
|
2069
|
+
block-size: var(--dialog-icon-glyph-size);
|
|
2070
|
+
font-size: var(--dialog-icon-glyph-size);
|
|
2071
|
+
line-height: 1;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
@media (max-width: 30rem) {
|
|
2075
|
+
dialog.modal.dialog-confirmation > form > .media,
|
|
2076
|
+
dialog.modal.dialog-confirmation > form > footer {
|
|
2077
|
+
padding: var(--space-40);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
|
|
1908
2081
|
dialog.modal[open] {
|
|
1909
2082
|
opacity: 1;
|
|
1910
2083
|
transform: none;
|
|
@@ -1960,7 +2133,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
1960
2133
|
|
|
1961
2134
|
@media (prefers-reduced-motion: no-preference) {
|
|
1962
2135
|
dialog.modal.is-static {
|
|
1963
|
-
animation: actual-
|
|
2136
|
+
animation: actual-dialog-static var(--duration-slow) var(--ease);
|
|
1964
2137
|
}
|
|
1965
2138
|
|
|
1966
2139
|
::view-transition-group(actual-dialog) {
|
|
@@ -1969,7 +2142,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
1969
2142
|
}
|
|
1970
2143
|
}
|
|
1971
2144
|
|
|
1972
|
-
@keyframes actual-
|
|
2145
|
+
@keyframes actual-dialog-static {
|
|
1973
2146
|
0%,
|
|
1974
2147
|
100% {
|
|
1975
2148
|
transform: none;
|
|
@@ -1995,6 +2168,70 @@ html.has-modal-open.had-scrollbar {
|
|
|
1995
2168
|
dialog.modal::backdrop {
|
|
1996
2169
|
background: Canvas;
|
|
1997
2170
|
}
|
|
2171
|
+
|
|
2172
|
+
.dialog-icon {
|
|
2173
|
+
border: var(--border-width) solid CanvasText;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/* Close controls. A close button is an action attached to a surface, so it
|
|
2178
|
+
never participates in the content flow. The modal and the drawer share the
|
|
2179
|
+
same simple placement — absolute at the panel's top inline-end, inside the
|
|
2180
|
+
surface — so both close buttons look and behave identically. The glyph comes
|
|
2181
|
+
from --icon-close, masked to currentColor, so no icon font or text glyph is
|
|
2182
|
+
required in the markup. */
|
|
2183
|
+
.dialog-close,
|
|
2184
|
+
.drawer-close {
|
|
2185
|
+
--dialog-close-icon-size: 1rem;
|
|
2186
|
+
|
|
2187
|
+
display: inline-grid;
|
|
2188
|
+
place-items: center;
|
|
2189
|
+
padding: 0;
|
|
2190
|
+
border: 0;
|
|
2191
|
+
border-radius: var(--radius);
|
|
2192
|
+
color: var(--text-muted);
|
|
2193
|
+
cursor: pointer;
|
|
2194
|
+
|
|
2195
|
+
/* Out of flow, anchored to the positioned dialog surface, not the header or
|
|
2196
|
+
any scroll container inside it. */
|
|
2197
|
+
position: absolute;
|
|
2198
|
+
z-index: 1;
|
|
2199
|
+
inset-block-start: var(--space-20);
|
|
2200
|
+
inset-inline-end: var(--space-20);
|
|
2201
|
+
|
|
2202
|
+
inline-size: var(--control-size);
|
|
2203
|
+
block-size: var(--control-size);
|
|
2204
|
+
|
|
2205
|
+
background: var(--surface-subtle);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
.dialog-close::before,
|
|
2209
|
+
.drawer-close::before {
|
|
2210
|
+
content: "";
|
|
2211
|
+
inline-size: var(--dialog-close-icon-size);
|
|
2212
|
+
block-size: var(--dialog-close-icon-size);
|
|
2213
|
+
background: currentColor;
|
|
2214
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
2215
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
@media (hover: hover) {
|
|
2219
|
+
.dialog-close:hover,
|
|
2220
|
+
.drawer-close:hover {
|
|
2221
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
2222
|
+
color: var(--text);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
.dialog-close:active,
|
|
2227
|
+
.drawer-close:active {
|
|
2228
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
.dialog-close:focus-visible,
|
|
2232
|
+
.drawer-close:focus-visible {
|
|
2233
|
+
outline: 2px solid transparent;
|
|
2234
|
+
box-shadow: var(--focus-ring-shadow);
|
|
1998
2235
|
}
|
|
1999
2236
|
|
|
2000
2237
|
/*
|
|
@@ -2005,16 +2242,30 @@ html.has-modal-open.had-scrollbar {
|
|
|
2005
2242
|
* discrete display/overlay transitions. The drawer is a layout shell only —
|
|
2006
2243
|
* navigation content inside uses .nav-link and .accordion, which carry their
|
|
2007
2244
|
* own link/summary styling.
|
|
2245
|
+
*
|
|
2246
|
+
* Public hooks:
|
|
2247
|
+
* --drawer-size
|
|
2248
|
+
* --drawer-pad
|
|
2249
|
+
*
|
|
2250
|
+
* Internal:
|
|
2251
|
+
* --drawer-closed-translate (side/RTL positioning state)
|
|
2008
2252
|
*/
|
|
2009
2253
|
dialog.drawer {
|
|
2010
2254
|
--drawer-size: 20rem;
|
|
2011
|
-
--drawer-pad: var(--space-
|
|
2255
|
+
--drawer-pad: var(--space-40);
|
|
2012
2256
|
--drawer-closed-translate: -100%;
|
|
2013
2257
|
|
|
2258
|
+
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
2259
|
+
provides the containing block for the absolutely positioned close button.
|
|
2260
|
+
A percentage height resolves against the fixed containing viewport, while
|
|
2261
|
+
dynamic viewport units can be stale in embedded previews. */
|
|
2262
|
+
position: fixed;
|
|
2263
|
+
inset-block-start: 0;
|
|
2264
|
+
|
|
2014
2265
|
inline-size: min(calc(var(--viewport-inline) * 0.9), var(--drawer-size));
|
|
2015
|
-
block-size:
|
|
2266
|
+
block-size: 100%;
|
|
2016
2267
|
max-inline-size: none;
|
|
2017
|
-
max-block-size:
|
|
2268
|
+
max-block-size: 100%;
|
|
2018
2269
|
margin: 0;
|
|
2019
2270
|
padding: var(--drawer-pad);
|
|
2020
2271
|
border: 0;
|
|
@@ -2033,7 +2284,7 @@ dialog.drawer {
|
|
|
2033
2284
|
dialog.drawer[open] {
|
|
2034
2285
|
display: grid;
|
|
2035
2286
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
2036
|
-
gap: var(--space-
|
|
2287
|
+
gap: var(--space-40);
|
|
2037
2288
|
opacity: 1;
|
|
2038
2289
|
transform: translateX(0);
|
|
2039
2290
|
}
|
|
@@ -2104,20 +2355,23 @@ dialog.drawer nav {
|
|
|
2104
2355
|
}
|
|
2105
2356
|
|
|
2106
2357
|
@media (forced-colors: active) {
|
|
2107
|
-
dialog.drawer {
|
|
2108
|
-
border-inline-end-color: ButtonText;
|
|
2358
|
+
dialog.drawer::backdrop {
|
|
2109
2359
|
background: Canvas;
|
|
2110
|
-
color: CanvasText;
|
|
2111
|
-
box-shadow: none;
|
|
2112
2360
|
}
|
|
2361
|
+
}
|
|
2113
2362
|
|
|
2114
|
-
|
|
2115
|
-
|
|
2363
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
2364
|
+
dialog.drawer.is-static {
|
|
2365
|
+
animation: actual-dialog-static var(--duration-slow) var(--ease);
|
|
2116
2366
|
}
|
|
2367
|
+
}
|
|
2117
2368
|
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2369
|
+
/* The close button shares the modal's interior placement (modal.css): absolute
|
|
2370
|
+
at the panel's top inline-end, out of the content flow. The header reserves
|
|
2371
|
+
inline room so the title never runs under it. */
|
|
2372
|
+
dialog.drawer > header,
|
|
2373
|
+
dialog.drawer > form > header {
|
|
2374
|
+
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
2121
2375
|
}
|
|
2122
2376
|
|
|
2123
2377
|
|
|
@@ -2133,7 +2387,16 @@ dialog.drawer nav {
|
|
|
2133
2387
|
* .is-sheet marks "auto" resolved to bottom-sheet presentation on coarse
|
|
2134
2388
|
* pointers. Authors opt into mobile behavior with
|
|
2135
2389
|
* [data-flyout-mobile="sheet|auto|none|anchored"].
|
|
2136
|
-
* --
|
|
2390
|
+
* --surface-anchor-width is set by surface.js while an open flyout is positioned.
|
|
2391
|
+
*
|
|
2392
|
+
* Public hooks:
|
|
2393
|
+
* --flyout-inline-size
|
|
2394
|
+
* --flyout-max-inline-size
|
|
2395
|
+
*
|
|
2396
|
+
* Internal:
|
|
2397
|
+
* --menu-item-size (derived from --control-size for menu rows)
|
|
2398
|
+
* --available-height (measured by floating.js)
|
|
2399
|
+
* --surface-anchor-width (measured by surface.js)
|
|
2137
2400
|
*/
|
|
2138
2401
|
|
|
2139
2402
|
.flyout-trigger {
|
|
@@ -2156,28 +2419,32 @@ dialog.drawer nav {
|
|
|
2156
2419
|
}
|
|
2157
2420
|
|
|
2158
2421
|
.flyout {
|
|
2422
|
+
--menu-item-size: var(--control-size);
|
|
2159
2423
|
z-index: var(--z-menu);
|
|
2160
2424
|
/* No-JS fallback: below the trigger inside the relative .flyout-trigger.
|
|
2161
2425
|
surface.js switches an open flyout to position: fixed with measured
|
|
2162
2426
|
coordinates; this only covers a flyout revealed without the runtime. */
|
|
2163
2427
|
position: absolute;
|
|
2164
|
-
inset-block-start: calc(100% + var(--space-
|
|
2428
|
+
inset-block-start: calc(100% + var(--space-10));
|
|
2165
2429
|
inset-inline-start: 0;
|
|
2166
2430
|
display: grid;
|
|
2167
|
-
gap: var(--space-
|
|
2431
|
+
gap: var(--space-10);
|
|
2168
2432
|
inline-size: var(--flyout-inline-size, max-content);
|
|
2169
2433
|
min-inline-size: min(
|
|
2170
|
-
var(--
|
|
2171
|
-
min(var(--flyout-max-inline-size, 20rem), calc(var(--viewport-inline) - var(--space-
|
|
2434
|
+
var(--surface-anchor-width, 0px),
|
|
2435
|
+
min(var(--flyout-max-inline-size, 20rem), calc(var(--viewport-inline) - var(--space-60)))
|
|
2172
2436
|
);
|
|
2173
2437
|
max-inline-size: min(
|
|
2174
2438
|
var(--flyout-max-inline-size, 20rem),
|
|
2175
|
-
calc(var(--viewport-inline) - var(--space-
|
|
2439
|
+
calc(var(--viewport-inline) - var(--space-60))
|
|
2440
|
+
);
|
|
2441
|
+
max-block-size: min(
|
|
2442
|
+
24rem,
|
|
2443
|
+
var(--available-height, calc(var(--viewport-block) - var(--space-60)))
|
|
2176
2444
|
);
|
|
2177
|
-
max-block-size: min(24rem, var(--available-height, calc(var(--viewport-block) - var(--space-6))));
|
|
2178
2445
|
overflow: auto;
|
|
2179
2446
|
margin: 0;
|
|
2180
|
-
padding: var(--space-
|
|
2447
|
+
padding: var(--space-10);
|
|
2181
2448
|
border: var(--border-width) solid var(--border);
|
|
2182
2449
|
border-radius: var(--radius);
|
|
2183
2450
|
background: var(--surface-raised);
|
|
@@ -2186,9 +2453,9 @@ dialog.drawer nav {
|
|
|
2186
2453
|
opacity: 1;
|
|
2187
2454
|
scale: 1;
|
|
2188
2455
|
transition:
|
|
2189
|
-
opacity var(--duration-fast
|
|
2190
|
-
scale var(--duration-fast
|
|
2191
|
-
translate var(--duration-fast
|
|
2456
|
+
opacity var(--duration-fast) var(--ease),
|
|
2457
|
+
scale var(--duration-fast) var(--ease),
|
|
2458
|
+
translate var(--duration-fast) var(--ease);
|
|
2192
2459
|
}
|
|
2193
2460
|
|
|
2194
2461
|
.flyout.is-open:not(.is-sheet) {
|
|
@@ -2205,6 +2472,7 @@ dialog.drawer nav {
|
|
|
2205
2472
|
/* Bottom-sheet presentation. surface.js toggles .is-sheet when the flyout is
|
|
2206
2473
|
opened on coarse pointer / narrow viewports. */
|
|
2207
2474
|
.flyout.is-sheet {
|
|
2475
|
+
--menu-item-size: var(--control-size-lg);
|
|
2208
2476
|
position: fixed;
|
|
2209
2477
|
inset-inline: 0;
|
|
2210
2478
|
inset-block-start: auto;
|
|
@@ -2215,8 +2483,8 @@ dialog.drawer nav {
|
|
|
2215
2483
|
max-block-size: min(calc(var(--viewport-block) * 0.75), 32rem);
|
|
2216
2484
|
overflow: auto;
|
|
2217
2485
|
grid-template-columns: 1fr;
|
|
2218
|
-
padding: max(var(--space-
|
|
2219
|
-
max(var(--space-
|
|
2486
|
+
padding: max(var(--space-30), env(safe-area-inset-top)) var(--space-30)
|
|
2487
|
+
max(var(--space-30), env(safe-area-inset-bottom));
|
|
2220
2488
|
border-end-start-radius: 0;
|
|
2221
2489
|
border-end-end-radius: 0;
|
|
2222
2490
|
}
|
|
@@ -2229,23 +2497,23 @@ dialog.drawer nav {
|
|
|
2229
2497
|
|
|
2230
2498
|
.flyout.is-sheet[hidden] {
|
|
2231
2499
|
scale: 1;
|
|
2232
|
-
translate: 0 var(--space-
|
|
2500
|
+
translate: 0 var(--space-30);
|
|
2233
2501
|
}
|
|
2234
2502
|
|
|
2235
2503
|
@starting-style {
|
|
2236
2504
|
.flyout.is-sheet.is-open {
|
|
2237
2505
|
scale: 1;
|
|
2238
|
-
translate: 0 var(--space-
|
|
2506
|
+
translate: 0 var(--space-30);
|
|
2239
2507
|
}
|
|
2240
2508
|
}
|
|
2241
2509
|
|
|
2242
2510
|
@supports (transition-behavior: allow-discrete) {
|
|
2243
2511
|
.flyout {
|
|
2244
2512
|
transition:
|
|
2245
|
-
opacity var(--duration-fast
|
|
2246
|
-
scale var(--duration-fast
|
|
2247
|
-
translate var(--duration-fast
|
|
2248
|
-
display var(--duration-fast
|
|
2513
|
+
opacity var(--duration-fast) var(--ease),
|
|
2514
|
+
scale var(--duration-fast) var(--ease),
|
|
2515
|
+
translate var(--duration-fast) var(--ease),
|
|
2516
|
+
display var(--duration-fast) allow-discrete;
|
|
2249
2517
|
}
|
|
2250
2518
|
}
|
|
2251
2519
|
|
|
@@ -2255,7 +2523,7 @@ dialog.drawer nav {
|
|
|
2255
2523
|
|
|
2256
2524
|
.flyout > :where(section, footer) {
|
|
2257
2525
|
min-inline-size: 0;
|
|
2258
|
-
padding: var(--space-
|
|
2526
|
+
padding: var(--space-20);
|
|
2259
2527
|
}
|
|
2260
2528
|
|
|
2261
2529
|
.flyout > section > :where(h1, h2, h3, h4, h5, h6) {
|
|
@@ -2267,8 +2535,8 @@ dialog.drawer nav {
|
|
|
2267
2535
|
|
|
2268
2536
|
.flyout > section > ul {
|
|
2269
2537
|
display: grid;
|
|
2270
|
-
gap: var(--space-
|
|
2271
|
-
margin: var(--space-
|
|
2538
|
+
gap: var(--space-10);
|
|
2539
|
+
margin: var(--space-10) 0 0;
|
|
2272
2540
|
padding: 0;
|
|
2273
2541
|
list-style: none;
|
|
2274
2542
|
}
|
|
@@ -2277,91 +2545,133 @@ dialog.drawer nav {
|
|
|
2277
2545
|
margin: 0;
|
|
2278
2546
|
}
|
|
2279
2547
|
|
|
2280
|
-
/*
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2548
|
+
/* Vertical dividers — .menu-separator (menu.css) is the canonical way to
|
|
2549
|
+
separate menu items. Flyout panels that are not menus use auto grid gap
|
|
2550
|
+
declared on .flyout itself. */
|
|
2551
|
+
|
|
2552
|
+
/*
|
|
2553
|
+
* Menu — action list primitive consumed by flyout and context-menu.
|
|
2554
|
+
*
|
|
2555
|
+
* Strict anatomy: .menu > li > .menu-item. Separators and section labels
|
|
2556
|
+
* sit directly under .menu as siblings of <li>.
|
|
2557
|
+
*
|
|
2558
|
+
* Public hooks:
|
|
2559
|
+
* --menu-item-icon-size
|
|
2560
|
+
*/
|
|
2561
|
+
|
|
2562
|
+
.menu {
|
|
2563
|
+
display: grid;
|
|
2564
|
+
gap: var(--space-10);
|
|
2565
|
+
margin: 0;
|
|
2566
|
+
padding: var(--space-10);
|
|
2567
|
+
list-style: none;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
.menu > li {
|
|
2571
|
+
margin: 0;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
.menu-item {
|
|
2575
|
+
--menu-item-icon-size: 1.25em;
|
|
2576
|
+
|
|
2286
2577
|
display: flex;
|
|
2287
2578
|
align-items: center;
|
|
2288
|
-
|
|
2289
|
-
gap: var(--space-2);
|
|
2579
|
+
gap: var(--space-20);
|
|
2290
2580
|
inline-size: 100%;
|
|
2291
|
-
min-block-size: var(--control-size);
|
|
2292
|
-
padding-inline: var(--space-
|
|
2581
|
+
min-block-size: var(--menu-item-size, var(--control-size));
|
|
2582
|
+
padding-inline: var(--space-20);
|
|
2293
2583
|
border: 0;
|
|
2294
2584
|
border-radius: var(--radius-sm);
|
|
2295
|
-
background:
|
|
2585
|
+
background: transparent;
|
|
2296
2586
|
color: var(--text);
|
|
2297
2587
|
font: inherit;
|
|
2298
|
-
font-weight: var(--font-weight);
|
|
2299
|
-
text-decoration: none;
|
|
2300
2588
|
text-align: start;
|
|
2589
|
+
text-decoration: none;
|
|
2301
2590
|
white-space: nowrap;
|
|
2302
2591
|
cursor: pointer;
|
|
2303
2592
|
}
|
|
2304
2593
|
|
|
2305
|
-
|
|
2306
|
-
|
|
2594
|
+
.menu-item-icon {
|
|
2595
|
+
display: grid;
|
|
2596
|
+
flex: 0 0 var(--menu-item-icon-size);
|
|
2597
|
+
inline-size: var(--menu-item-icon-size);
|
|
2598
|
+
place-items: center;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
.menu-item-text {
|
|
2602
|
+
flex: 1;
|
|
2603
|
+
min-inline-size: 0;
|
|
2604
|
+
overflow: hidden;
|
|
2605
|
+
text-overflow: ellipsis;
|
|
2307
2606
|
}
|
|
2308
2607
|
|
|
2309
|
-
|
|
2608
|
+
.menu-item-end {
|
|
2609
|
+
display: inline-flex;
|
|
2610
|
+
align-items: center;
|
|
2611
|
+
gap: var(--space-10);
|
|
2612
|
+
margin-inline-start: auto;
|
|
2613
|
+
color: var(--text-muted);
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/* Checkable menu items are one command with one accessible state. Their
|
|
2617
|
+
indicator is generated so authors do not add a second interactive input. */
|
|
2618
|
+
.menu-item:is([role="menuitemcheckbox"], [role="menuitemradio"])::before {
|
|
2619
|
+
display: grid;
|
|
2620
|
+
flex: 0 0 var(--menu-item-icon-size);
|
|
2621
|
+
inline-size: var(--menu-item-icon-size);
|
|
2622
|
+
place-items: center;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
.menu-item[role="menuitemcheckbox"]::before {
|
|
2626
|
+
content: "";
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
.menu-item[role="menuitemcheckbox"][aria-checked="true"]::before {
|
|
2630
|
+
content: "✓";
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
.menu-item[role="menuitemcheckbox"][aria-checked="mixed"]::before {
|
|
2634
|
+
content: "−";
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
.menu-item[role="menuitemradio"]::before {
|
|
2638
|
+
content: "○";
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
.menu-item[role="menuitemradio"][aria-checked="true"]::before {
|
|
2642
|
+
content: "●";
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
.menu-item:focus-visible {
|
|
2310
2646
|
background: var(--surface-subtle);
|
|
2311
|
-
outline: var(--focus-outline);
|
|
2312
|
-
outline-offset: var(--focus-outline-offset);
|
|
2313
2647
|
}
|
|
2314
2648
|
|
|
2315
|
-
:
|
|
2316
|
-
|
|
2317
|
-
cursor: not-allowed;
|
|
2318
|
-
opacity: var(--disabled-opacity);
|
|
2649
|
+
.menu-item:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
2650
|
+
background: var(--surface-subtle);
|
|
2319
2651
|
}
|
|
2320
2652
|
|
|
2321
|
-
:is(
|
|
2322
|
-
|
|
2653
|
+
.menu-item:is(:disabled, [aria-disabled="true"]) {
|
|
2654
|
+
cursor: not-allowed;
|
|
2655
|
+
opacity: var(--disabled-opacity);
|
|
2323
2656
|
}
|
|
2324
2657
|
|
|
2325
|
-
|
|
2326
|
-
:
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
background: var(--surface-subtle);
|
|
2331
|
-
outline: none;
|
|
2332
|
-
}
|
|
2658
|
+
.menu-label {
|
|
2659
|
+
padding: var(--space-20);
|
|
2660
|
+
color: var(--text-muted);
|
|
2661
|
+
font-size: var(--font-size-xs);
|
|
2662
|
+
font-weight: var(--font-weight-strong);
|
|
2333
2663
|
}
|
|
2334
2664
|
|
|
2335
|
-
.
|
|
2336
|
-
.flyout > li > hr {
|
|
2337
|
-
inline-size: 100%;
|
|
2665
|
+
.menu-separator {
|
|
2338
2666
|
margin: 0;
|
|
2339
|
-
margin-block: var(--space-1);
|
|
2340
2667
|
border: 0;
|
|
2341
2668
|
border-block-start: var(--border-width) solid var(--border);
|
|
2342
2669
|
}
|
|
2343
2670
|
|
|
2344
2671
|
@media (forced-colors: active) {
|
|
2345
|
-
.
|
|
2346
|
-
border-
|
|
2347
|
-
|
|
2348
|
-
color: CanvasText;
|
|
2349
|
-
box-shadow: none;
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
:is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]):focus-visible {
|
|
2353
|
-
background: Canvas;
|
|
2354
|
-
color: CanvasText;
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
@media (hover: hover) {
|
|
2358
|
-
:is(.flyout, .flyout :where(li))
|
|
2359
|
-
> :where(button, a, [role="menuitem"]):hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
2360
|
-
outline: var(--border-width) solid ButtonText;
|
|
2361
|
-
outline-offset: -1px;
|
|
2362
|
-
background: Canvas;
|
|
2363
|
-
color: CanvasText;
|
|
2364
|
-
}
|
|
2672
|
+
.menu-item:focus-visible {
|
|
2673
|
+
outline: var(--border-width) solid ButtonText;
|
|
2674
|
+
outline-offset: -1px;
|
|
2365
2675
|
}
|
|
2366
2676
|
}
|
|
2367
2677
|
|
|
@@ -2371,6 +2681,15 @@ dialog.drawer nav {
|
|
|
2371
2681
|
* Position, placement, and arrow position are managed by JavaScript
|
|
2372
2682
|
* via position: fixed, left, top, data-placement, and --arrow-x.
|
|
2373
2683
|
* Visibility is managed with hidden; CSS owns entry and exit animation.
|
|
2684
|
+
*
|
|
2685
|
+
* Public hooks:
|
|
2686
|
+
* --tooltip-bg
|
|
2687
|
+
* --tooltip-fg
|
|
2688
|
+
* --tooltip-arrow-size
|
|
2689
|
+
*
|
|
2690
|
+
* Internal:
|
|
2691
|
+
* --arrow-x (arrow position, written by floating.js)
|
|
2692
|
+
* --arrow-y (arrow position, written by floating.js)
|
|
2374
2693
|
*/
|
|
2375
2694
|
.tooltip {
|
|
2376
2695
|
--tooltip-bg: var(--surface-solid);
|
|
@@ -2392,8 +2711,8 @@ dialog.drawer nav {
|
|
|
2392
2711
|
opacity: 1;
|
|
2393
2712
|
scale: 1;
|
|
2394
2713
|
transition:
|
|
2395
|
-
opacity var(--duration-fast
|
|
2396
|
-
scale var(--duration-fast
|
|
2714
|
+
opacity var(--duration-fast) var(--ease),
|
|
2715
|
+
scale var(--duration-fast) var(--ease);
|
|
2397
2716
|
}
|
|
2398
2717
|
|
|
2399
2718
|
.tooltip[hidden] {
|
|
@@ -2412,9 +2731,9 @@ dialog.drawer nav {
|
|
|
2412
2731
|
@supports (transition-behavior: allow-discrete) {
|
|
2413
2732
|
.tooltip {
|
|
2414
2733
|
transition:
|
|
2415
|
-
opacity var(--duration-fast
|
|
2416
|
-
scale var(--duration-fast
|
|
2417
|
-
display var(--duration-fast
|
|
2734
|
+
opacity var(--duration-fast) var(--ease),
|
|
2735
|
+
scale var(--duration-fast) var(--ease),
|
|
2736
|
+
display var(--duration-fast) allow-discrete;
|
|
2418
2737
|
}
|
|
2419
2738
|
}
|
|
2420
2739
|
|
|
@@ -2464,8 +2783,6 @@ dialog.drawer nav {
|
|
|
2464
2783
|
.tooltip {
|
|
2465
2784
|
--tooltip-bg: Canvas;
|
|
2466
2785
|
--tooltip-fg: CanvasText;
|
|
2467
|
-
|
|
2468
|
-
border: var(--border-width) solid ButtonText;
|
|
2469
2786
|
}
|
|
2470
2787
|
|
|
2471
2788
|
.tooltip::before {
|
|
@@ -2481,9 +2798,14 @@ dialog.drawer nav {
|
|
|
2481
2798
|
* Keeps the clickable summary affordance aligned and usable without JavaScript
|
|
2482
2799
|
* or demo styles. Decorative palettes and custom markers belong outside the
|
|
2483
2800
|
* base component.
|
|
2801
|
+
*
|
|
2802
|
+
* Public hooks:
|
|
2803
|
+
* --accordion-radius
|
|
2804
|
+
* --accordion-marker-color
|
|
2484
2805
|
*/
|
|
2485
2806
|
.accordion {
|
|
2486
2807
|
--accordion-radius: var(--radius);
|
|
2808
|
+
--accordion-marker-color: var(--text-muted);
|
|
2487
2809
|
|
|
2488
2810
|
border: var(--border-width) solid var(--border);
|
|
2489
2811
|
border-radius: var(--accordion-radius);
|
|
@@ -2497,14 +2819,18 @@ dialog.drawer nav {
|
|
|
2497
2819
|
.accordion summary {
|
|
2498
2820
|
display: flex;
|
|
2499
2821
|
align-items: center;
|
|
2500
|
-
gap: var(--space-
|
|
2822
|
+
gap: var(--space-30);
|
|
2501
2823
|
cursor: pointer;
|
|
2502
2824
|
list-style: none;
|
|
2503
|
-
padding: var(--space-
|
|
2825
|
+
padding: var(--space-40);
|
|
2504
2826
|
color: var(--text);
|
|
2505
2827
|
font-weight: var(--font-weight-bold);
|
|
2506
2828
|
}
|
|
2507
2829
|
|
|
2830
|
+
.accordion summary:hover {
|
|
2831
|
+
color: var(--primary);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2508
2834
|
.accordion summary::-webkit-details-marker {
|
|
2509
2835
|
display: none;
|
|
2510
2836
|
}
|
|
@@ -2515,15 +2841,9 @@ dialog.drawer nav {
|
|
|
2515
2841
|
margin-inline-start: auto;
|
|
2516
2842
|
inline-size: 1rem;
|
|
2517
2843
|
block-size: 1rem;
|
|
2518
|
-
background-color: var(--
|
|
2519
|
-
-webkit-mask
|
|
2520
|
-
mask
|
|
2521
|
-
-webkit-mask-repeat: no-repeat;
|
|
2522
|
-
mask-repeat: no-repeat;
|
|
2523
|
-
-webkit-mask-size: contain;
|
|
2524
|
-
mask-size: contain;
|
|
2525
|
-
-webkit-mask-position: center;
|
|
2526
|
-
mask-position: center;
|
|
2844
|
+
background-color: var(--accordion-marker-color);
|
|
2845
|
+
-webkit-mask: var(--icon-chevron) center / contain no-repeat;
|
|
2846
|
+
mask: var(--icon-chevron) center / contain no-repeat;
|
|
2527
2847
|
transition: transform var(--duration);
|
|
2528
2848
|
}
|
|
2529
2849
|
|
|
@@ -2532,14 +2852,14 @@ dialog.drawer nav {
|
|
|
2532
2852
|
}
|
|
2533
2853
|
|
|
2534
2854
|
.accordion details > summary + * {
|
|
2535
|
-
padding-block: 0 var(--space-
|
|
2536
|
-
padding-inline: var(--space-
|
|
2855
|
+
padding-block: 0 var(--space-40);
|
|
2856
|
+
padding-inline: var(--space-40);
|
|
2537
2857
|
color: var(--text-muted);
|
|
2538
2858
|
margin-block: 0;
|
|
2539
2859
|
}
|
|
2540
2860
|
|
|
2541
2861
|
.accordion details[open] > summary + * {
|
|
2542
|
-
padding-block-start: var(--space-
|
|
2862
|
+
padding-block-start: var(--space-10);
|
|
2543
2863
|
}
|
|
2544
2864
|
|
|
2545
2865
|
.accordion details > summary + * > :first-child {
|
|
@@ -2549,27 +2869,12 @@ dialog.drawer nav {
|
|
|
2549
2869
|
.accordion details > summary + ul,
|
|
2550
2870
|
.accordion details > summary + ol {
|
|
2551
2871
|
margin: 0;
|
|
2552
|
-
padding-inline-start: 2rem;
|
|
2553
|
-
}
|
|
2554
|
-
|
|
2555
|
-
@media (forced-colors: active) {
|
|
2556
|
-
.accordion {
|
|
2557
|
-
border-color: ButtonText;
|
|
2558
|
-
background: Canvas;
|
|
2559
|
-
color: CanvasText;
|
|
2560
|
-
}
|
|
2561
|
-
|
|
2562
|
-
.accordion > details + details {
|
|
2563
|
-
border-block-start-color: ButtonText;
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
|
-
.accordion summary {
|
|
2567
|
-
color: CanvasText;
|
|
2568
|
-
}
|
|
2872
|
+
padding-inline-start: 2rem;
|
|
2873
|
+
}
|
|
2569
2874
|
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2875
|
+
@media (forced-colors: active) {
|
|
2876
|
+
.accordion {
|
|
2877
|
+
--accordion-marker-color: CanvasText;
|
|
2573
2878
|
}
|
|
2574
2879
|
}
|
|
2575
2880
|
|
|
@@ -2580,18 +2885,17 @@ dialog.drawer nav {
|
|
|
2580
2885
|
*
|
|
2581
2886
|
* Uses currentColor so it adapts to buttons, alerts, badges, and text. The
|
|
2582
2887
|
* [aria-busy] overlay composition lives in components/busy.css.
|
|
2888
|
+
*
|
|
2889
|
+
* Public hooks:
|
|
2890
|
+
* --spinner-size
|
|
2891
|
+
* --spinner-width
|
|
2892
|
+
* --spinner-track
|
|
2893
|
+
* --spinner-value
|
|
2583
2894
|
*/
|
|
2584
2895
|
/* Intent boundary — inherited intents do not tint this component.
|
|
2585
2896
|
Local intent classes still win because :where() has zero specificity. */
|
|
2586
2897
|
:where(.spinner) {
|
|
2587
|
-
/* @sync intent-boundary */
|
|
2588
2898
|
--intent: initial;
|
|
2589
|
-
--intent-fg: initial;
|
|
2590
|
-
--ui-bg: initial;
|
|
2591
|
-
--ui-fg: initial;
|
|
2592
|
-
--ui-border: initial;
|
|
2593
|
-
--ui-hover-bg: initial;
|
|
2594
|
-
/* @sync intent-boundary */
|
|
2595
2899
|
}
|
|
2596
2900
|
|
|
2597
2901
|
.spinner {
|
|
@@ -2641,6 +2945,13 @@ dialog.drawer nav {
|
|
|
2641
2945
|
*
|
|
2642
2946
|
* Shows approximate loading structure without implying real content hierarchy.
|
|
2643
2947
|
* Geometry, animation, and color stay theme-aware.
|
|
2948
|
+
*
|
|
2949
|
+
* Public hooks:
|
|
2950
|
+
* --skeleton-size
|
|
2951
|
+
* --skeleton-width
|
|
2952
|
+
* --skeleton-radius
|
|
2953
|
+
* --skeleton-track
|
|
2954
|
+
* --skeleton-highlight
|
|
2644
2955
|
*/
|
|
2645
2956
|
.skeleton {
|
|
2646
2957
|
--skeleton-size: 1rem;
|
|
@@ -2710,6 +3021,12 @@ dialog.drawer nav {
|
|
|
2710
3021
|
* A non-button element with aria-busy="true" and a direct last-child .spinner
|
|
2711
3022
|
* shows that spinner as a centered overlay over a faded surface. Buttons keep
|
|
2712
3023
|
* their spinners inline (e.g. .btn[aria-busy] > .spinner stays inline-flex).
|
|
3024
|
+
*
|
|
3025
|
+
* Public hooks:
|
|
3026
|
+
* --busy-overlay-bg
|
|
3027
|
+
*
|
|
3028
|
+
* Internal:
|
|
3029
|
+
* --spinner-size (overlay copy of the shared spinner hook)
|
|
2713
3030
|
*/
|
|
2714
3031
|
[aria-busy] > .spinner:last-child {
|
|
2715
3032
|
display: none;
|
|
@@ -2755,26 +3072,21 @@ dialog.drawer nav {
|
|
|
2755
3072
|
*
|
|
2756
3073
|
* Consumes the shared --intent/--primary for the value bar and --bar-height
|
|
2757
3074
|
* for thickness. Indeterminate state animates a shimmer using --duration-shimmer.
|
|
3075
|
+
*
|
|
3076
|
+
* Public hooks:
|
|
3077
|
+
* --progress-track
|
|
3078
|
+
* --progress-value
|
|
2758
3079
|
*/
|
|
2759
3080
|
/* Intent boundary — inherited intents do not tint this component.
|
|
2760
3081
|
Local intent classes still win because :where() has zero specificity. */
|
|
2761
3082
|
:where(.progress) {
|
|
2762
|
-
/* @sync intent-boundary */
|
|
2763
3083
|
--intent: initial;
|
|
2764
|
-
--intent-fg: initial;
|
|
2765
|
-
--ui-bg: initial;
|
|
2766
|
-
--ui-fg: initial;
|
|
2767
|
-
--ui-border: initial;
|
|
2768
|
-
--ui-hover-bg: initial;
|
|
2769
|
-
/* @sync intent-boundary */
|
|
2770
3084
|
}
|
|
2771
3085
|
|
|
2772
3086
|
.progress {
|
|
2773
3087
|
--progress-track: var(--surface-subtle);
|
|
2774
3088
|
--progress-value: var(--intent, var(--primary));
|
|
2775
|
-
}
|
|
2776
3089
|
|
|
2777
|
-
.progress {
|
|
2778
3090
|
appearance: none;
|
|
2779
3091
|
-webkit-appearance: none;
|
|
2780
3092
|
display: block;
|
|
@@ -2853,27 +3165,14 @@ dialog.drawer nav {
|
|
|
2853
3165
|
}
|
|
2854
3166
|
|
|
2855
3167
|
@media (forced-colors: active) {
|
|
2856
|
-
.progress
|
|
2857
|
-
|
|
2858
|
-
background: Canvas;
|
|
2859
|
-
}
|
|
2860
|
-
|
|
2861
|
-
.progress::-webkit-progress-bar {
|
|
2862
|
-
background: Canvas;
|
|
2863
|
-
}
|
|
2864
|
-
|
|
2865
|
-
.progress::-webkit-progress-value {
|
|
3168
|
+
.progress::-webkit-progress-value,
|
|
3169
|
+
.progress:indeterminate::-webkit-progress-value {
|
|
2866
3170
|
background: Highlight;
|
|
2867
|
-
forced-color-adjust: none;
|
|
2868
3171
|
}
|
|
2869
3172
|
|
|
2870
|
-
.progress::-moz-progress-bar
|
|
3173
|
+
.progress::-moz-progress-bar,
|
|
3174
|
+
.progress:indeterminate::-moz-progress-bar {
|
|
2871
3175
|
background: Highlight;
|
|
2872
|
-
forced-color-adjust: none;
|
|
2873
|
-
}
|
|
2874
|
-
|
|
2875
|
-
.progress:indeterminate {
|
|
2876
|
-
background: Canvas;
|
|
2877
3176
|
}
|
|
2878
3177
|
}
|
|
2879
3178
|
|
|
@@ -2892,19 +3191,17 @@ dialog.drawer nav {
|
|
|
2892
3191
|
* CSS API: .status-bar
|
|
2893
3192
|
* JS target contract: [data-status][role="status"]
|
|
2894
3193
|
* Normally combined, but they describe separate concerns.
|
|
3194
|
+
*
|
|
3195
|
+
* Internal:
|
|
3196
|
+
* --status-bg
|
|
3197
|
+
* --status-fg
|
|
2895
3198
|
*/
|
|
2896
3199
|
|
|
2897
3200
|
/* Intent boundary — inherited intents do not tint this component.
|
|
2898
3201
|
Local intent classes still win because :where() has zero specificity. */
|
|
2899
3202
|
:where(.status-bar) {
|
|
2900
|
-
/* @sync intent-boundary */
|
|
2901
3203
|
--intent: initial;
|
|
2902
3204
|
--intent-fg: initial;
|
|
2903
|
-
--ui-bg: initial;
|
|
2904
|
-
--ui-fg: initial;
|
|
2905
|
-
--ui-border: initial;
|
|
2906
|
-
--ui-hover-bg: initial;
|
|
2907
|
-
/* @sync intent-boundary */
|
|
2908
3205
|
}
|
|
2909
3206
|
|
|
2910
3207
|
.status-bar {
|
|
@@ -2912,22 +3209,22 @@ dialog.drawer nav {
|
|
|
2912
3209
|
--status-fg: var(--intent-fg, var(--surface));
|
|
2913
3210
|
|
|
2914
3211
|
position: fixed;
|
|
2915
|
-
inset-block-end: var(--space-
|
|
2916
|
-
inset-inline: var(--space-
|
|
2917
|
-
z-index: var(--z-status
|
|
3212
|
+
inset-block-end: var(--space-40);
|
|
3213
|
+
inset-inline: var(--space-40);
|
|
3214
|
+
z-index: var(--z-status);
|
|
2918
3215
|
|
|
2919
3216
|
display: inline-flex;
|
|
2920
3217
|
align-items: center;
|
|
2921
3218
|
justify-content: center;
|
|
2922
|
-
gap: var(--space-
|
|
3219
|
+
gap: var(--space-20);
|
|
2923
3220
|
|
|
2924
3221
|
inline-size: fit-content;
|
|
2925
|
-
max-inline-size: min(calc(
|
|
3222
|
+
max-inline-size: min(calc(var(--viewport-inline) - var(--space-60)), 32rem);
|
|
2926
3223
|
margin-inline: auto;
|
|
2927
3224
|
overflow-wrap: anywhere;
|
|
2928
3225
|
|
|
2929
|
-
padding-block: var(--space-
|
|
2930
|
-
padding-inline: var(--space-
|
|
3226
|
+
padding-block: var(--space-20);
|
|
3227
|
+
padding-inline: var(--space-30);
|
|
2931
3228
|
|
|
2932
3229
|
border-radius: var(--radius-full);
|
|
2933
3230
|
background: var(--status-bg);
|
|
@@ -2940,25 +3237,18 @@ dialog.drawer nav {
|
|
|
2940
3237
|
pointer-events: none;
|
|
2941
3238
|
|
|
2942
3239
|
transition:
|
|
2943
|
-
opacity var(--duration-slow) var(--ease
|
|
2944
|
-
scale var(--duration-slow) var(--ease
|
|
2945
|
-
translate var(--duration-slow) var(--ease
|
|
3240
|
+
opacity var(--duration-slow) var(--ease),
|
|
3241
|
+
scale var(--duration-slow) var(--ease),
|
|
3242
|
+
translate var(--duration-slow) var(--ease);
|
|
2946
3243
|
}
|
|
2947
3244
|
|
|
2948
3245
|
.status-bar:empty {
|
|
2949
3246
|
opacity: 0;
|
|
2950
3247
|
scale: 0.96;
|
|
2951
|
-
translate: 0 var(--space-
|
|
3248
|
+
translate: 0 var(--space-20);
|
|
2952
3249
|
pointer-events: none;
|
|
2953
3250
|
}
|
|
2954
3251
|
|
|
2955
|
-
@media (forced-colors: active) {
|
|
2956
|
-
.status-bar {
|
|
2957
|
-
border: var(--border-width) solid ButtonText;
|
|
2958
|
-
box-shadow: none;
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
|
|
2962
3252
|
|
|
2963
3253
|
/* Display */
|
|
2964
3254
|
/*
|
|
@@ -2967,18 +3257,26 @@ dialog.drawer nav {
|
|
|
2967
3257
|
* The visual box stays the same across semantic elements. Grouping and status
|
|
2968
3258
|
* dots (an empty .badge child) compose around the avatar without changing its
|
|
2969
3259
|
* meaning.
|
|
3260
|
+
*
|
|
3261
|
+
* Public hooks:
|
|
3262
|
+
* --avatar-size
|
|
3263
|
+
* --avatar-radius
|
|
3264
|
+
* --avatar-font-size
|
|
3265
|
+
* --avatar-stack-size
|
|
3266
|
+
* --avatar-stack-font-size
|
|
3267
|
+
* --avatar-stack-overlap
|
|
3268
|
+
* --avatar-stack-ring
|
|
3269
|
+
*
|
|
3270
|
+
* Internal:
|
|
3271
|
+
* --avatar-bg
|
|
3272
|
+
* --avatar-fg
|
|
3273
|
+
* --avatar-status-translate-x
|
|
2970
3274
|
*/
|
|
2971
3275
|
/* Intent boundary — inherited intents do not tint this component.
|
|
2972
3276
|
Local intent classes still win because :where() has zero specificity. */
|
|
2973
3277
|
:where(.avatar) {
|
|
2974
|
-
/* @sync intent-boundary */
|
|
2975
3278
|
--intent: initial;
|
|
2976
3279
|
--intent-fg: initial;
|
|
2977
|
-
--ui-bg: initial;
|
|
2978
|
-
--ui-fg: initial;
|
|
2979
|
-
--ui-border: initial;
|
|
2980
|
-
--ui-hover-bg: initial;
|
|
2981
|
-
/* @sync intent-boundary */
|
|
2982
3280
|
}
|
|
2983
3281
|
|
|
2984
3282
|
.avatar {
|
|
@@ -3070,10 +3368,8 @@ button.avatar {
|
|
|
3070
3368
|
transition: opacity var(--duration);
|
|
3071
3369
|
}
|
|
3072
3370
|
|
|
3073
|
-
|
|
3074
|
-
:
|
|
3075
|
-
opacity: 1;
|
|
3076
|
-
}
|
|
3371
|
+
:where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):hover::after {
|
|
3372
|
+
opacity: 1;
|
|
3077
3373
|
}
|
|
3078
3374
|
|
|
3079
3375
|
:where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):active {
|
|
@@ -3166,6 +3462,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
3166
3462
|
*
|
|
3167
3463
|
* Use .key outside prose. Plain kbd inside .prose gets the same visual
|
|
3168
3464
|
* treatment from prose.css so authored content stays convenient.
|
|
3465
|
+
*
|
|
3466
|
+
* Visual twin of .prose kbd (prose.css) — keep the two recipes in sync.
|
|
3169
3467
|
*/
|
|
3170
3468
|
.key {
|
|
3171
3469
|
display: inline-flex;
|
|
@@ -3197,14 +3495,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3197
3495
|
/* Intent boundary — inherited intents do not tint this component.
|
|
3198
3496
|
Local intent classes still win because :where() has zero specificity. */
|
|
3199
3497
|
:where(.overline) {
|
|
3200
|
-
/* @sync intent-boundary */
|
|
3201
3498
|
--intent: initial;
|
|
3202
|
-
--intent-fg: initial;
|
|
3203
|
-
--ui-bg: initial;
|
|
3204
|
-
--ui-fg: initial;
|
|
3205
|
-
--ui-border: initial;
|
|
3206
|
-
--ui-hover-bg: initial;
|
|
3207
|
-
/* @sync intent-boundary */
|
|
3208
3499
|
}
|
|
3209
3500
|
|
|
3210
3501
|
.overline {
|
|
@@ -3245,6 +3536,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
3245
3536
|
* .navbar is a horizontal bar; .navbar-brand is the brand link; .navbar-nav is
|
|
3246
3537
|
* the inline link list using .nav-link items. .nav-list is the vertical nav
|
|
3247
3538
|
* list shared with .drawer, fed by the shared --gap token.
|
|
3539
|
+
*
|
|
3540
|
+
* Internal:
|
|
3541
|
+
* --gap (local value for the shared gap token)
|
|
3248
3542
|
*/
|
|
3249
3543
|
.navbar {
|
|
3250
3544
|
display: flex;
|
|
@@ -3252,8 +3546,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
3252
3546
|
align-items: center;
|
|
3253
3547
|
justify-content: space-between;
|
|
3254
3548
|
gap: var(--gap);
|
|
3255
|
-
padding-block: var(--space-
|
|
3256
|
-
padding-inline: var(--space-
|
|
3549
|
+
padding-block: var(--space-30);
|
|
3550
|
+
padding-inline: var(--space-40);
|
|
3257
3551
|
border: var(--border-width) solid var(--border);
|
|
3258
3552
|
border-radius: var(--radius-lg);
|
|
3259
3553
|
background: var(--surface-raised);
|
|
@@ -3263,7 +3557,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3263
3557
|
.navbar-brand {
|
|
3264
3558
|
display: inline-flex;
|
|
3265
3559
|
align-items: center;
|
|
3266
|
-
gap: var(--space-
|
|
3560
|
+
gap: var(--space-20);
|
|
3267
3561
|
color: var(--text);
|
|
3268
3562
|
font-weight: var(--font-weight-bold);
|
|
3269
3563
|
text-decoration: none;
|
|
@@ -3277,7 +3571,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3277
3571
|
display: flex;
|
|
3278
3572
|
flex-wrap: wrap;
|
|
3279
3573
|
align-items: center;
|
|
3280
|
-
gap: var(--space-
|
|
3574
|
+
gap: var(--space-10);
|
|
3281
3575
|
margin: 0;
|
|
3282
3576
|
padding: 0;
|
|
3283
3577
|
list-style: none;
|
|
@@ -3287,7 +3581,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3287
3581
|
display: inline-flex;
|
|
3288
3582
|
align-items: center;
|
|
3289
3583
|
min-block-size: var(--control-size-sm);
|
|
3290
|
-
padding-inline: var(--space-
|
|
3584
|
+
padding-inline: var(--space-20);
|
|
3291
3585
|
border-radius: var(--radius);
|
|
3292
3586
|
color: var(--text-muted);
|
|
3293
3587
|
font-weight: var(--font-weight-medium);
|
|
@@ -3299,21 +3593,19 @@ a.avatar[aria-disabled="true"] {
|
|
|
3299
3593
|
color: var(--text);
|
|
3300
3594
|
}
|
|
3301
3595
|
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
color: var(--text);
|
|
3306
|
-
}
|
|
3596
|
+
.nav-link:hover {
|
|
3597
|
+
background: var(--surface-subtle);
|
|
3598
|
+
color: var(--text);
|
|
3307
3599
|
}
|
|
3308
3600
|
|
|
3309
3601
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links. */
|
|
3310
3602
|
.nav-list {
|
|
3311
3603
|
display: grid;
|
|
3604
|
+
--gap: var(--space-20);
|
|
3312
3605
|
gap: var(--gap);
|
|
3313
3606
|
margin: 0;
|
|
3314
3607
|
padding: 0;
|
|
3315
3608
|
list-style: none;
|
|
3316
|
-
--gap: var(--space-2);
|
|
3317
3609
|
}
|
|
3318
3610
|
|
|
3319
3611
|
.nav-list > li {
|
|
@@ -3328,13 +3620,11 @@ a.avatar[aria-disabled="true"] {
|
|
|
3328
3620
|
forced-color-adjust: none;
|
|
3329
3621
|
}
|
|
3330
3622
|
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
color: CanvasText;
|
|
3337
|
-
}
|
|
3623
|
+
.nav-link:hover:not(:where([aria-current]:not([aria-current="false"]))) {
|
|
3624
|
+
outline: var(--border-width) solid ButtonText;
|
|
3625
|
+
outline-offset: 2px;
|
|
3626
|
+
background: Canvas;
|
|
3627
|
+
color: CanvasText;
|
|
3338
3628
|
}
|
|
3339
3629
|
}
|
|
3340
3630
|
|
|
@@ -3348,7 +3638,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3348
3638
|
display: flex;
|
|
3349
3639
|
flex-wrap: wrap;
|
|
3350
3640
|
align-items: center;
|
|
3351
|
-
gap: var(--space-
|
|
3641
|
+
gap: var(--space-10);
|
|
3352
3642
|
margin: 0;
|
|
3353
3643
|
padding: 0;
|
|
3354
3644
|
color: var(--text-subtle);
|
|
@@ -3359,7 +3649,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3359
3649
|
.breadcrumb li {
|
|
3360
3650
|
display: inline-flex;
|
|
3361
3651
|
align-items: center;
|
|
3362
|
-
gap: var(--space-
|
|
3652
|
+
gap: var(--space-10);
|
|
3363
3653
|
}
|
|
3364
3654
|
|
|
3365
3655
|
.breadcrumb li + li::before {
|
|
@@ -3372,25 +3662,17 @@ a.avatar[aria-disabled="true"] {
|
|
|
3372
3662
|
text-decoration: none;
|
|
3373
3663
|
}
|
|
3374
3664
|
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
color: var(--text);
|
|
3380
|
-
text-decoration: underline;
|
|
3381
|
-
}
|
|
3665
|
+
.breadcrumb a:not([aria-current]):hover,
|
|
3666
|
+
.breadcrumb a[aria-current="false"]:hover {
|
|
3667
|
+
color: var(--text);
|
|
3668
|
+
text-decoration: underline;
|
|
3382
3669
|
}
|
|
3383
3670
|
|
|
3384
3671
|
.breadcrumb a:focus-visible {
|
|
3385
3672
|
border-radius: var(--radius-sm);
|
|
3386
3673
|
}
|
|
3387
3674
|
|
|
3388
|
-
.breadcrumb a:where([aria-current]:not([aria-current="false"])) {
|
|
3389
|
-
color: var(--text);
|
|
3390
|
-
font-weight: var(--font-weight-medium);
|
|
3391
|
-
}
|
|
3392
|
-
|
|
3393
|
-
.breadcrumb li:where([aria-current]:not([aria-current="false"])) > :where(a, span) {
|
|
3675
|
+
.breadcrumb :where(a, span):where([aria-current]:not([aria-current="false"])) {
|
|
3394
3676
|
color: var(--text);
|
|
3395
3677
|
font-weight: var(--font-weight-medium);
|
|
3396
3678
|
}
|
|
@@ -3409,7 +3691,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3409
3691
|
display: flex;
|
|
3410
3692
|
flex-wrap: wrap;
|
|
3411
3693
|
align-items: center;
|
|
3412
|
-
gap: var(--space-
|
|
3694
|
+
gap: var(--space-10);
|
|
3413
3695
|
margin: 0;
|
|
3414
3696
|
padding: 0;
|
|
3415
3697
|
list-style: none;
|
|
@@ -3432,24 +3714,20 @@ a.avatar[aria-disabled="true"] {
|
|
|
3432
3714
|
* JavaScript owns roving tabindex, aria-selected, panel visibility,
|
|
3433
3715
|
* and arrow-key navigation. CSS owns the tab strip appearance and
|
|
3434
3716
|
* active indicator.
|
|
3717
|
+
*
|
|
3718
|
+
* Public hooks:
|
|
3719
|
+
* --tab-gap
|
|
3435
3720
|
*/
|
|
3436
3721
|
/* Intent boundary — inherited intents do not tint this component.
|
|
3437
3722
|
Local intent classes still win because :where() has zero specificity. */
|
|
3438
3723
|
:where(.tab) {
|
|
3439
|
-
/* @sync intent-boundary */
|
|
3440
3724
|
--intent: initial;
|
|
3441
|
-
--intent-fg: initial;
|
|
3442
|
-
--ui-bg: initial;
|
|
3443
|
-
--ui-fg: initial;
|
|
3444
|
-
--ui-border: initial;
|
|
3445
|
-
--ui-hover-bg: initial;
|
|
3446
|
-
/* @sync intent-boundary */
|
|
3447
3725
|
}
|
|
3448
3726
|
|
|
3449
3727
|
.tabs {
|
|
3450
3728
|
display: flex;
|
|
3451
3729
|
flex-wrap: wrap;
|
|
3452
|
-
gap: var(--space-
|
|
3730
|
+
gap: var(--space-10);
|
|
3453
3731
|
margin: 0;
|
|
3454
3732
|
padding: 0;
|
|
3455
3733
|
border-block-end: var(--border-width) solid var(--border);
|
|
@@ -3467,8 +3745,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
3467
3745
|
.tab {
|
|
3468
3746
|
display: inline-flex;
|
|
3469
3747
|
align-items: center;
|
|
3748
|
+
gap: var(--tab-gap, 0.375em);
|
|
3470
3749
|
min-block-size: var(--control-size);
|
|
3471
|
-
padding-inline: var(--space-
|
|
3750
|
+
padding-inline: var(--space-30);
|
|
3472
3751
|
border: 0;
|
|
3473
3752
|
border-block-end: 2px solid transparent;
|
|
3474
3753
|
border-radius: 0;
|
|
@@ -3484,7 +3763,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3484
3763
|
justify-content: flex-start;
|
|
3485
3764
|
border-block-end: 0;
|
|
3486
3765
|
border-inline-end: 2px solid transparent;
|
|
3487
|
-
padding-inline-end: var(--space-
|
|
3766
|
+
padding-inline-end: var(--space-40);
|
|
3488
3767
|
}
|
|
3489
3768
|
|
|
3490
3769
|
.tab:is(:disabled, [aria-disabled="true"]) {
|
|
@@ -3492,10 +3771,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
3492
3771
|
opacity: var(--disabled-opacity);
|
|
3493
3772
|
}
|
|
3494
3773
|
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
color: var(--text);
|
|
3498
|
-
}
|
|
3774
|
+
.tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
3775
|
+
color: var(--text);
|
|
3499
3776
|
}
|
|
3500
3777
|
|
|
3501
3778
|
.tab:focus-visible {
|
|
@@ -3505,52 +3782,28 @@ a.avatar[aria-disabled="true"] {
|
|
|
3505
3782
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
3506
3783
|
}
|
|
3507
3784
|
|
|
3508
|
-
.tab[aria-selected="true"]
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
border-block-end-color: var(--intent, var(--primary));
|
|
3512
|
-
color: var(--intent, var(--primary));
|
|
3785
|
+
.tab[aria-selected="true"] {
|
|
3786
|
+
border-block-end-color: var(--intent, var(--state-selected));
|
|
3787
|
+
color: var(--intent, var(--state-selected));
|
|
3513
3788
|
}
|
|
3514
3789
|
|
|
3515
|
-
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"]
|
|
3516
|
-
.tabs[aria-orientation="vertical"] .tab[aria-current]:not([aria-current="false"]),
|
|
3517
|
-
.tabs[aria-orientation="vertical"] .tab[aria-pressed="true"] {
|
|
3790
|
+
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
|
|
3518
3791
|
border-block-end-color: transparent;
|
|
3519
|
-
border-inline-end-color: var(--intent, var(--
|
|
3792
|
+
border-inline-end-color: var(--intent, var(--state-selected));
|
|
3520
3793
|
}
|
|
3521
3794
|
|
|
3522
3795
|
@media (forced-colors: active) {
|
|
3523
|
-
.
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
.tabs[aria-orientation="vertical"] {
|
|
3528
|
-
border-block-end-color: transparent;
|
|
3529
|
-
border-inline-end-color: ButtonText;
|
|
3530
|
-
}
|
|
3531
|
-
|
|
3532
|
-
.tab {
|
|
3533
|
-
color: ButtonText;
|
|
3534
|
-
}
|
|
3535
|
-
|
|
3536
|
-
@media (hover: hover) {
|
|
3537
|
-
.tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
3538
|
-
color: ButtonText;
|
|
3539
|
-
text-decoration: underline;
|
|
3540
|
-
text-underline-offset: 0.18em;
|
|
3541
|
-
}
|
|
3796
|
+
.tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
3797
|
+
text-decoration: underline;
|
|
3798
|
+
text-underline-offset: 0.18em;
|
|
3542
3799
|
}
|
|
3543
3800
|
|
|
3544
|
-
.tab[aria-selected="true"]
|
|
3545
|
-
.tab[aria-current]:not([aria-current="false"]),
|
|
3546
|
-
.tab[aria-pressed="true"] {
|
|
3801
|
+
.tab[aria-selected="true"] {
|
|
3547
3802
|
border-block-end-color: Highlight;
|
|
3548
3803
|
color: Highlight;
|
|
3549
3804
|
}
|
|
3550
3805
|
|
|
3551
|
-
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"]
|
|
3552
|
-
.tabs[aria-orientation="vertical"] .tab[aria-current]:not([aria-current="false"]),
|
|
3553
|
-
.tabs[aria-orientation="vertical"] .tab[aria-pressed="true"] {
|
|
3806
|
+
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
|
|
3554
3807
|
border-block-end-color: transparent;
|
|
3555
3808
|
border-inline-end-color: Highlight;
|
|
3556
3809
|
}
|
|
@@ -3591,6 +3844,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
3591
3844
|
*
|
|
3592
3845
|
* .table-wrap provides the scroll container and outer chrome; .table owns row
|
|
3593
3846
|
* and cell rules. Tokens are sourced from the shared scale.
|
|
3847
|
+
*
|
|
3848
|
+
* Public hooks:
|
|
3849
|
+
* --table-min
|
|
3850
|
+
* --table-cell-pad
|
|
3594
3851
|
*/
|
|
3595
3852
|
.table-wrap {
|
|
3596
3853
|
inline-size: 100%;
|
|
@@ -3604,7 +3861,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3604
3861
|
|
|
3605
3862
|
.table {
|
|
3606
3863
|
--table-min: 100%;
|
|
3607
|
-
--table-cell-pad: var(--space-
|
|
3864
|
+
--table-cell-pad: var(--space-30);
|
|
3608
3865
|
|
|
3609
3866
|
inline-size: max(var(--table-min), 100%);
|
|
3610
3867
|
border-collapse: collapse;
|
|
@@ -3640,10 +3897,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
3640
3897
|
background: var(--surface-subtle);
|
|
3641
3898
|
}
|
|
3642
3899
|
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
background-color: var(--hover-overlay);
|
|
3646
|
-
}
|
|
3900
|
+
.table tbody tr:hover {
|
|
3901
|
+
background-color: var(--hover-overlay);
|
|
3647
3902
|
}
|
|
3648
3903
|
|
|
3649
3904
|
.table > tr:last-child > *,
|
|
@@ -3657,18 +3912,6 @@ a.avatar[aria-disabled="true"] {
|
|
|
3657
3912
|
* Maps the three standard meter zones (optimum / suboptimum / even-less-good)
|
|
3658
3913
|
* to the shared success / warning / danger intent colors.
|
|
3659
3914
|
*/
|
|
3660
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
3661
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
3662
|
-
:where(.meter) {
|
|
3663
|
-
/* @sync intent-boundary */
|
|
3664
|
-
--intent: initial;
|
|
3665
|
-
--intent-fg: initial;
|
|
3666
|
-
--ui-bg: initial;
|
|
3667
|
-
--ui-fg: initial;
|
|
3668
|
-
--ui-border: initial;
|
|
3669
|
-
--ui-hover-bg: initial;
|
|
3670
|
-
/* @sync intent-boundary */
|
|
3671
|
-
}
|
|
3672
3915
|
|
|
3673
3916
|
.meter {
|
|
3674
3917
|
appearance: none;
|
|
@@ -3714,29 +3957,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
3714
3957
|
}
|
|
3715
3958
|
|
|
3716
3959
|
@media (forced-colors: active) {
|
|
3717
|
-
.meter {
|
|
3718
|
-
border: var(--border-width) solid ButtonText;
|
|
3719
|
-
background: Canvas;
|
|
3720
|
-
}
|
|
3721
|
-
|
|
3722
|
-
.meter::-webkit-meter-bar {
|
|
3723
|
-
border: 0;
|
|
3724
|
-
background: Canvas;
|
|
3725
|
-
}
|
|
3726
|
-
|
|
3727
3960
|
.meter::-webkit-meter-optimum-value,
|
|
3728
3961
|
.meter::-webkit-meter-suboptimum-value,
|
|
3729
3962
|
.meter::-webkit-meter-even-less-good-value,
|
|
3730
3963
|
.meter::-moz-meter-bar {
|
|
3731
3964
|
background: Highlight;
|
|
3732
|
-
forced-color-adjust: none;
|
|
3733
3965
|
}
|
|
3734
3966
|
}
|
|
3735
3967
|
|
|
3736
|
-
/* Forms — import order matters: switch.css
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3968
|
+
/* Forms — import order matters: switch.css follows choice.css so its rules
|
|
3969
|
+
land after the shared choice recipe. validation.css is part of the default
|
|
3970
|
+
forms bundle, but custom builds may omit it when validation styling is
|
|
3971
|
+
app-owned. */
|
|
3740
3972
|
/*
|
|
3741
3973
|
* Form layout and supporting text.
|
|
3742
3974
|
*
|
|
@@ -3745,23 +3977,26 @@ a.avatar[aria-disabled="true"] {
|
|
|
3745
3977
|
*
|
|
3746
3978
|
* Public API: .field, .field-label, .field-help, .field-error, .field-group,
|
|
3747
3979
|
* .required-mark.
|
|
3980
|
+
*
|
|
3981
|
+
* Public hooks:
|
|
3982
|
+
* --fieldset-legend-padding-inline
|
|
3748
3983
|
*/
|
|
3749
3984
|
.field {
|
|
3750
3985
|
display: grid;
|
|
3751
|
-
gap: var(--space-
|
|
3986
|
+
gap: var(--space-10);
|
|
3752
3987
|
color: var(--text);
|
|
3753
3988
|
}
|
|
3754
3989
|
|
|
3755
3990
|
.field-group {
|
|
3756
3991
|
min-inline-size: 0;
|
|
3757
3992
|
margin: 0;
|
|
3758
|
-
padding: var(--space-
|
|
3993
|
+
padding: var(--space-30);
|
|
3759
3994
|
border: var(--border-width) solid var(--border);
|
|
3760
3995
|
border-radius: var(--radius);
|
|
3761
3996
|
}
|
|
3762
3997
|
|
|
3763
3998
|
.field-group + .field-group {
|
|
3764
|
-
margin-block-start: var(--space-
|
|
3999
|
+
margin-block-start: var(--space-40);
|
|
3765
4000
|
}
|
|
3766
4001
|
|
|
3767
4002
|
.field-label,
|
|
@@ -3772,7 +4007,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3772
4007
|
|
|
3773
4008
|
.field-group > legend {
|
|
3774
4009
|
padding-block: 0;
|
|
3775
|
-
padding-inline: var(--fieldset-legend-padding-inline, var(--space-
|
|
4010
|
+
padding-inline: var(--fieldset-legend-padding-inline, var(--space-10));
|
|
3776
4011
|
font-size: var(--font-size-xs);
|
|
3777
4012
|
}
|
|
3778
4013
|
|
|
@@ -3811,6 +4046,12 @@ a.avatar[aria-disabled="true"] {
|
|
|
3811
4046
|
* --control-border-block-end-color/-width are unset extension points, mirroring
|
|
3812
4047
|
* --btn-border-block-end-* (button.css), for themes that give text controls an
|
|
3813
4048
|
* underline-style lower edge instead of a uniform border.
|
|
4049
|
+
*
|
|
4050
|
+
* Public hooks:
|
|
4051
|
+
* --control-border
|
|
4052
|
+
* --control-bg
|
|
4053
|
+
* --control-fg
|
|
4054
|
+
* --control-cursor
|
|
3814
4055
|
*/
|
|
3815
4056
|
.input,
|
|
3816
4057
|
.textarea,
|
|
@@ -3818,14 +4059,12 @@ a.avatar[aria-disabled="true"] {
|
|
|
3818
4059
|
--control-border: var(--border);
|
|
3819
4060
|
--control-bg: var(--surface);
|
|
3820
4061
|
--control-fg: var(--text);
|
|
3821
|
-
--control-ring: none;
|
|
3822
4062
|
--control-cursor: auto;
|
|
3823
|
-
--control-opacity: 1;
|
|
3824
4063
|
|
|
3825
4064
|
appearance: none;
|
|
3826
4065
|
inline-size: 100%;
|
|
3827
4066
|
min-block-size: var(--control-size);
|
|
3828
|
-
padding-inline: var(--space-
|
|
4067
|
+
padding-inline: var(--space-30);
|
|
3829
4068
|
border: var(--border-width) solid var(--form-invalid-border, var(--control-border));
|
|
3830
4069
|
border-block-end-color: var(
|
|
3831
4070
|
--form-invalid-border,
|
|
@@ -3836,9 +4075,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3836
4075
|
background: var(--control-bg);
|
|
3837
4076
|
color: var(--control-fg);
|
|
3838
4077
|
font-size: var(--control-font-size);
|
|
3839
|
-
box-shadow: var(--control-ring);
|
|
3840
4078
|
cursor: var(--control-cursor);
|
|
3841
|
-
opacity: var(--control-opacity);
|
|
3842
4079
|
outline: none;
|
|
3843
4080
|
transition:
|
|
3844
4081
|
border-color var(--duration),
|
|
@@ -3849,26 +4086,24 @@ a.avatar[aria-disabled="true"] {
|
|
|
3849
4086
|
|
|
3850
4087
|
.textarea {
|
|
3851
4088
|
min-block-size: 6rem;
|
|
3852
|
-
padding-block: var(--space-
|
|
4089
|
+
padding-block: var(--space-20);
|
|
3853
4090
|
resize: vertical;
|
|
3854
4091
|
}
|
|
3855
4092
|
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
}
|
|
4093
|
+
.input:hover:not(:disabled),
|
|
4094
|
+
.textarea:hover:not(:disabled) {
|
|
4095
|
+
--control-border: var(--text-subtle);
|
|
4096
|
+
}
|
|
3861
4097
|
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
}
|
|
4098
|
+
.select:hover:not(:disabled) {
|
|
4099
|
+
--control-bg: var(--surface-subtle);
|
|
3865
4100
|
}
|
|
3866
4101
|
|
|
3867
4102
|
.input:focus-visible,
|
|
3868
4103
|
.textarea:focus-visible,
|
|
3869
4104
|
.select:focus-visible {
|
|
3870
4105
|
--control-border: var(--focus);
|
|
3871
|
-
outline:
|
|
4106
|
+
outline: 2px solid transparent;
|
|
3872
4107
|
box-shadow: var(--focus-ring-shadow);
|
|
3873
4108
|
}
|
|
3874
4109
|
|
|
@@ -3882,10 +4117,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
3882
4117
|
.textarea:disabled,
|
|
3883
4118
|
.select:disabled {
|
|
3884
4119
|
--control-bg: var(--surface-subtle);
|
|
3885
|
-
--control-fg: var(--
|
|
4120
|
+
--control-fg: var(--state-disabled);
|
|
3886
4121
|
--control-border: var(--border);
|
|
3887
4122
|
--control-cursor: not-allowed;
|
|
3888
|
-
--control-opacity: 1;
|
|
3889
4123
|
}
|
|
3890
4124
|
|
|
3891
4125
|
.input::placeholder,
|
|
@@ -3894,40 +4128,58 @@ a.avatar[aria-disabled="true"] {
|
|
|
3894
4128
|
}
|
|
3895
4129
|
|
|
3896
4130
|
/*
|
|
3897
|
-
* Input Icon —
|
|
4131
|
+
* Input Icon — decorative icon inside a relative wrapper, leading or trailing.
|
|
3898
4132
|
*
|
|
3899
|
-
* Icon-library agnostic:
|
|
3900
|
-
*
|
|
3901
|
-
*
|
|
3902
|
-
* the
|
|
3903
|
-
*
|
|
4133
|
+
* Icon-library agnostic: detects position by DOM order. First child that is
|
|
4134
|
+
* not .input = leading icon; icon after .input = trailing. The icon keeps a
|
|
4135
|
+
* fixed size; density contexts shrink the control geometry around it, not
|
|
4136
|
+
* the pictogram.
|
|
4137
|
+
*
|
|
4138
|
+
* Public hooks:
|
|
4139
|
+
* --input-icon-size
|
|
3904
4140
|
*/
|
|
3905
4141
|
.input-icon {
|
|
3906
|
-
--input-icon-size:
|
|
4142
|
+
--input-icon-size: 1.25rem;
|
|
3907
4143
|
|
|
3908
4144
|
display: block;
|
|
3909
4145
|
position: relative;
|
|
3910
4146
|
}
|
|
3911
4147
|
|
|
3912
|
-
|
|
4148
|
+
/* Icon — shared by leading and trailing */
|
|
4149
|
+
.input-icon > :where(:not(.input)) {
|
|
3913
4150
|
position: absolute;
|
|
3914
4151
|
inset-block-start: 50%;
|
|
3915
|
-
inset-inline-start: var(--space-3);
|
|
3916
4152
|
transform: translateY(-50%);
|
|
3917
4153
|
font-size: var(--input-icon-size);
|
|
3918
4154
|
color: var(--text-muted);
|
|
3919
4155
|
pointer-events: none;
|
|
3920
4156
|
}
|
|
3921
4157
|
|
|
4158
|
+
/* Leading: icon is the first child */
|
|
4159
|
+
.input-icon > :first-child:where(:not(.input)) {
|
|
4160
|
+
inset-inline-start: var(--space-30);
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
/* Trailing: input is the first child, icon follows */
|
|
4164
|
+
.input-icon > .input:first-child ~ :where(:not(.input)) {
|
|
4165
|
+
inset-inline-end: var(--space-30);
|
|
4166
|
+
}
|
|
4167
|
+
|
|
3922
4168
|
/* font-size only sizes text-metric icons (webfont <i>, <span>). An inline
|
|
3923
4169
|
<svg> with just a viewBox ignores it, so give it explicit dimensions. */
|
|
3924
|
-
.input-icon > svg:
|
|
4170
|
+
.input-icon > svg:where(:not(.input)) {
|
|
3925
4171
|
inline-size: var(--input-icon-size);
|
|
3926
4172
|
block-size: var(--input-icon-size);
|
|
3927
4173
|
}
|
|
3928
4174
|
|
|
3929
|
-
|
|
3930
|
-
|
|
4175
|
+
/* Leading: input gets start padding to clear the icon */
|
|
4176
|
+
.input-icon > :first-child:where(:not(.input)) ~ .input {
|
|
4177
|
+
padding-inline-start: calc(var(--space-30) * 2 + var(--input-icon-size));
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
/* Trailing: input is first child and has a sibling → end padding */
|
|
4181
|
+
.input-icon > .input:first-child:where(:not(:last-child)) {
|
|
4182
|
+
padding-inline-end: calc(var(--space-30) * 2 + var(--input-icon-size));
|
|
3931
4183
|
}
|
|
3932
4184
|
|
|
3933
4185
|
/*
|
|
@@ -3940,23 +4192,23 @@ a.avatar[aria-disabled="true"] {
|
|
|
3940
4192
|
*
|
|
3941
4193
|
* Switches (input[role="switch"]) are excluded so switch.css owns their
|
|
3942
4194
|
* geometry and states. switch.css is imported after this file.
|
|
4195
|
+
*
|
|
4196
|
+
* Public hooks:
|
|
4197
|
+
* --choice-control-offset
|
|
4198
|
+
* --choice-border
|
|
4199
|
+
* --choice-bg
|
|
4200
|
+
* --choice-mark
|
|
3943
4201
|
*/
|
|
3944
4202
|
/* Intent boundary — inherited intents do not tint this component.
|
|
3945
4203
|
Local intent classes still win because :where() has zero specificity. */
|
|
3946
4204
|
:where(.choice, .check, .radio) {
|
|
3947
|
-
/* @sync intent-boundary */
|
|
3948
4205
|
--intent: initial;
|
|
3949
4206
|
--intent-fg: initial;
|
|
3950
|
-
--ui-bg: initial;
|
|
3951
|
-
--ui-fg: initial;
|
|
3952
|
-
--ui-border: initial;
|
|
3953
|
-
--ui-hover-bg: initial;
|
|
3954
|
-
/* @sync intent-boundary */
|
|
3955
4207
|
}
|
|
3956
4208
|
|
|
3957
|
-
/* A classed .choice is the component surface. Let its
|
|
4209
|
+
/* A classed .choice is the component surface. Let its direct input consume a
|
|
3958
4210
|
local intent without reopening inherited page-level intents. */
|
|
3959
|
-
:where(.choice) :where(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
|
|
4211
|
+
:where(.choice) > :where(input[type="checkbox"]:not([role="switch"]), input[type="radio"]) {
|
|
3960
4212
|
--intent: inherit;
|
|
3961
4213
|
--intent-fg: inherit;
|
|
3962
4214
|
}
|
|
@@ -3966,7 +4218,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3966
4218
|
|
|
3967
4219
|
display: inline-flex;
|
|
3968
4220
|
align-items: flex-start;
|
|
3969
|
-
gap: var(--space-
|
|
4221
|
+
gap: var(--space-20);
|
|
3970
4222
|
color: var(--text);
|
|
3971
4223
|
font-size: var(--control-font-size);
|
|
3972
4224
|
cursor: pointer;
|
|
@@ -3989,8 +4241,11 @@ a.avatar[aria-disabled="true"] {
|
|
|
3989
4241
|
display: block;
|
|
3990
4242
|
}
|
|
3991
4243
|
|
|
3992
|
-
|
|
3993
|
-
|
|
4244
|
+
/* Progressive enhancement: dim the whole row when one of its controls is
|
|
4245
|
+
disabled. Without :has() the native disabled control keeps its own state
|
|
4246
|
+
styling — the row-level dim is lost but the field stays clearly disabled. */
|
|
4247
|
+
.choice:has(> :disabled) {
|
|
4248
|
+
color: var(--state-disabled);
|
|
3994
4249
|
cursor: not-allowed;
|
|
3995
4250
|
}
|
|
3996
4251
|
|
|
@@ -4007,8 +4262,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4007
4262
|
place-content: center;
|
|
4008
4263
|
inline-size: 1.125em;
|
|
4009
4264
|
block-size: 1.125em;
|
|
4010
|
-
margin
|
|
4011
|
-
margin-block-end: 0;
|
|
4265
|
+
margin: 0;
|
|
4012
4266
|
border: var(--border-width) solid var(--form-invalid-border, var(--choice-border));
|
|
4013
4267
|
background: var(--choice-bg);
|
|
4014
4268
|
color: var(--choice-mark);
|
|
@@ -4049,7 +4303,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4049
4303
|
background: currentColor;
|
|
4050
4304
|
}
|
|
4051
4305
|
|
|
4052
|
-
.check:checked,
|
|
4306
|
+
.check:is(:checked, :indeterminate),
|
|
4053
4307
|
.radio:checked {
|
|
4054
4308
|
--choice-border: var(--intent, var(--primary));
|
|
4055
4309
|
--choice-bg: var(--intent, var(--primary));
|
|
@@ -4060,12 +4314,6 @@ a.avatar[aria-disabled="true"] {
|
|
|
4060
4314
|
transform: translateY(-0.06em) rotate(-45deg) scale(1);
|
|
4061
4315
|
}
|
|
4062
4316
|
|
|
4063
|
-
.check:indeterminate {
|
|
4064
|
-
--choice-border: var(--intent, var(--primary));
|
|
4065
|
-
--choice-bg: var(--intent, var(--primary));
|
|
4066
|
-
--choice-mark: var(--intent-fg, var(--primary-fg));
|
|
4067
|
-
}
|
|
4068
|
-
|
|
4069
4317
|
.check:indeterminate::before {
|
|
4070
4318
|
border-inline-start: 0;
|
|
4071
4319
|
inline-size: 0.6em;
|
|
@@ -4078,17 +4326,15 @@ a.avatar[aria-disabled="true"] {
|
|
|
4078
4326
|
transform: scale(1);
|
|
4079
4327
|
}
|
|
4080
4328
|
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
--choice-border: var(--text-subtle);
|
|
4085
|
-
}
|
|
4329
|
+
.check:hover:not(:checked, :indeterminate, :disabled),
|
|
4330
|
+
.radio:hover:not(:checked, :indeterminate, :disabled) {
|
|
4331
|
+
--choice-border: var(--text-subtle);
|
|
4086
4332
|
}
|
|
4087
4333
|
|
|
4088
4334
|
.check:focus-visible,
|
|
4089
4335
|
.radio:focus-visible {
|
|
4090
4336
|
--choice-border: var(--focus);
|
|
4091
|
-
outline:
|
|
4337
|
+
outline: 2px solid transparent;
|
|
4092
4338
|
box-shadow: var(--focus-ring-shadow);
|
|
4093
4339
|
}
|
|
4094
4340
|
|
|
@@ -4096,7 +4342,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4096
4342
|
.radio:disabled {
|
|
4097
4343
|
--choice-bg: var(--surface-subtle);
|
|
4098
4344
|
--choice-border: var(--border);
|
|
4099
|
-
--choice-mark: var(--
|
|
4345
|
+
--choice-mark: var(--state-disabled);
|
|
4100
4346
|
cursor: not-allowed;
|
|
4101
4347
|
opacity: 1;
|
|
4102
4348
|
}
|
|
@@ -4138,20 +4384,21 @@ a.avatar[aria-disabled="true"] {
|
|
|
4138
4384
|
* The badge overhangs the top-right corner by 0.375em; ancestors with
|
|
4139
4385
|
* overflow: hidden will clip it. Pair with grid or flex utilities for group
|
|
4140
4386
|
* layout.
|
|
4387
|
+
*
|
|
4388
|
+
* Public hooks:
|
|
4389
|
+
* --choice-card-pad
|
|
4390
|
+
* --choice-card-radius
|
|
4391
|
+
* --choice-card-border
|
|
4392
|
+
* --choice-card-bg
|
|
4393
|
+
* --choice-card-check-size
|
|
4141
4394
|
*/
|
|
4142
4395
|
:where(.choice-card) {
|
|
4143
|
-
/* @sync intent-boundary */
|
|
4144
4396
|
--intent: initial;
|
|
4145
4397
|
--intent-fg: initial;
|
|
4146
|
-
--ui-bg: initial;
|
|
4147
|
-
--ui-fg: initial;
|
|
4148
|
-
--ui-border: initial;
|
|
4149
|
-
--ui-hover-bg: initial;
|
|
4150
|
-
/* @sync intent-boundary */
|
|
4151
4398
|
}
|
|
4152
4399
|
|
|
4153
4400
|
.choice-card {
|
|
4154
|
-
--choice-card-pad: var(--
|
|
4401
|
+
--choice-card-pad: var(--density-space, var(--space-40));
|
|
4155
4402
|
--choice-card-radius: var(--radius);
|
|
4156
4403
|
--choice-card-border: var(--border);
|
|
4157
4404
|
--choice-card-bg: var(--surface);
|
|
@@ -4191,7 +4438,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4191
4438
|
|
|
4192
4439
|
/* Focus ring on the label follows the hidden input's focus. */
|
|
4193
4440
|
.choice-card:has(:focus-visible) {
|
|
4194
|
-
outline:
|
|
4441
|
+
outline: 2px solid transparent;
|
|
4195
4442
|
box-shadow: var(--focus-ring-shadow);
|
|
4196
4443
|
}
|
|
4197
4444
|
|
|
@@ -4200,10 +4447,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4200
4447
|
cursor: not-allowed;
|
|
4201
4448
|
}
|
|
4202
4449
|
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
--choice-card-border: var(--text-subtle);
|
|
4206
|
-
}
|
|
4450
|
+
.choice-card:hover:not(:has(:disabled)) {
|
|
4451
|
+
--choice-card-border: var(--text-subtle);
|
|
4207
4452
|
}
|
|
4208
4453
|
|
|
4209
4454
|
/* Checked border — shared by radio and checkbox */
|
|
@@ -4250,8 +4495,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4250
4495
|
/* Check mark */
|
|
4251
4496
|
.choice-card::after {
|
|
4252
4497
|
background: var(--intent-fg, var(--primary-fg));
|
|
4253
|
-
-webkit-mask: var(--icon-check
|
|
4254
|
-
mask: var(--icon-check
|
|
4498
|
+
-webkit-mask: var(--icon-check) center / 60% no-repeat;
|
|
4499
|
+
mask: var(--icon-check) center / 60% no-repeat;
|
|
4255
4500
|
z-index: 2;
|
|
4256
4501
|
}
|
|
4257
4502
|
|
|
@@ -4294,19 +4539,19 @@ a.avatar[aria-disabled="true"] {
|
|
|
4294
4539
|
* custom-select.css can be opted out independently. Add .native to a specific
|
|
4295
4540
|
* select to opt out of the enhancement.
|
|
4296
4541
|
*/
|
|
4297
|
-
.select:
|
|
4542
|
+
.select:not([multiple], [size]:not([size="1"])) {
|
|
4298
4543
|
appearance: none;
|
|
4299
|
-
padding-inline-end: calc(var(--space-
|
|
4300
|
-
background-image: var(--icon-chevron
|
|
4301
|
-
background-position: center right var(--space-
|
|
4544
|
+
padding-inline-end: calc(var(--space-30) + var(--space-60));
|
|
4545
|
+
background-image: var(--icon-chevron);
|
|
4546
|
+
background-position: center right var(--space-30);
|
|
4302
4547
|
background-size: 1rem auto;
|
|
4303
4548
|
background-repeat: no-repeat;
|
|
4304
4549
|
}
|
|
4305
4550
|
|
|
4306
4551
|
/* The chevron sits on the inline-end side; flip it in RTL so it follows
|
|
4307
4552
|
the padding-inline-end space reserved for it. */
|
|
4308
|
-
.select:
|
|
4309
|
-
background-position: center left var(--space-
|
|
4553
|
+
.select:not([multiple], [size]:not([size="1"])):dir(rtl) {
|
|
4554
|
+
background-position: center left var(--space-30);
|
|
4310
4555
|
}
|
|
4311
4556
|
|
|
4312
4557
|
/* Both forced-colors and prefers-contrast hand the arrow back to the
|
|
@@ -4314,10 +4559,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
4314
4559
|
simpler than maintaining contrast variants, and native rendering already
|
|
4315
4560
|
matches whatever contrast mode the user picked. */
|
|
4316
4561
|
@media (forced-colors: active), (prefers-contrast: more) {
|
|
4317
|
-
.select:
|
|
4562
|
+
.select:not([multiple], [size]:not([size="1"])) {
|
|
4318
4563
|
appearance: auto;
|
|
4319
4564
|
background-image: none;
|
|
4320
|
-
padding-inline-end: var(--space-
|
|
4565
|
+
padding-inline-end: var(--space-30);
|
|
4321
4566
|
}
|
|
4322
4567
|
}
|
|
4323
4568
|
|
|
@@ -4330,6 +4575,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
4330
4575
|
*
|
|
4331
4576
|
* No layout or interaction contract may depend on appearance: base-select.
|
|
4332
4577
|
* Arbitrary menu and popover behavior belongs elsewhere.
|
|
4578
|
+
*
|
|
4579
|
+
* Internal:
|
|
4580
|
+
* --control-border
|
|
4581
|
+
* --control-bg
|
|
4333
4582
|
*/
|
|
4334
4583
|
@media (forced-colors: none) and (prefers-contrast: no-preference) and (hover: hover) and (
|
|
4335
4584
|
pointer: fine
|
|
@@ -4342,7 +4591,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4342
4591
|
&::picker(select) {
|
|
4343
4592
|
appearance: base-select;
|
|
4344
4593
|
max-height: min(24rem, calc(var(--viewport-block) * 0.7));
|
|
4345
|
-
margin-block: var(--space-
|
|
4594
|
+
margin-block: var(--space-20);
|
|
4346
4595
|
padding: 0;
|
|
4347
4596
|
border: var(--border-width) solid var(--border);
|
|
4348
4597
|
border-radius: var(--radius);
|
|
@@ -4364,8 +4613,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4364
4613
|
|
|
4365
4614
|
& option {
|
|
4366
4615
|
position: relative;
|
|
4367
|
-
padding-block: var(--space-
|
|
4368
|
-
padding-inline: var(--space-
|
|
4616
|
+
padding-block: var(--space-20);
|
|
4617
|
+
padding-inline: var(--space-30);
|
|
4369
4618
|
white-space: normal;
|
|
4370
4619
|
transition-property: color, background-color;
|
|
4371
4620
|
transition-duration: var(--duration);
|
|
@@ -4393,14 +4642,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4393
4642
|
inline-size: 1rem;
|
|
4394
4643
|
block-size: 1rem;
|
|
4395
4644
|
background-color: currentColor;
|
|
4396
|
-
-webkit-mask
|
|
4397
|
-
mask
|
|
4398
|
-
-webkit-mask-repeat: no-repeat;
|
|
4399
|
-
mask-repeat: no-repeat;
|
|
4400
|
-
-webkit-mask-size: contain;
|
|
4401
|
-
mask-size: contain;
|
|
4402
|
-
-webkit-mask-position: center;
|
|
4403
|
-
mask-position: center;
|
|
4645
|
+
-webkit-mask: var(--icon-check) center / contain no-repeat;
|
|
4646
|
+
mask: var(--icon-check) center / contain no-repeat;
|
|
4404
4647
|
}
|
|
4405
4648
|
}
|
|
4406
4649
|
|
|
@@ -4416,9 +4659,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
4416
4659
|
}
|
|
4417
4660
|
|
|
4418
4661
|
/*
|
|
4419
|
-
* Native-only control polish: file input
|
|
4420
|
-
* so the framework stays out of the way and just keeps
|
|
4421
|
-
* theme-aware.
|
|
4662
|
+
* Native-only control polish: the file input and color picker mostly defeat
|
|
4663
|
+
* custom styling, so the framework stays out of the way and just keeps them
|
|
4664
|
+
* readable and theme-aware.
|
|
4422
4665
|
*
|
|
4423
4666
|
* Range has its own custom-styled file (range.css) so the focus ring can
|
|
4424
4667
|
* target the thumb pseudo-element rather than the rectangular CSS box.
|
|
@@ -4428,9 +4671,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
4428
4671
|
}
|
|
4429
4672
|
|
|
4430
4673
|
.file::file-selector-button {
|
|
4431
|
-
margin-inline-end: var(--space-
|
|
4432
|
-
padding-block: var(--space-
|
|
4433
|
-
padding-inline: var(--space-
|
|
4674
|
+
margin-inline-end: var(--space-30);
|
|
4675
|
+
padding-block: var(--space-10);
|
|
4676
|
+
padding-inline: var(--space-20);
|
|
4434
4677
|
border: var(--border-width) solid var(--border);
|
|
4435
4678
|
border-radius: var(--radius-sm);
|
|
4436
4679
|
background: var(--surface-subtle);
|
|
@@ -4449,17 +4692,56 @@ a.avatar[aria-disabled="true"] {
|
|
|
4449
4692
|
cursor: not-allowed;
|
|
4450
4693
|
}
|
|
4451
4694
|
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
background: var(--border);
|
|
4455
|
-
}
|
|
4695
|
+
.file:not(:disabled)::file-selector-button:hover {
|
|
4696
|
+
background: var(--border);
|
|
4456
4697
|
}
|
|
4457
4698
|
|
|
4458
4699
|
.file:focus-visible {
|
|
4459
|
-
outline:
|
|
4700
|
+
outline: 2px solid transparent;
|
|
4701
|
+
box-shadow: var(--focus-ring-shadow);
|
|
4702
|
+
}
|
|
4703
|
+
|
|
4704
|
+
/* Native color picker — keep the browser's dialog and selected-color UI. */
|
|
4705
|
+
.color {
|
|
4706
|
+
inline-size: var(--control-size);
|
|
4707
|
+
block-size: var(--control-size);
|
|
4708
|
+
padding: var(--space-10);
|
|
4709
|
+
border: var(--border-width) solid var(--border);
|
|
4710
|
+
border-radius: var(--radius);
|
|
4711
|
+
background: var(--surface);
|
|
4712
|
+
cursor: pointer;
|
|
4713
|
+
}
|
|
4714
|
+
|
|
4715
|
+
.color::-webkit-color-swatch-wrapper {
|
|
4716
|
+
padding: 0;
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
.color::-webkit-color-swatch {
|
|
4720
|
+
border: 0;
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
.color::-moz-color-swatch {
|
|
4724
|
+
border: 0;
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4727
|
+
.color:disabled {
|
|
4728
|
+
cursor: not-allowed;
|
|
4729
|
+
opacity: var(--disabled-opacity);
|
|
4730
|
+
}
|
|
4731
|
+
|
|
4732
|
+
.color:focus-visible {
|
|
4733
|
+
outline: 2px solid transparent;
|
|
4460
4734
|
box-shadow: var(--focus-ring-shadow);
|
|
4461
4735
|
}
|
|
4462
4736
|
|
|
4737
|
+
@media (forced-colors: active) {
|
|
4738
|
+
.color {
|
|
4739
|
+
border-color: ButtonText;
|
|
4740
|
+
background: ButtonFace;
|
|
4741
|
+
forced-color-adjust: none;
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4463
4745
|
/*
|
|
4464
4746
|
* Range slider — custom-styled so the focus ring always targets the
|
|
4465
4747
|
* user-manipulated surface (the thumb) rather than the CSS box.
|
|
@@ -4467,19 +4749,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
4467
4749
|
* appearance: none gives us full control over track and thumb; without it,
|
|
4468
4750
|
* pseudo-element rules for ::-webkit-slider-thumb and ::-moz-range-thumb
|
|
4469
4751
|
* are not reliably applied across browsers.
|
|
4752
|
+
*
|
|
4753
|
+
* Public hooks:
|
|
4754
|
+
* --range-thumb-size
|
|
4755
|
+
* --range-track-height
|
|
4756
|
+
* --range-thumb-bg
|
|
4757
|
+
* --range-track-bg
|
|
4470
4758
|
*/
|
|
4471
4759
|
|
|
4472
4760
|
/* Intent boundary — inherited intents do not tint this component.
|
|
4473
4761
|
Local intent classes still win because :where() has zero specificity. */
|
|
4474
4762
|
:where(.range) {
|
|
4475
|
-
/* @sync intent-boundary */
|
|
4476
4763
|
--intent: initial;
|
|
4477
|
-
--intent-fg: initial;
|
|
4478
|
-
--ui-bg: initial;
|
|
4479
|
-
--ui-fg: initial;
|
|
4480
|
-
--ui-border: initial;
|
|
4481
|
-
--ui-hover-bg: initial;
|
|
4482
|
-
/* @sync intent-boundary */
|
|
4483
4764
|
}
|
|
4484
4765
|
|
|
4485
4766
|
.range {
|
|
@@ -4530,7 +4811,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4530
4811
|
}
|
|
4531
4812
|
|
|
4532
4813
|
.range:focus-visible {
|
|
4533
|
-
outline:
|
|
4814
|
+
outline: 2px solid transparent;
|
|
4534
4815
|
}
|
|
4535
4816
|
|
|
4536
4817
|
/*
|
|
@@ -4551,28 +4832,41 @@ a.avatar[aria-disabled="true"] {
|
|
|
4551
4832
|
}
|
|
4552
4833
|
|
|
4553
4834
|
.range:disabled {
|
|
4554
|
-
--range-thumb-bg: var(--
|
|
4835
|
+
--range-thumb-bg: var(--state-disabled);
|
|
4555
4836
|
cursor: not-allowed;
|
|
4556
4837
|
opacity: var(--disabled-opacity);
|
|
4557
4838
|
}
|
|
4558
4839
|
|
|
4840
|
+
/* Forced colors: map the custom thumb/track to system colors. The focus
|
|
4841
|
+
indicator stays visible through the transparent outline on :focus-visible. */
|
|
4842
|
+
@media (forced-colors: active) {
|
|
4843
|
+
.range {
|
|
4844
|
+
--range-thumb-bg: ButtonText;
|
|
4845
|
+
--range-track-bg: Canvas;
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
|
|
4559
4849
|
/*
|
|
4560
4850
|
* Switch controls for checkbox inputs with switch semantics.
|
|
4561
4851
|
*
|
|
4562
4852
|
* The switch remains an input control, not a decorative toggle. Checked, hover,
|
|
4563
4853
|
* focus-visible, and disabled states stay clear.
|
|
4854
|
+
*
|
|
4855
|
+
* Public hooks:
|
|
4856
|
+
* --switch-bg
|
|
4857
|
+
* --switch-border
|
|
4858
|
+
* --switch-knob
|
|
4859
|
+
* --switch-width
|
|
4860
|
+
* --switch-block-size
|
|
4861
|
+
* --switch-knob-margin
|
|
4862
|
+
* --switch-knob-size
|
|
4863
|
+
* --switch-travel
|
|
4564
4864
|
*/
|
|
4565
4865
|
/* Intent boundary — inherited intents do not tint this component.
|
|
4566
4866
|
Local intent classes still win because :where() has zero specificity. */
|
|
4567
4867
|
:where(.switch) {
|
|
4568
|
-
/* @sync intent-boundary */
|
|
4569
4868
|
--intent: initial;
|
|
4570
4869
|
--intent-fg: initial;
|
|
4571
|
-
--ui-bg: initial;
|
|
4572
|
-
--ui-fg: initial;
|
|
4573
|
-
--ui-border: initial;
|
|
4574
|
-
--ui-hover-bg: initial;
|
|
4575
|
-
/* @sync intent-boundary */
|
|
4576
4870
|
}
|
|
4577
4871
|
|
|
4578
4872
|
.switch {
|
|
@@ -4620,10 +4914,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4620
4914
|
box-shadow var(--duration);
|
|
4621
4915
|
}
|
|
4622
4916
|
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
--switch-border: var(--intent, var(--primary));
|
|
4626
|
-
}
|
|
4917
|
+
.switch:hover:not(:disabled) {
|
|
4918
|
+
--switch-border: var(--intent, var(--primary));
|
|
4627
4919
|
}
|
|
4628
4920
|
|
|
4629
4921
|
.switch:checked {
|
|
@@ -4644,14 +4936,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
4644
4936
|
|
|
4645
4937
|
.switch:focus-visible {
|
|
4646
4938
|
--switch-border: var(--focus);
|
|
4647
|
-
outline:
|
|
4939
|
+
outline: 2px solid transparent;
|
|
4648
4940
|
box-shadow: var(--focus-ring-shadow);
|
|
4649
4941
|
}
|
|
4650
4942
|
|
|
4651
4943
|
.switch:disabled {
|
|
4652
4944
|
--switch-bg: var(--surface-subtle);
|
|
4653
4945
|
--switch-border: var(--border);
|
|
4654
|
-
--switch-knob: var(--
|
|
4946
|
+
--switch-knob: var(--state-disabled);
|
|
4655
4947
|
opacity: 1;
|
|
4656
4948
|
cursor: not-allowed;
|
|
4657
4949
|
}
|
|
@@ -4688,6 +4980,17 @@ a.avatar[aria-disabled="true"] {
|
|
|
4688
4980
|
*
|
|
4689
4981
|
* Imported by default through forms/index.css. Custom builds may omit this
|
|
4690
4982
|
* file when an app wants to own validation styling completely.
|
|
4983
|
+
*
|
|
4984
|
+
* State coverage (which states each component ships) is audited in
|
|
4985
|
+
* tests/validation.test.js and summarized in docs/pages/forms/validation.md —
|
|
4986
|
+
* not kept as a source comment that can drift from the implementation.
|
|
4987
|
+
*
|
|
4988
|
+
* Public hooks:
|
|
4989
|
+
* --form-invalid-border
|
|
4990
|
+
*
|
|
4991
|
+
* Internal:
|
|
4992
|
+
* --choice-bg
|
|
4993
|
+
* --switch-bg
|
|
4691
4994
|
*/
|
|
4692
4995
|
|
|
4693
4996
|
.field.danger {
|
|
@@ -4710,17 +5013,33 @@ a.avatar[aria-disabled="true"] {
|
|
|
4710
5013
|
--form-invalid-border: var(--danger);
|
|
4711
5014
|
}
|
|
4712
5015
|
|
|
4713
|
-
/* Override checked/indeterminate
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
5016
|
+
/* Override checked/indeterminate fill so both border and fill signal danger
|
|
5017
|
+
instead of a red border around a primary-colored control. All three
|
|
5018
|
+
invalid routes must converge: aria-invalid, was-validated :invalid, and
|
|
5019
|
+
:user-invalid. */
|
|
5020
|
+
.check:is(:checked, :indeterminate)[aria-invalid="true"],
|
|
5021
|
+
.needs-validation.was-validated .check:is(:checked, :indeterminate):invalid,
|
|
5022
|
+
.radio:checked[aria-invalid="true"],
|
|
5023
|
+
.needs-validation.was-validated .radio:checked:invalid {
|
|
4717
5024
|
--choice-bg: var(--danger);
|
|
4718
5025
|
}
|
|
4719
5026
|
|
|
4720
|
-
.switch[aria-invalid="true"]
|
|
5027
|
+
.switch:checked[aria-invalid="true"],
|
|
5028
|
+
.needs-validation.was-validated .switch:checked:invalid {
|
|
4721
5029
|
--switch-bg: var(--danger);
|
|
4722
5030
|
}
|
|
4723
5031
|
|
|
5032
|
+
@supports selector(:user-invalid) {
|
|
5033
|
+
.needs-validation .check:is(:checked, :indeterminate):user-invalid,
|
|
5034
|
+
.needs-validation .radio:checked:user-invalid {
|
|
5035
|
+
--choice-bg: var(--danger);
|
|
5036
|
+
}
|
|
5037
|
+
|
|
5038
|
+
.needs-validation .switch:checked:user-invalid {
|
|
5039
|
+
--switch-bg: var(--danger);
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
|
|
4724
5043
|
/* Choice cards hide their native input, so invalid state must surface on the
|
|
4725
5044
|
card itself. Sets the same --form-invalid-border hook the card's border
|
|
4726
5045
|
consumes. */
|
|
@@ -4753,13 +5072,24 @@ a.avatar[aria-disabled="true"] {
|
|
|
4753
5072
|
* A flex cluster of buttons/actions that closes a form. The sticky variant
|
|
4754
5073
|
* docks to the viewport bottom with a translucent blur where supported; at
|
|
4755
5074
|
* rest it stays visually neutral so it does not double as a separator.
|
|
5075
|
+
*
|
|
5076
|
+
* Public hooks:
|
|
5077
|
+
* --form-actions-align
|
|
5078
|
+
* --form-actions-justify
|
|
5079
|
+
* --form-actions-margin-block-start
|
|
5080
|
+
*
|
|
5081
|
+
* Internal:
|
|
5082
|
+
* --form-actions-sticky-block-offset (owned by .form-actions.sticky)
|
|
5083
|
+
* --form-actions-sticky-inline-offset (owned by .form-actions.sticky)
|
|
5084
|
+
* --form-actions-sticky-padding (owned by .form-actions.sticky)
|
|
4756
5085
|
*/
|
|
4757
5086
|
.form-actions {
|
|
4758
5087
|
display: flex;
|
|
4759
5088
|
flex-wrap: wrap;
|
|
4760
5089
|
gap: var(--gap);
|
|
4761
|
-
align-items: center;
|
|
4762
|
-
|
|
5090
|
+
align-items: var(--form-actions-align, center);
|
|
5091
|
+
justify-content: var(--form-actions-justify, flex-start);
|
|
5092
|
+
margin-block-start: var(--form-actions-margin-block-start, var(--space-50));
|
|
4763
5093
|
}
|
|
4764
5094
|
|
|
4765
5095
|
/* Opt-in sticky variant. When the bar is not stuck (at rest) the translucent
|
|
@@ -4773,7 +5103,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4773
5103
|
isolation: isolate;
|
|
4774
5104
|
|
|
4775
5105
|
margin-inline: calc(var(--form-actions-sticky-inline-offset, 0px) * -1);
|
|
4776
|
-
padding: var(--form-actions-sticky-padding, var(--space-
|
|
5106
|
+
padding: var(--form-actions-sticky-padding, var(--space-30));
|
|
4777
5107
|
|
|
4778
5108
|
border-block-start: var(--border-width) solid var(--border);
|
|
4779
5109
|
background: var(--surface);
|
|
@@ -4823,7 +5153,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4823
5153
|
min-inline-size: 0;
|
|
4824
5154
|
}
|
|
4825
5155
|
|
|
4826
|
-
.nowrap {
|
|
5156
|
+
.text-nowrap {
|
|
4827
5157
|
white-space: nowrap;
|
|
4828
5158
|
}
|
|
4829
5159
|
|
|
@@ -4918,15 +5248,13 @@ a.avatar[aria-disabled="true"] {
|
|
|
4918
5248
|
text-decoration: none;
|
|
4919
5249
|
}
|
|
4920
5250
|
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
color: var(--text);
|
|
4924
|
-
}
|
|
5251
|
+
.link-muted:hover {
|
|
5252
|
+
color: var(--text);
|
|
4925
5253
|
}
|
|
4926
5254
|
|
|
4927
5255
|
/* Spacing step helpers — semantic scale (sm / default / lg).
|
|
4928
5256
|
Names map to logical properties for writing-direction safety. The default
|
|
4929
|
-
step uses --space-
|
|
5257
|
+
step uses --space-40, which matches the framework's built-in padding on
|
|
4930
5258
|
cards, dialogs, and form-actions. */
|
|
4931
5259
|
|
|
4932
5260
|
/* Gap */
|
|
@@ -4936,22 +5264,22 @@ a.avatar[aria-disabled="true"] {
|
|
|
4936
5264
|
|
|
4937
5265
|
/* Padding — block (vertical) */
|
|
4938
5266
|
.py {
|
|
4939
|
-
padding-block: var(--
|
|
5267
|
+
padding-block: var(--density-space);
|
|
4940
5268
|
}
|
|
4941
5269
|
|
|
4942
5270
|
/* Padding — inline (horizontal) */
|
|
4943
5271
|
.px {
|
|
4944
|
-
padding-inline: var(--
|
|
5272
|
+
padding-inline: var(--density-space);
|
|
4945
5273
|
}
|
|
4946
5274
|
|
|
4947
5275
|
/* Margin — block-start */
|
|
4948
5276
|
.mbs {
|
|
4949
|
-
margin-block-start: var(--
|
|
5277
|
+
margin-block-start: var(--density-space);
|
|
4950
5278
|
}
|
|
4951
5279
|
|
|
4952
5280
|
/* Margin — block-end */
|
|
4953
5281
|
.mbe {
|
|
4954
|
-
margin-block-end: var(--
|
|
5282
|
+
margin-block-end: var(--density-space);
|
|
4955
5283
|
}
|
|
4956
5284
|
|
|
4957
5285
|
/* Joined controls are a component, but must load after the controls it groups. */
|
|
@@ -4964,6 +5292,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
4964
5292
|
* Cascade note: .join writes border-radius longhands on its direct children.
|
|
4965
5293
|
* Keep this file loaded after control/component styles so those corners win at
|
|
4966
5294
|
* equal specificity.
|
|
5295
|
+
*
|
|
5296
|
+
* Public hooks:
|
|
5297
|
+
* --join-radius
|
|
5298
|
+
*
|
|
5299
|
+
* Internal:
|
|
5300
|
+
* --join-ss
|
|
5301
|
+
* --join-se
|
|
5302
|
+
* --join-es
|
|
5303
|
+
* --join-ee
|
|
5304
|
+
* --control-border
|
|
5305
|
+
* --control-bg
|
|
5306
|
+
* --control-fg
|
|
4967
5307
|
*/
|
|
4968
5308
|
.join {
|
|
4969
5309
|
--join-radius: var(--radius);
|
|
@@ -4991,7 +5331,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4991
5331
|
display: inline-flex;
|
|
4992
5332
|
justify-content: center;
|
|
4993
5333
|
min-block-size: var(--control-size);
|
|
4994
|
-
padding-inline: var(--space-
|
|
5334
|
+
padding-inline: var(--space-30);
|
|
4995
5335
|
border: var(--border-width) solid var(--control-border);
|
|
4996
5336
|
border-radius: var(--radius);
|
|
4997
5337
|
background: var(--control-bg);
|
|
@@ -5052,7 +5392,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
5052
5392
|
focus, and unsupported :has() browsers retain the control's normal ring. */
|
|
5053
5393
|
.join:has(> :is(.input, .textarea, .select):focus-visible) {
|
|
5054
5394
|
border-radius: var(--join-radius);
|
|
5055
|
-
outline:
|
|
5395
|
+
outline: 2px solid transparent;
|
|
5056
5396
|
box-shadow: var(--focus-ring-shadow);
|
|
5057
5397
|
}
|
|
5058
5398
|
|
|
@@ -5061,9 +5401,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
5061
5401
|
box-shadow: none;
|
|
5062
5402
|
}
|
|
5063
5403
|
|
|
5064
|
-
/* Focus baseline —
|
|
5065
|
-
|
|
5066
|
-
|
|
5404
|
+
/* Focus baseline — a visible outline is the guaranteed focus affordance. The
|
|
5405
|
+
:focus outline covers legacy browsers; modern browsers hide it for
|
|
5406
|
+
pointer/mouse focus and interactive components layer their ring on top.
|
|
5407
|
+
|
|
5408
|
+
Invariant: an interactive component must never rely on box-shadow alone
|
|
5409
|
+
for its keyboard focus indicator. Components that remove the native
|
|
5410
|
+
outline on :focus-visible replace it with a transparent outline — invisible
|
|
5411
|
+
normally, forced to the system focus color under forced-colors. */
|
|
5067
5412
|
:focus {
|
|
5068
5413
|
outline: var(--focus-outline);
|
|
5069
5414
|
outline-offset: var(--focus-outline-offset);
|
|
@@ -5073,42 +5418,13 @@ a.avatar[aria-disabled="true"] {
|
|
|
5073
5418
|
:focus:not(:focus-visible) {
|
|
5074
5419
|
outline: none;
|
|
5075
5420
|
}
|
|
5076
|
-
}
|
|
5077
|
-
|
|
5078
|
-
/*
|
|
5079
|
-
* Forced-colors focus — centralised so individual components do not need
|
|
5080
|
-
* their own fallback. The double :focus keeps specificity high enough
|
|
5081
|
-
* (0,0,2,0) to beat a single :focus-visible; against a class selector like
|
|
5082
|
-
* .input:focus-visible it ties, but this file loads last so it wins on
|
|
5083
|
-
* source order.
|
|
5084
|
-
*
|
|
5085
|
-
* .choice-card and .join receive focus indirectly (:has on a wrapper that
|
|
5086
|
-
* never matches :focus), so they are listed explicitly.
|
|
5087
|
-
*
|
|
5088
|
-
* .range is excluded from the rectangular fallback: its focus is drawn on
|
|
5089
|
-
* the thumb pseudo-element, which is the manipulated surface.
|
|
5090
|
-
*
|
|
5091
|
-
* box-shadow: none is redundant when forced-color-adjust is auto, but safe
|
|
5092
|
-
* when a component uses forced-color-adjust: none (e.g. checked switches /
|
|
5093
|
-
* checkboxes).
|
|
5094
|
-
*/
|
|
5095
|
-
@media (forced-colors: active) {
|
|
5096
|
-
:focus:focus-visible:not(.range),
|
|
5097
|
-
.choice-card:has(:focus-visible),
|
|
5098
|
-
.join:has(> :is(.input, .textarea, .select):focus-visible) {
|
|
5099
|
-
outline: var(--focus-outline);
|
|
5100
|
-
outline-offset: var(--focus-outline-offset);
|
|
5101
|
-
box-shadow: none;
|
|
5102
|
-
}
|
|
5103
|
-
|
|
5104
|
-
.range:focus-visible::-webkit-slider-thumb {
|
|
5105
|
-
outline: var(--focus-outline);
|
|
5106
|
-
outline-offset: var(--focus-outline-offset);
|
|
5107
|
-
}
|
|
5108
5421
|
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5422
|
+
/* Pointer focus must still signal in high-contrast mode, where the shared
|
|
5423
|
+
box-shadow rings are disabled (--focus-ring-shadow: none). */
|
|
5424
|
+
@media (forced-colors: active) {
|
|
5425
|
+
:focus:not(:focus-visible) {
|
|
5426
|
+
outline: var(--focus-outline);
|
|
5427
|
+
}
|
|
5112
5428
|
}
|
|
5113
5429
|
}
|
|
5114
5430
|
|
|
@@ -5159,3 +5475,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
5159
5475
|
break-after: avoid;
|
|
5160
5476
|
}
|
|
5161
5477
|
}
|
|
5478
|
+
|
|
5479
|
+
|
|
5480
|
+
/*
|
|
5481
|
+
* NOTE: The 'optional/' directory contains layout and utility extensions
|
|
5482
|
+
* (such as optional grids, split, visual effects, and extra utility classes
|
|
5483
|
+
* like .gap-sm, .flex-nowrap, .shadow-sm, etc.). These are intentionally not
|
|
5484
|
+
* bundled here. Two entry points ship them:
|
|
5485
|
+
*
|
|
5486
|
+
* actual-css/css/optional/index optional layer only
|
|
5487
|
+
* actual-css/css/actual.full core + optional
|
|
5488
|
+
*
|
|
5489
|
+
* Import one of these paths in your project when you need them.
|
|
5490
|
+
*/
|