actual-css 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -1
- package/README.md +25 -8
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1492 -931
- 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 +95 -6
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +101 -0
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +3 -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 +119 -0
- 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 +10 -4
- package/package.json +30 -16
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -9
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +162 -49
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +82 -39
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +7 -0
- package/src/css/components/button.css +82 -38
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -110
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -3
- 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/scrollspy.css +2 -2
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +10 -11
- package/src/css/components/status-bar.css +18 -28
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +20 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +16 -3
- package/src/css/forms/choice-card.css +13 -15
- package/src/css/forms/choice.css +21 -28
- package/src/css/forms/control.css +16 -26
- 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 +5 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +48 -33
- package/src/css/forms/range.css +103 -0
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -13
- 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 +19 -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 +29 -13
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +11 -37
- 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 +81 -21
- 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 +72 -53
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +76 -19
- 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 +106 -96
- 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 +89 -35
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +133 -62
- package/src/js/selectors.js +14 -31
- package/src/js/status.js +16 -14
- package/src/js/surface.js +219 -125
- package/src/js/tab.js +46 -66
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +45 -51
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -26
- 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 -426
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -415
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -937
- 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,23 @@ 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: 0 0
|
|
99
|
+
--shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04);
|
|
147
100
|
--shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15);
|
|
148
101
|
|
|
149
102
|
/* Motion — see reset.css for the prefers-reduced-motion hard reset.
|
|
@@ -157,14 +110,15 @@ select {
|
|
|
157
110
|
--ease: ease;
|
|
158
111
|
|
|
159
112
|
/* Focus ring — shared by every interactive component's :focus-visible */
|
|
160
|
-
--focus-outline:
|
|
113
|
+
--focus-outline-color: currentColor;
|
|
114
|
+
--focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color);
|
|
161
115
|
--focus-outline-offset: 2px;
|
|
116
|
+
--focus-ring-width: 3px;
|
|
162
117
|
|
|
163
118
|
/* Status dot positioning — used by .avatar > .badge:empty. The offset is
|
|
164
119
|
the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
|
|
165
120
|
dot with the parent surface color. */
|
|
166
121
|
--indicator-offset: 14.65%;
|
|
167
|
-
--indicator-ring: var(--surface);
|
|
168
122
|
|
|
169
123
|
/* Shared backdrop — consumed by modal, drawer, and mobile flyout sheets. */
|
|
170
124
|
--backdrop-color: rgb(0 0 0);
|
|
@@ -175,7 +129,8 @@ select {
|
|
|
175
129
|
--bar-height: 0.65rem;
|
|
176
130
|
|
|
177
131
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
178
|
-
.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. */
|
|
179
134
|
--control-size-sm: 2rem;
|
|
180
135
|
--control-size-md: 2.375rem;
|
|
181
136
|
--control-size-lg: 2.75rem;
|
|
@@ -183,19 +138,38 @@ select {
|
|
|
183
138
|
--control-pad-x: 1em;
|
|
184
139
|
--control-font-size: var(--font-size-md);
|
|
185
140
|
--disabled-opacity: 0.65;
|
|
141
|
+
}
|
|
186
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 {
|
|
187
163
|
/* Variant tuning — themes may adjust generated soft surfaces without
|
|
188
164
|
replacing the component rules. */
|
|
189
165
|
--soft-bg-mix: 88%;
|
|
190
166
|
--soft-border-mix: 65%;
|
|
191
167
|
--soft-hover-alpha: 12%;
|
|
192
168
|
|
|
193
|
-
/* Shared
|
|
194
|
-
--icon-chevron
|
|
195
|
-
--icon-check
|
|
196
|
-
|
|
197
|
-
The stroke matches the default theme's text-subtle (hue 258 neutral). */
|
|
198
|
-
--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");
|
|
199
173
|
|
|
200
174
|
/* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
|
|
201
175
|
Consumed by full-viewport components (app-shell, drawer, modal, flyout).
|
|
@@ -204,13 +178,24 @@ select {
|
|
|
204
178
|
--viewport-block: 100vh;
|
|
205
179
|
--viewport-inline: 100vw;
|
|
206
180
|
|
|
207
|
-
/*
|
|
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. */
|
|
208
185
|
--z-sticky: 10;
|
|
209
186
|
--z-menu: 20;
|
|
210
187
|
--z-tooltip: 50;
|
|
211
188
|
--z-status: 60;
|
|
212
189
|
}
|
|
213
190
|
|
|
191
|
+
@media (forced-colors: active) {
|
|
192
|
+
:root {
|
|
193
|
+
--state-selected: Highlight;
|
|
194
|
+
--state-selected-fg: HighlightText;
|
|
195
|
+
--state-disabled: GrayText;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
214
199
|
@supports (block-size: 100dvh) {
|
|
215
200
|
:root {
|
|
216
201
|
--viewport-block: 100dvh;
|
|
@@ -224,9 +209,17 @@ select {
|
|
|
224
209
|
}
|
|
225
210
|
|
|
226
211
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
227
|
-
:root
|
|
228
|
-
|
|
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. */
|
|
216
|
+
--shadow:
|
|
217
|
+
0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent),
|
|
218
|
+
0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent);
|
|
229
219
|
--shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
:root {
|
|
230
223
|
--backdrop-fill: color-mix(
|
|
231
224
|
in oklch,
|
|
232
225
|
var(--backdrop-color) calc(var(--backdrop-opacity) * 100%),
|
|
@@ -235,6 +228,17 @@ select {
|
|
|
235
228
|
}
|
|
236
229
|
}
|
|
237
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
|
+
*/
|
|
238
242
|
:root {
|
|
239
243
|
color-scheme: light dark;
|
|
240
244
|
}
|
|
@@ -298,11 +302,10 @@ select {
|
|
|
298
302
|
|
|
299
303
|
/* Lines and focus */
|
|
300
304
|
--border: hsl(260 15% 88%);
|
|
301
|
-
--focus: var(--
|
|
305
|
+
--focus: var(--neutral);
|
|
302
306
|
/* Pre-color-mix fallback only — superseded below by a derivation from
|
|
303
|
-
--focus so overriding --
|
|
304
|
-
--focus-ring: hsl(
|
|
305
|
-
--focus-ring-shadow: 0 0 0 3px var(--focus-ring);
|
|
307
|
+
--focus so overriding --neutral/--focus never leaves a stale ring. */
|
|
308
|
+
--focus-ring: hsl(258 7% 43% / 0.3);
|
|
306
309
|
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
307
310
|
|
|
308
311
|
/* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
|
|
@@ -313,13 +316,19 @@ select {
|
|
|
313
316
|
/* Shadow color — consumed by --shadow / --shadow-popout in tokens.css */
|
|
314
317
|
--shadow-color: hsl(260 25% 10%);
|
|
315
318
|
|
|
316
|
-
/*
|
|
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] {
|
|
317
328
|
--heading: var(--text);
|
|
318
329
|
--selection-bg: var(--primary);
|
|
319
330
|
--selection-fg: var(--primary-fg);
|
|
320
|
-
|
|
321
|
-
/* hgroup internal gap */
|
|
322
|
-
--hgroup-gap: 0.25em;
|
|
331
|
+
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
323
332
|
}
|
|
324
333
|
|
|
325
334
|
/* Tone subsystem — fixed-backdrop foreground for components placed over media
|
|
@@ -343,7 +352,7 @@ select {
|
|
|
343
352
|
:root,
|
|
344
353
|
[data-theme="light"],
|
|
345
354
|
[data-theme="dark"] {
|
|
346
|
-
--focus-ring: color-mix(in oklch, var(--focus)
|
|
355
|
+
--focus-ring: color-mix(in oklch, var(--focus) 30%, transparent);
|
|
347
356
|
}
|
|
348
357
|
}
|
|
349
358
|
|
|
@@ -383,7 +392,7 @@ select {
|
|
|
383
392
|
--text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%));
|
|
384
393
|
|
|
385
394
|
--border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%));
|
|
386
|
-
--focus-ring: light-dark(hsl(
|
|
395
|
+
--focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28));
|
|
387
396
|
--hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06));
|
|
388
397
|
--shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%));
|
|
389
398
|
}
|
|
@@ -393,7 +402,7 @@ select {
|
|
|
393
402
|
:root,
|
|
394
403
|
[data-theme="light"] {
|
|
395
404
|
--focus-ring: light-dark(
|
|
396
|
-
color-mix(in oklch, var(--focus)
|
|
405
|
+
color-mix(in oklch, var(--focus) 30%, transparent),
|
|
397
406
|
color-mix(in oklch, var(--focus) 28%, transparent)
|
|
398
407
|
);
|
|
399
408
|
}
|
|
@@ -403,7 +412,7 @@ select {
|
|
|
403
412
|
:root,
|
|
404
413
|
[data-theme="light"],
|
|
405
414
|
[data-theme="dark"] {
|
|
406
|
-
--focus-outline:
|
|
415
|
+
--focus-outline-color: Highlight;
|
|
407
416
|
--focus-ring-shadow: none;
|
|
408
417
|
|
|
409
418
|
--backdrop-fill: Canvas;
|
|
@@ -416,7 +425,7 @@ select {
|
|
|
416
425
|
[data-theme="dark"] {
|
|
417
426
|
--border-width: 2px;
|
|
418
427
|
--focus-outline-offset: 3px;
|
|
419
|
-
--focus-ring-
|
|
428
|
+
--focus-ring-width: 4px;
|
|
420
429
|
--text-muted: var(--text);
|
|
421
430
|
--text-subtle: var(--text);
|
|
422
431
|
}
|
|
@@ -441,14 +450,8 @@ a {
|
|
|
441
450
|
text-underline-offset: 0.18em;
|
|
442
451
|
}
|
|
443
452
|
|
|
444
|
-
a:
|
|
445
|
-
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
@media (hover: hover) {
|
|
449
|
-
a:hover {
|
|
450
|
-
text-decoration-thickness: 0.12em;
|
|
451
|
-
}
|
|
453
|
+
a:hover {
|
|
454
|
+
text-decoration-thickness: 0.12em;
|
|
452
455
|
}
|
|
453
456
|
|
|
454
457
|
/* Global heading chrome: color (--heading or inherit), a tight line-height,
|
|
@@ -474,7 +477,7 @@ hgroup > * {
|
|
|
474
477
|
hr {
|
|
475
478
|
border: 0;
|
|
476
479
|
border-block-start: var(--border-width) solid var(--border);
|
|
477
|
-
margin-block: var(--space-
|
|
480
|
+
margin-block: var(--space-60);
|
|
478
481
|
}
|
|
479
482
|
|
|
480
483
|
code,
|
|
@@ -497,8 +500,8 @@ small {
|
|
|
497
500
|
* Intent classes do not style elements directly.
|
|
498
501
|
* They only expose --intent and --intent-fg.
|
|
499
502
|
* Components and variants decide how to consume them. Each component that
|
|
500
|
-
* consumes --intent or --ui-*
|
|
501
|
-
* 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
|
|
502
505
|
* do not leak into nested components. Local intent classes (.primary, .danger,
|
|
503
506
|
* etc.) still apply because they have higher specificity than :where().
|
|
504
507
|
*/
|
|
@@ -527,6 +530,28 @@ small {
|
|
|
527
530
|
--intent-fg: var(--neutral-fg);
|
|
528
531
|
}
|
|
529
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
|
+
*/
|
|
530
555
|
.container-query {
|
|
531
556
|
container-type: inline-size;
|
|
532
557
|
}
|
|
@@ -543,12 +568,16 @@ small {
|
|
|
543
568
|
|
|
544
569
|
.cluster {
|
|
545
570
|
display: flex;
|
|
546
|
-
flex-wrap: wrap;
|
|
571
|
+
flex-wrap: var(--cluster-wrap, wrap);
|
|
547
572
|
align-items: var(--cluster-align, center);
|
|
548
573
|
justify-content: var(--cluster-justify, flex-start);
|
|
549
574
|
gap: var(--gap);
|
|
550
575
|
}
|
|
551
576
|
|
|
577
|
+
.cluster > * {
|
|
578
|
+
margin: 0;
|
|
579
|
+
}
|
|
580
|
+
|
|
552
581
|
.center {
|
|
553
582
|
box-sizing: content-box;
|
|
554
583
|
max-inline-size: var(--center-size, 72rem);
|
|
@@ -592,6 +621,8 @@ small {
|
|
|
592
621
|
grid-template-columns: minmax(0, 1fr);
|
|
593
622
|
}
|
|
594
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. */
|
|
595
626
|
@container (width >= 64rem) {
|
|
596
627
|
.container-query .sidebar-layout {
|
|
597
628
|
grid-template-columns: minmax(0, 1fr) var(--sidebar-layout-size, 18rem);
|
|
@@ -608,29 +639,27 @@ small {
|
|
|
608
639
|
}
|
|
609
640
|
}
|
|
610
641
|
|
|
642
|
+
/*
|
|
643
|
+
* Responsive equal-width grids, with fixed-column presets below.
|
|
644
|
+
*
|
|
645
|
+
* Public hooks:
|
|
646
|
+
* --grid-min
|
|
647
|
+
* --grid-columns
|
|
648
|
+
*/
|
|
611
649
|
.grid {
|
|
612
650
|
display: grid;
|
|
613
651
|
gap: var(--gap);
|
|
614
|
-
grid-template-columns:
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
* .grid-6: 6 cols
|
|
626
|
-
* When placed inside .container-query, the column count collapses
|
|
627
|
-
* on narrow wrappers. The intermediate 2-col step applies to
|
|
628
|
-
* .grid-3/.grid-4; .grid-6 starts at 2 columns because it is meant
|
|
629
|
-
* for compact items that do not need a full single-column stack.
|
|
630
|
-
* .grid-2: 1 col -> 2 cols
|
|
631
|
-
* .grid-3: 1 col -> 2 cols -> 3 cols
|
|
632
|
-
* .grid-4: 1 col -> 2 cols -> 4 cols
|
|
633
|
-
* .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). */
|
|
634
663
|
.grid-2,
|
|
635
664
|
.grid-3,
|
|
636
665
|
.grid-4,
|
|
@@ -652,6 +681,9 @@ small {
|
|
|
652
681
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
653
682
|
}
|
|
654
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. */
|
|
655
687
|
@container (width < 28rem) {
|
|
656
688
|
.container-query .grid-2,
|
|
657
689
|
.container-query .grid-3,
|
|
@@ -708,15 +740,20 @@ small {
|
|
|
708
740
|
* Cross-cutting variant token mapping.
|
|
709
741
|
*
|
|
710
742
|
* Variant scope: .solid/.soft/.outline set --ui-* for components that opt into
|
|
711
|
-
* shared variants.
|
|
712
|
-
*
|
|
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:
|
|
713
748
|
* components/button.css — .btn default solid + .btn.ghost + .btn.link
|
|
714
|
-
* components/badge.css — .badge default
|
|
715
|
-
* components/alert.css — .alert default
|
|
749
|
+
* components/badge.css — .badge default soft
|
|
750
|
+
* components/alert.css — .alert default soft (consumes --ui-* directly)
|
|
716
751
|
*
|
|
717
|
-
* .sm/.lg set shared
|
|
718
|
-
*
|
|
719
|
-
*
|
|
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.
|
|
720
757
|
* Components with bespoke sizing (spinner, avatar) define local .sm/.lg;
|
|
721
758
|
* applying .sm/.lg to card, dialog, accordion, navbar, table is
|
|
722
759
|
* a no-op.
|
|
@@ -742,27 +779,34 @@ small {
|
|
|
742
779
|
--ui-border: var(--intent, var(--border));
|
|
743
780
|
}
|
|
744
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
|
+
|
|
745
799
|
.sm {
|
|
746
|
-
--gap: var(--space-
|
|
747
|
-
--
|
|
800
|
+
--gap: var(--space-20);
|
|
801
|
+
--density-space: var(--space-20);
|
|
748
802
|
--control-size: var(--control-size-sm);
|
|
749
|
-
--
|
|
750
|
-
--variant-font-size: var(--font-size-sm);
|
|
751
|
-
--variant-icon-size: 1rem;
|
|
752
|
-
--variant-pad-block: var(--space-3);
|
|
753
|
-
--variant-compact-size: 1.25rem;
|
|
754
|
-
--variant-compact-font-size: 0.75rem;
|
|
803
|
+
--density-compact-size: 1.25rem;
|
|
755
804
|
}
|
|
756
805
|
.lg {
|
|
757
|
-
--gap: var(--space-
|
|
758
|
-
--
|
|
806
|
+
--gap: var(--space-50);
|
|
807
|
+
--density-space: var(--space-50);
|
|
759
808
|
--control-size: var(--control-size-lg);
|
|
760
|
-
--
|
|
761
|
-
--variant-font-size: var(--font-size-lg);
|
|
762
|
-
--variant-icon-size: 1.5rem;
|
|
763
|
-
--variant-pad-block: var(--space-5);
|
|
764
|
-
--variant-compact-size: 1.875rem;
|
|
765
|
-
--variant-compact-font-size: var(--font-size-sm);
|
|
809
|
+
--density-compact-size: 1.875rem;
|
|
766
810
|
}
|
|
767
811
|
|
|
768
812
|
/* Modern color enhancement. Baseline variant values above remain usable
|
|
@@ -780,54 +824,25 @@ small {
|
|
|
780
824
|
}
|
|
781
825
|
}
|
|
782
826
|
|
|
783
|
-
/* Interactive hover
|
|
784
|
-
|
|
785
|
-
rules, so these values are inert until hovered. :is() carries real
|
|
786
|
-
specificity so the components' :where() intent boundaries
|
|
787
|
-
(--ui-hover-bg: initial) cannot override the recipe. The solid/soft overlay
|
|
788
|
-
darkening lives in the consuming components. */
|
|
789
|
-
:is(.btn, a.badge, button.badge):is(.outline, .ghost) {
|
|
790
|
-
--ui-hover-bg: var(--surface-subtle);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
794
|
-
/* Solid has no literal class requirement: bare .btn/.badge default to solid,
|
|
795
|
-
so this must match anything that isn't one of the other variants rather
|
|
796
|
-
than an explicit .solid class. Stacks with the box-shadow overlay the
|
|
797
|
-
consuming components apply on top for extra depth. */
|
|
798
|
-
:is(.btn, a.badge, button.badge):not(:where(.soft, .outline, .ghost, .link)) {
|
|
799
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/* Same recipe as the resting .soft bg with the surface share reduced by
|
|
803
|
-
--soft-hover-alpha: stays opaque, so it reads on any backdrop instead of
|
|
804
|
-
letting the container show through on hover. */
|
|
805
|
-
:is(.btn, a.badge, button.badge):is(.soft) {
|
|
806
|
-
--ui-hover-bg: color-mix(
|
|
807
|
-
in oklch,
|
|
808
|
-
var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
|
|
809
|
-
var(--intent, var(--neutral))
|
|
810
|
-
);
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/* Mixed against the opaque --surface-subtle rather than transparent: a
|
|
814
|
-
transparent-based tint reads clean on light surfaces but goes muddy and
|
|
815
|
-
low-contrast on dark ones, since the same low alpha lands very
|
|
816
|
-
differently over a light vs. a near-black backdrop. */
|
|
817
|
-
:is(.btn, a.badge, button.badge):is(.outline) {
|
|
818
|
-
--ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
:is(.btn, a.badge, button.badge):is(.ghost) {
|
|
822
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
827
|
+
/* Interactive hover recipes live in the components that consume them:
|
|
828
|
+
components/button.css and components/badge.css. */
|
|
825
829
|
|
|
826
830
|
/*
|
|
827
831
|
* Prose is the opt-in rich text scope for authored content.
|
|
828
832
|
*
|
|
829
833
|
* Global element styles stay mild so app UI is not shaped accidentally. Page
|
|
830
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
|
|
831
846
|
*/
|
|
832
847
|
|
|
833
848
|
.prose {
|
|
@@ -891,15 +906,13 @@ small {
|
|
|
891
906
|
text-underline-offset: 0.18em;
|
|
892
907
|
}
|
|
893
908
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
909
|
+
.prose a:hover {
|
|
910
|
+
text-decoration-thickness: 0.12em;
|
|
911
|
+
}
|
|
898
912
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
}
|
|
913
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
914
|
+
.prose a:hover {
|
|
915
|
+
color: color-mix(in oklch, var(--primary), var(--text) 15%);
|
|
903
916
|
}
|
|
904
917
|
}
|
|
905
918
|
|
|
@@ -919,6 +932,7 @@ small {
|
|
|
919
932
|
font-size: 0.875em;
|
|
920
933
|
}
|
|
921
934
|
|
|
935
|
+
/* Visual twin of .key (components/key.css) — keep the two recipes in sync. */
|
|
922
936
|
.prose kbd {
|
|
923
937
|
display: inline-flex;
|
|
924
938
|
align-items: center;
|
|
@@ -1023,19 +1037,35 @@ small {
|
|
|
1023
1037
|
* Disabled state uses the native disabled attribute for buttons and inputs.
|
|
1024
1038
|
* Anchors have no native disabled state, so a.btn uses aria-disabled="true"
|
|
1025
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
|
|
1026
1058
|
*/
|
|
1027
1059
|
|
|
1028
1060
|
/* Intent boundary — inherited intents do not tint this component.
|
|
1029
1061
|
Local intent classes still win because :where() has zero specificity. */
|
|
1030
1062
|
:where(.btn) {
|
|
1031
|
-
/* @sync intent-boundary */
|
|
1032
1063
|
--intent: initial;
|
|
1033
1064
|
--intent-fg: initial;
|
|
1034
1065
|
--ui-bg: initial;
|
|
1035
1066
|
--ui-fg: initial;
|
|
1036
1067
|
--ui-border: initial;
|
|
1037
1068
|
--ui-hover-bg: initial;
|
|
1038
|
-
/* @sync intent-boundary */
|
|
1039
1069
|
}
|
|
1040
1070
|
|
|
1041
1071
|
.btn {
|
|
@@ -1047,18 +1077,17 @@ small {
|
|
|
1047
1077
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
1048
1078
|
--btn-radius: var(--radius);
|
|
1049
1079
|
--btn-focus-color: var(--intent, var(--focus));
|
|
1050
|
-
--btn-focus-outline-width: calc(var(--border-width) * 2);
|
|
1051
|
-
--btn-hover-bg: var(--ui-hover-bg, var(--btn-bg));
|
|
1052
1080
|
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
1053
1081
|
|
|
1082
|
+
/* Positioning context for indicators, overlays, and grouped states. */
|
|
1054
1083
|
position: relative;
|
|
1055
1084
|
display: inline-flex;
|
|
1056
1085
|
align-items: center;
|
|
1057
1086
|
justify-content: center;
|
|
1058
|
-
gap: 0.
|
|
1087
|
+
gap: var(--btn-gap, 0.375em);
|
|
1059
1088
|
min-block-size: var(--btn-min-size);
|
|
1060
|
-
padding-block: var(--space-
|
|
1061
|
-
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));
|
|
1062
1091
|
padding-inline: var(--btn-pad-x);
|
|
1063
1092
|
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
1064
1093
|
border-block-end-color: var(
|
|
@@ -1071,7 +1100,7 @@ small {
|
|
|
1071
1100
|
color: var(--btn-fg);
|
|
1072
1101
|
font: inherit;
|
|
1073
1102
|
font-size: var(--btn-font-size);
|
|
1074
|
-
font-weight: var(--font-weight-
|
|
1103
|
+
font-weight: var(--font-weight-medium);
|
|
1075
1104
|
line-height: 1;
|
|
1076
1105
|
text-decoration: none;
|
|
1077
1106
|
cursor: pointer;
|
|
@@ -1083,6 +1112,11 @@ small {
|
|
|
1083
1112
|
transform var(--duration);
|
|
1084
1113
|
}
|
|
1085
1114
|
|
|
1115
|
+
.btn.icon-only {
|
|
1116
|
+
inline-size: var(--btn-min-size);
|
|
1117
|
+
padding-inline: 0;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1086
1120
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
1087
1121
|
.btn {
|
|
1088
1122
|
--btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
|
|
@@ -1107,15 +1141,42 @@ small {
|
|
|
1107
1141
|
--btn-hover-overlay: transparent;
|
|
1108
1142
|
}
|
|
1109
1143
|
|
|
1110
|
-
/*
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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)));
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.btn.ghost {
|
|
1169
|
+
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
|
|
1116
1170
|
}
|
|
1117
1171
|
}
|
|
1118
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);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1119
1180
|
.btn:not(:where(:disabled, [aria-disabled="true"])):active {
|
|
1120
1181
|
transform: translateY(1px);
|
|
1121
1182
|
}
|
|
@@ -1138,23 +1199,21 @@ small {
|
|
|
1138
1199
|
/* .btn.circle needs no local rule: the .circle utility (utilities.css) loads
|
|
1139
1200
|
after components and overrides border-radius at equal specificity. */
|
|
1140
1201
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
text-decoration-thickness: 0.12em;
|
|
1146
|
-
}
|
|
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;
|
|
1147
1206
|
}
|
|
1148
1207
|
|
|
1149
1208
|
.btn:focus-visible {
|
|
1150
|
-
outline:
|
|
1151
|
-
|
|
1152
|
-
box-shadow: 0 0 0 3px var(--btn-focus-ring-color, var(--focus-ring));
|
|
1209
|
+
outline: 2px solid transparent;
|
|
1210
|
+
box-shadow: 0 0 0 var(--focus-ring-width) var(--btn-focus-ring-color, var(--focus-ring));
|
|
1153
1211
|
}
|
|
1154
1212
|
|
|
1155
1213
|
.btn.link:focus-visible {
|
|
1156
|
-
outline
|
|
1214
|
+
outline: var(--focus-outline);
|
|
1157
1215
|
outline-offset: 0.2em;
|
|
1216
|
+
box-shadow: none;
|
|
1158
1217
|
}
|
|
1159
1218
|
|
|
1160
1219
|
.btn:disabled {
|
|
@@ -1178,17 +1237,18 @@ a.btn[aria-disabled="true"] {
|
|
|
1178
1237
|
}
|
|
1179
1238
|
|
|
1180
1239
|
.btn[aria-busy="true"] > .spinner {
|
|
1181
|
-
--spinner-size: 1em;
|
|
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,
|
|
@@ -1232,36 +1290,49 @@ a.btn[aria-disabled="true"] {
|
|
|
1232
1290
|
/*
|
|
1233
1291
|
* Badges for compact labels, counts, and state hints.
|
|
1234
1292
|
*
|
|
1235
|
-
*
|
|
1236
|
-
*
|
|
1293
|
+
* Soft by default. .solid for counters and notification badges. The dot
|
|
1294
|
+
* (.badge:empty) stays solid regardless — a soft dot is pointless.
|
|
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
|
|
1237
1308
|
*/
|
|
1238
1309
|
/* Intent boundary — inherited intents do not tint this component.
|
|
1239
1310
|
Local intent classes still win because :where() has zero specificity. */
|
|
1240
1311
|
:where(.badge) {
|
|
1241
|
-
/* @sync intent-boundary */
|
|
1242
1312
|
--intent: initial;
|
|
1243
1313
|
--intent-fg: initial;
|
|
1244
1314
|
--ui-bg: initial;
|
|
1245
1315
|
--ui-fg: initial;
|
|
1246
1316
|
--ui-border: initial;
|
|
1247
1317
|
--ui-hover-bg: initial;
|
|
1248
|
-
/* @sync intent-boundary */
|
|
1249
1318
|
}
|
|
1250
1319
|
|
|
1251
1320
|
.badge {
|
|
1252
|
-
--badge-size: var(--
|
|
1321
|
+
--badge-size: var(--density-compact-size);
|
|
1253
1322
|
--badge-pad-x: 0.55em;
|
|
1254
|
-
--badge-font-size:
|
|
1323
|
+
--badge-font-size: 0.8125rem;
|
|
1255
1324
|
--badge-dot-size: 0.5rem;
|
|
1256
|
-
--badge-bg: var(--ui-bg, var(--
|
|
1257
|
-
--badge-fg: var(--ui-fg, var(--intent
|
|
1258
|
-
--badge-border: var(--ui-border,
|
|
1259
|
-
--badge-radius:
|
|
1325
|
+
--badge-bg: var(--ui-bg, var(--surface-subtle));
|
|
1326
|
+
--badge-fg: var(--ui-fg, var(--intent, var(--text)));
|
|
1327
|
+
--badge-border: var(--ui-border, var(--border));
|
|
1328
|
+
--badge-radius: var(--radius-full);
|
|
1260
1329
|
|
|
1261
1330
|
display: inline-flex;
|
|
1262
1331
|
align-items: center;
|
|
1263
1332
|
justify-content: center;
|
|
1264
1333
|
gap: 0.35em;
|
|
1334
|
+
inline-size: fit-content;
|
|
1335
|
+
max-inline-size: 100%;
|
|
1265
1336
|
min-block-size: var(--badge-size);
|
|
1266
1337
|
padding-inline: var(--badge-pad-x);
|
|
1267
1338
|
border: var(--border-width) solid var(--badge-border);
|
|
@@ -1280,8 +1351,31 @@ a.btn[aria-disabled="true"] {
|
|
|
1280
1351
|
box-shadow var(--duration);
|
|
1281
1352
|
}
|
|
1282
1353
|
|
|
1354
|
+
/* Soft by default — duplicated here with @sync markers for check-sync.js
|
|
1355
|
+
to guard, the same way alert.css guards its own soft recipe. */
|
|
1356
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
1357
|
+
.badge:where(:not(.solid, .outline)) {
|
|
1358
|
+
/* @sync soft-recipe */
|
|
1359
|
+
--badge-bg: color-mix(
|
|
1360
|
+
in oklch,
|
|
1361
|
+
var(--surface) var(--soft-bg-mix),
|
|
1362
|
+
var(--intent, var(--neutral))
|
|
1363
|
+
);
|
|
1364
|
+
--badge-fg: var(--intent, var(--text));
|
|
1365
|
+
--badge-border: color-mix(
|
|
1366
|
+
in oklch,
|
|
1367
|
+
var(--surface) var(--soft-border-mix),
|
|
1368
|
+
var(--intent, var(--neutral))
|
|
1369
|
+
);
|
|
1370
|
+
/* @sync soft-recipe */
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1283
1374
|
.badge:empty {
|
|
1284
1375
|
--badge-size: var(--badge-dot-size);
|
|
1376
|
+
--badge-bg: var(--intent, var(--neutral));
|
|
1377
|
+
--badge-fg: var(--intent-fg, var(--neutral-fg));
|
|
1378
|
+
--badge-border: transparent;
|
|
1285
1379
|
padding: 0;
|
|
1286
1380
|
border: 0;
|
|
1287
1381
|
inline-size: var(--badge-size);
|
|
@@ -1307,28 +1401,44 @@ a.btn[aria-disabled="true"] {
|
|
|
1307
1401
|
cursor: pointer;
|
|
1308
1402
|
}
|
|
1309
1403
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
background: var(--hover-overlay);
|
|
1313
|
-
}
|
|
1404
|
+
.badge > button:not(:disabled):hover {
|
|
1405
|
+
background: var(--hover-overlay);
|
|
1314
1406
|
}
|
|
1315
1407
|
|
|
1316
|
-
/*
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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);
|
|
1322
1417
|
}
|
|
1323
1418
|
|
|
1324
|
-
:
|
|
1325
|
-
|
|
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
|
+
);
|
|
1326
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)));
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
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);
|
|
1327
1438
|
}
|
|
1328
1439
|
|
|
1329
1440
|
button.badge {
|
|
1330
1441
|
cursor: pointer;
|
|
1331
|
-
font: inherit;
|
|
1332
1442
|
}
|
|
1333
1443
|
|
|
1334
1444
|
button.badge:disabled,
|
|
@@ -1342,27 +1452,18 @@ a.badge[aria-disabled="true"] {
|
|
|
1342
1452
|
}
|
|
1343
1453
|
|
|
1344
1454
|
@media (forced-colors: active) {
|
|
1345
|
-
.badge {
|
|
1346
|
-
border-color: ButtonText;
|
|
1347
|
-
background: Canvas;
|
|
1348
|
-
color: CanvasText;
|
|
1349
|
-
box-shadow: none;
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
1455
|
.badge:empty {
|
|
1353
1456
|
background: ButtonText;
|
|
1354
1457
|
}
|
|
1355
1458
|
|
|
1356
|
-
|
|
1357
|
-
:
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
}
|
|
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
|
+
}
|
|
1361
1463
|
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1464
|
+
.badge > button:not(:disabled):hover {
|
|
1465
|
+
outline: var(--border-width) solid currentColor;
|
|
1466
|
+
outline-offset: 1px;
|
|
1366
1467
|
}
|
|
1367
1468
|
|
|
1368
1469
|
button.badge:disabled,
|
|
@@ -1382,42 +1483,48 @@ a.badge[aria-disabled="true"] {
|
|
|
1382
1483
|
* --alert-border-inline-start-color/-width are unset extension points, mirroring
|
|
1383
1484
|
* --btn-border-block-end-* (button.css), for themes that give alerts a
|
|
1384
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
|
|
1385
1500
|
*/
|
|
1386
1501
|
/* Intent boundary — inherited intents do not tint this component.
|
|
1387
1502
|
Local intent classes still win because :where() has zero specificity. */
|
|
1388
1503
|
:where(.alert) {
|
|
1389
|
-
/* @sync intent-boundary */
|
|
1390
1504
|
--intent: initial;
|
|
1391
1505
|
--intent-fg: initial;
|
|
1392
1506
|
--ui-bg: initial;
|
|
1393
1507
|
--ui-fg: initial;
|
|
1394
1508
|
--ui-border: initial;
|
|
1395
|
-
--ui-hover-bg: initial;
|
|
1396
|
-
/* @sync intent-boundary */
|
|
1397
1509
|
}
|
|
1398
1510
|
|
|
1399
1511
|
.alert {
|
|
1400
|
-
--alert-pad-block: var(--
|
|
1401
|
-
--alert-pad-inline: var(--space-
|
|
1402
|
-
--alert-font-size: var(--
|
|
1403
|
-
--alert-icon-size:
|
|
1404
|
-
--alert-bg: var(--surface-subtle);
|
|
1405
|
-
--alert-
|
|
1406
|
-
--
|
|
1407
|
-
--ui-border: var(--alert-border);
|
|
1408
|
-
--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));
|
|
1409
1519
|
|
|
1410
1520
|
padding-block: var(--alert-pad-block);
|
|
1411
1521
|
padding-inline: var(--alert-pad-inline);
|
|
1412
|
-
background: var(--
|
|
1413
|
-
color: var(--
|
|
1414
|
-
border: var(--border-width) solid var(--
|
|
1415
|
-
border-inline-start-color: var(
|
|
1416
|
-
--alert-border-inline-start-color,
|
|
1417
|
-
var(--ui-border, var(--border))
|
|
1418
|
-
);
|
|
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));
|
|
1419
1526
|
border-inline-start-width: var(--alert-border-inline-start-width, var(--border-width));
|
|
1420
|
-
border-radius: var(--radius-lg);
|
|
1527
|
+
border-radius: var(--alert-radius, var(--radius-lg));
|
|
1421
1528
|
font-size: var(--alert-font-size);
|
|
1422
1529
|
}
|
|
1423
1530
|
|
|
@@ -1433,21 +1540,10 @@ a.badge[aria-disabled="true"] {
|
|
|
1433
1540
|
margin-block-end: 0;
|
|
1434
1541
|
}
|
|
1435
1542
|
|
|
1436
|
-
/* Alerts are soft by default.
|
|
1437
|
-
.alert.
|
|
1438
|
-
--ui-bg: var(--intent, var(--neutral));
|
|
1439
|
-
--ui-fg: var(--intent-fg, var(--neutral-fg));
|
|
1440
|
-
--ui-border: transparent;
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
.alert.outline {
|
|
1444
|
-
--ui-bg: transparent;
|
|
1445
|
-
--ui-fg: var(--intent, var(--text));
|
|
1446
|
-
--ui-border: var(--intent, var(--border));
|
|
1447
|
-
}
|
|
1448
|
-
|
|
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. */
|
|
1449
1545
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
1450
|
-
.alert:where(:not(.solid, .outline)) {
|
|
1546
|
+
.alert:where(:not(.solid, .outline, .callout)) {
|
|
1451
1547
|
/* @sync soft-recipe */
|
|
1452
1548
|
--ui-bg: color-mix(in oklch, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral)));
|
|
1453
1549
|
--ui-border: color-mix(
|
|
@@ -1459,8 +1555,8 @@ a.badge[aria-disabled="true"] {
|
|
|
1459
1555
|
}
|
|
1460
1556
|
}
|
|
1461
1557
|
|
|
1462
|
-
/* Decorative leading icon */
|
|
1463
|
-
.alert >
|
|
1558
|
+
/* Decorative leading icon — use .alert-icon on the icon element. */
|
|
1559
|
+
.alert > .alert-icon {
|
|
1464
1560
|
display: inline-block;
|
|
1465
1561
|
flex: none;
|
|
1466
1562
|
inline-size: var(--alert-icon-size);
|
|
@@ -1470,33 +1566,84 @@ a.badge[aria-disabled="true"] {
|
|
|
1470
1566
|
vertical-align: -0.125em;
|
|
1471
1567
|
}
|
|
1472
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. */
|
|
1473
1612
|
@supports selector(.alert:has(> *)) {
|
|
1474
|
-
.alert:has(>
|
|
1613
|
+
.alert:has(> .alert-icon, > .alert-dismiss) {
|
|
1475
1614
|
display: grid;
|
|
1476
|
-
grid-template-columns:
|
|
1615
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1477
1616
|
align-items: var(--items-align, start);
|
|
1478
|
-
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);
|
|
1479
1622
|
}
|
|
1480
1623
|
|
|
1481
|
-
.alert:has(>
|
|
1624
|
+
.alert:has(> .alert-icon) > .alert-icon {
|
|
1482
1625
|
grid-column: 1;
|
|
1483
1626
|
grid-row: 1 / -1;
|
|
1484
1627
|
display: inline-grid;
|
|
1485
1628
|
place-items: center;
|
|
1486
|
-
inline-size: var(--alert-icon-size);
|
|
1487
|
-
block-size: var(--alert-icon-size);
|
|
1488
1629
|
margin-block-start: 0.125em;
|
|
1489
|
-
font-size: var(--alert-icon-size);
|
|
1490
|
-
line-height: 1;
|
|
1491
1630
|
}
|
|
1492
1631
|
|
|
1493
|
-
.alert:has(>
|
|
1494
|
-
|
|
1495
|
-
block-size: var(--alert-icon-size);
|
|
1632
|
+
.alert:has(> .alert-icon) > :not(.alert-icon, .alert-dismiss) {
|
|
1633
|
+
grid-column: 2;
|
|
1496
1634
|
}
|
|
1497
1635
|
|
|
1498
|
-
.alert:has(>
|
|
1499
|
-
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;
|
|
1500
1647
|
}
|
|
1501
1648
|
}
|
|
1502
1649
|
|
|
@@ -1511,14 +1658,84 @@ a.badge[aria-disabled="true"] {
|
|
|
1511
1658
|
Canvas backplate (unreadable) — so .solid intentionally falls back to the
|
|
1512
1659
|
default look. */
|
|
1513
1660
|
|
|
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. */
|
|
1663
|
+
.alert.callout {
|
|
1664
|
+
--alert-bg: var(--surface-subtle);
|
|
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)));
|
|
1669
|
+
}
|
|
1670
|
+
|
|
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. */
|
|
1674
|
+
.alert.admonition {
|
|
1675
|
+
padding: 0;
|
|
1676
|
+
display: block;
|
|
1677
|
+
overflow: clip;
|
|
1678
|
+
border-color: var(--intent, var(--border));
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.alert.admonition > .alert-title {
|
|
1682
|
+
display: flex;
|
|
1683
|
+
align-items: center;
|
|
1684
|
+
gap: var(--space-20);
|
|
1685
|
+
padding-block: var(--alert-pad-block);
|
|
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
|
+
}
|
|
1690
|
+
|
|
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 {
|
|
1698
|
+
background: color-mix(
|
|
1699
|
+
in oklch,
|
|
1700
|
+
var(--surface) var(--soft-bg-mix),
|
|
1701
|
+
var(--intent, var(--neutral))
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
@supports not (color: color-mix(in oklch, red, white)) {
|
|
1707
|
+
.alert.admonition > .alert-title {
|
|
1708
|
+
background: var(--intent, var(--neutral));
|
|
1709
|
+
color: var(--intent-fg, var(--neutral-fg));
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
.alert.admonition > .alert-body {
|
|
1714
|
+
padding-block: var(--alert-pad-block);
|
|
1715
|
+
padding-inline: var(--alert-pad-inline);
|
|
1716
|
+
background: var(--surface);
|
|
1717
|
+
color: var(--text);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
.alert.admonition > .alert-body > :first-child {
|
|
1721
|
+
margin-block-start: 0;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
.alert.admonition > .alert-body > :last-child {
|
|
1725
|
+
margin-block-end: 0;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1514
1728
|
/*
|
|
1515
1729
|
* Actions — a horizontal action-button cluster.
|
|
1516
1730
|
*
|
|
1517
1731
|
* Composes with .btn (or any inline element) and feeds layout via the shared
|
|
1518
1732
|
* --gap token.
|
|
1733
|
+
*
|
|
1734
|
+
* Internal:
|
|
1735
|
+
* --gap (local value for the shared gap token)
|
|
1519
1736
|
*/
|
|
1520
1737
|
.actions {
|
|
1521
|
-
--gap: var(--space-
|
|
1738
|
+
--gap: var(--space-20);
|
|
1522
1739
|
|
|
1523
1740
|
display: flex;
|
|
1524
1741
|
flex-wrap: wrap;
|
|
@@ -1541,28 +1758,36 @@ a.badge[aria-disabled="true"] {
|
|
|
1541
1758
|
* Surface, border, radius, padding, density, and elevation stay local here.
|
|
1542
1759
|
* Direct header/footer children are structural slots. Page layout remains the
|
|
1543
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
|
|
1544
1774
|
*/
|
|
1545
1775
|
/* Intent boundary — inherited intents do not tint this component.
|
|
1546
1776
|
Local intent classes still win because :where() has zero specificity. */
|
|
1547
1777
|
:where(.card) {
|
|
1548
|
-
/* @sync intent-boundary */
|
|
1549
|
-
--intent: initial;
|
|
1550
|
-
--intent-fg: initial;
|
|
1551
1778
|
--ui-bg: initial;
|
|
1552
1779
|
--ui-fg: initial;
|
|
1553
1780
|
--ui-border: initial;
|
|
1554
|
-
--ui-hover-bg: initial;
|
|
1555
|
-
/* @sync intent-boundary */
|
|
1556
1781
|
}
|
|
1557
1782
|
|
|
1558
1783
|
.card {
|
|
1559
|
-
--card-pad: var(--space-
|
|
1784
|
+
--card-pad: var(--space-40);
|
|
1560
1785
|
--card-radius: var(--radius-lg);
|
|
1561
1786
|
--card-max-inline-size: none;
|
|
1562
1787
|
|
|
1563
1788
|
max-inline-size: var(--card-max-inline-size);
|
|
1564
1789
|
padding: var(--card-pad);
|
|
1565
|
-
background: var(--ui-bg, var(--surface));
|
|
1790
|
+
background: var(--ui-bg, var(--surface-raised));
|
|
1566
1791
|
color: var(--ui-fg, var(--text));
|
|
1567
1792
|
border: var(--border-width) solid var(--ui-border, var(--border));
|
|
1568
1793
|
border-radius: var(--card-radius);
|
|
@@ -1577,7 +1802,7 @@ a.badge[aria-disabled="true"] {
|
|
|
1577
1802
|
flex-wrap: wrap;
|
|
1578
1803
|
align-items: var(--items-align, center);
|
|
1579
1804
|
justify-content: space-between;
|
|
1580
|
-
gap: var(--space-
|
|
1805
|
+
gap: var(--space-30);
|
|
1581
1806
|
min-inline-size: 0;
|
|
1582
1807
|
}
|
|
1583
1808
|
|
|
@@ -1604,16 +1829,8 @@ a.badge[aria-disabled="true"] {
|
|
|
1604
1829
|
.card.subtle {
|
|
1605
1830
|
background: var(--surface-subtle);
|
|
1606
1831
|
}
|
|
1607
|
-
.card.inverted {
|
|
1608
|
-
background: var(--surface-solid);
|
|
1609
|
-
color: var(--surface);
|
|
1610
|
-
border-color: transparent;
|
|
1611
|
-
--heading: var(--surface);
|
|
1612
|
-
--busy-overlay-bg: var(--surface-solid);
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
1832
|
.card.compact {
|
|
1616
|
-
--card-pad: var(--space-
|
|
1833
|
+
--card-pad: var(--space-20);
|
|
1617
1834
|
}
|
|
1618
1835
|
|
|
1619
1836
|
/* Full-bleed children */
|
|
@@ -1656,26 +1873,6 @@ a.badge[aria-disabled="true"] {
|
|
|
1656
1873
|
padding: var(--card-pad);
|
|
1657
1874
|
}
|
|
1658
1875
|
|
|
1659
|
-
@media (forced-colors: active) {
|
|
1660
|
-
.card {
|
|
1661
|
-
border-color: ButtonText;
|
|
1662
|
-
background: Canvas;
|
|
1663
|
-
color: CanvasText;
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
.card.raised {
|
|
1667
|
-
box-shadow: none;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
.card.inverted {
|
|
1671
|
-
--heading: CanvasText;
|
|
1672
|
-
|
|
1673
|
-
border-color: ButtonText;
|
|
1674
|
-
background: Canvas;
|
|
1675
|
-
color: CanvasText;
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
1876
|
/*
|
|
1680
1877
|
* Surface — shared modal scrim and backdrop for flyout and drawer.
|
|
1681
1878
|
*
|
|
@@ -1697,7 +1894,7 @@ a.badge[aria-disabled="true"] {
|
|
|
1697
1894
|
z-index: calc(var(--z-menu) - 1);
|
|
1698
1895
|
background: var(--backdrop-fill);
|
|
1699
1896
|
opacity: 1;
|
|
1700
|
-
transition: opacity var(--duration) var(--ease
|
|
1897
|
+
transition: opacity var(--duration) var(--ease);
|
|
1701
1898
|
}
|
|
1702
1899
|
|
|
1703
1900
|
.surface-backdrop[hidden] {
|
|
@@ -1714,28 +1911,40 @@ a.badge[aria-disabled="true"] {
|
|
|
1714
1911
|
@supports (transition-behavior: allow-discrete) {
|
|
1715
1912
|
.surface-backdrop {
|
|
1716
1913
|
transition:
|
|
1717
|
-
opacity var(--duration) var(--ease
|
|
1914
|
+
opacity var(--duration) var(--ease),
|
|
1718
1915
|
display var(--duration) allow-discrete;
|
|
1719
1916
|
}
|
|
1720
1917
|
}
|
|
1721
1918
|
|
|
1722
|
-
@media (forced-colors: active) {
|
|
1723
|
-
.surface-backdrop {
|
|
1724
|
-
background: Canvas;
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
1919
|
/*
|
|
1729
1920
|
* Modal shell styling for native dialog elements.
|
|
1730
1921
|
*
|
|
1731
1922
|
* Spacing, radius, transitions, and focus treatment come from shared tokens.
|
|
1732
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
|
|
1733
1934
|
*/
|
|
1734
1935
|
dialog.modal {
|
|
1735
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));
|
|
1941
|
+
|
|
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;
|
|
1736
1945
|
|
|
1737
|
-
max-inline-size: min(100% -
|
|
1738
|
-
max-block-size: calc(var(--viewport-block) -
|
|
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));
|
|
1739
1948
|
border: var(--border-width) solid var(--border);
|
|
1740
1949
|
border-radius: var(--radius-lg);
|
|
1741
1950
|
background: var(--surface-raised);
|
|
@@ -1759,11 +1968,18 @@ dialog.modal {
|
|
|
1759
1968
|
dialog.modal > form,
|
|
1760
1969
|
dialog.modal > .stack {
|
|
1761
1970
|
display: grid;
|
|
1762
|
-
gap: var(--space-
|
|
1763
|
-
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));
|
|
1764
1973
|
overflow: auto;
|
|
1765
1974
|
overscroll-behavior: contain;
|
|
1766
|
-
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
|
+
}
|
|
1767
1983
|
}
|
|
1768
1984
|
|
|
1769
1985
|
dialog.modal.scrollable > form,
|
|
@@ -1779,21 +1995,87 @@ dialog.modal.scrollable > .stack > :not(header, footer) {
|
|
|
1779
1995
|
overscroll-behavior: contain;
|
|
1780
1996
|
}
|
|
1781
1997
|
|
|
1782
|
-
dialog.modal header > :first-child,
|
|
1783
|
-
dialog.modal footer > :first-child {
|
|
1784
|
-
margin-block-start: 0;
|
|
1998
|
+
dialog.modal header > :first-child,
|
|
1999
|
+
dialog.modal footer > :first-child {
|
|
2000
|
+
margin-block-start: 0;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
dialog.modal header > :last-child,
|
|
2004
|
+
dialog.modal footer > :last-child {
|
|
2005
|
+
margin-block-end: 0;
|
|
2006
|
+
}
|
|
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
|
+
|
|
2015
|
+
dialog.modal footer {
|
|
2016
|
+
display: flex;
|
|
2017
|
+
flex-wrap: wrap;
|
|
2018
|
+
justify-content: flex-end;
|
|
2019
|
+
gap: var(--gap);
|
|
2020
|
+
}
|
|
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)));
|
|
1785
2063
|
}
|
|
1786
2064
|
|
|
1787
|
-
dialog
|
|
1788
|
-
|
|
1789
|
-
|
|
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;
|
|
1790
2072
|
}
|
|
1791
2073
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
+
}
|
|
1797
2079
|
}
|
|
1798
2080
|
|
|
1799
2081
|
dialog.modal[open] {
|
|
@@ -1851,7 +2133,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
1851
2133
|
|
|
1852
2134
|
@media (prefers-reduced-motion: no-preference) {
|
|
1853
2135
|
dialog.modal.is-static {
|
|
1854
|
-
animation: actual-
|
|
2136
|
+
animation: actual-dialog-static var(--duration-slow) var(--ease);
|
|
1855
2137
|
}
|
|
1856
2138
|
|
|
1857
2139
|
::view-transition-group(actual-dialog) {
|
|
@@ -1860,7 +2142,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
1860
2142
|
}
|
|
1861
2143
|
}
|
|
1862
2144
|
|
|
1863
|
-
@keyframes actual-
|
|
2145
|
+
@keyframes actual-dialog-static {
|
|
1864
2146
|
0%,
|
|
1865
2147
|
100% {
|
|
1866
2148
|
transform: none;
|
|
@@ -1886,6 +2168,70 @@ html.has-modal-open.had-scrollbar {
|
|
|
1886
2168
|
dialog.modal::backdrop {
|
|
1887
2169
|
background: Canvas;
|
|
1888
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);
|
|
1889
2235
|
}
|
|
1890
2236
|
|
|
1891
2237
|
/*
|
|
@@ -1896,16 +2242,30 @@ html.has-modal-open.had-scrollbar {
|
|
|
1896
2242
|
* discrete display/overlay transitions. The drawer is a layout shell only —
|
|
1897
2243
|
* navigation content inside uses .nav-link and .accordion, which carry their
|
|
1898
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)
|
|
1899
2252
|
*/
|
|
1900
2253
|
dialog.drawer {
|
|
1901
2254
|
--drawer-size: 20rem;
|
|
1902
|
-
--drawer-pad: var(--space-
|
|
2255
|
+
--drawer-pad: var(--space-40);
|
|
1903
2256
|
--drawer-closed-translate: -100%;
|
|
1904
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
|
+
|
|
1905
2265
|
inline-size: min(calc(var(--viewport-inline) * 0.9), var(--drawer-size));
|
|
1906
|
-
block-size:
|
|
2266
|
+
block-size: 100%;
|
|
1907
2267
|
max-inline-size: none;
|
|
1908
|
-
max-block-size:
|
|
2268
|
+
max-block-size: 100%;
|
|
1909
2269
|
margin: 0;
|
|
1910
2270
|
padding: var(--drawer-pad);
|
|
1911
2271
|
border: 0;
|
|
@@ -1924,7 +2284,7 @@ dialog.drawer {
|
|
|
1924
2284
|
dialog.drawer[open] {
|
|
1925
2285
|
display: grid;
|
|
1926
2286
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
1927
|
-
gap: var(--space-
|
|
2287
|
+
gap: var(--space-40);
|
|
1928
2288
|
opacity: 1;
|
|
1929
2289
|
transform: translateX(0);
|
|
1930
2290
|
}
|
|
@@ -1995,20 +2355,23 @@ dialog.drawer nav {
|
|
|
1995
2355
|
}
|
|
1996
2356
|
|
|
1997
2357
|
@media (forced-colors: active) {
|
|
1998
|
-
dialog.drawer {
|
|
1999
|
-
border-inline-end-color: ButtonText;
|
|
2358
|
+
dialog.drawer::backdrop {
|
|
2000
2359
|
background: Canvas;
|
|
2001
|
-
color: CanvasText;
|
|
2002
|
-
box-shadow: none;
|
|
2003
2360
|
}
|
|
2361
|
+
}
|
|
2004
2362
|
|
|
2005
|
-
|
|
2006
|
-
|
|
2363
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
2364
|
+
dialog.drawer.is-static {
|
|
2365
|
+
animation: actual-dialog-static var(--duration-slow) var(--ease);
|
|
2007
2366
|
}
|
|
2367
|
+
}
|
|
2008
2368
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
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));
|
|
2012
2375
|
}
|
|
2013
2376
|
|
|
2014
2377
|
|
|
@@ -2024,7 +2387,16 @@ dialog.drawer nav {
|
|
|
2024
2387
|
* .is-sheet marks "auto" resolved to bottom-sheet presentation on coarse
|
|
2025
2388
|
* pointers. Authors opt into mobile behavior with
|
|
2026
2389
|
* [data-flyout-mobile="sheet|auto|none|anchored"].
|
|
2027
|
-
* --
|
|
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)
|
|
2028
2400
|
*/
|
|
2029
2401
|
|
|
2030
2402
|
.flyout-trigger {
|
|
@@ -2047,28 +2419,32 @@ dialog.drawer nav {
|
|
|
2047
2419
|
}
|
|
2048
2420
|
|
|
2049
2421
|
.flyout {
|
|
2422
|
+
--menu-item-size: var(--control-size);
|
|
2050
2423
|
z-index: var(--z-menu);
|
|
2051
2424
|
/* No-JS fallback: below the trigger inside the relative .flyout-trigger.
|
|
2052
2425
|
surface.js switches an open flyout to position: fixed with measured
|
|
2053
2426
|
coordinates; this only covers a flyout revealed without the runtime. */
|
|
2054
2427
|
position: absolute;
|
|
2055
|
-
inset-block-start: calc(100% + var(--space-
|
|
2428
|
+
inset-block-start: calc(100% + var(--space-10));
|
|
2056
2429
|
inset-inline-start: 0;
|
|
2057
2430
|
display: grid;
|
|
2058
|
-
gap: var(--space-
|
|
2431
|
+
gap: var(--space-10);
|
|
2059
2432
|
inline-size: var(--flyout-inline-size, max-content);
|
|
2060
2433
|
min-inline-size: min(
|
|
2061
|
-
var(--
|
|
2062
|
-
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)))
|
|
2063
2436
|
);
|
|
2064
2437
|
max-inline-size: min(
|
|
2065
2438
|
var(--flyout-max-inline-size, 20rem),
|
|
2066
|
-
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)))
|
|
2067
2444
|
);
|
|
2068
|
-
max-block-size: min(24rem, var(--available-height, calc(var(--viewport-block) - var(--space-6))));
|
|
2069
2445
|
overflow: auto;
|
|
2070
2446
|
margin: 0;
|
|
2071
|
-
padding: var(--space-
|
|
2447
|
+
padding: var(--space-10);
|
|
2072
2448
|
border: var(--border-width) solid var(--border);
|
|
2073
2449
|
border-radius: var(--radius);
|
|
2074
2450
|
background: var(--surface-raised);
|
|
@@ -2077,9 +2453,9 @@ dialog.drawer nav {
|
|
|
2077
2453
|
opacity: 1;
|
|
2078
2454
|
scale: 1;
|
|
2079
2455
|
transition:
|
|
2080
|
-
opacity var(--duration-fast
|
|
2081
|
-
scale var(--duration-fast
|
|
2082
|
-
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);
|
|
2083
2459
|
}
|
|
2084
2460
|
|
|
2085
2461
|
.flyout.is-open:not(.is-sheet) {
|
|
@@ -2096,6 +2472,7 @@ dialog.drawer nav {
|
|
|
2096
2472
|
/* Bottom-sheet presentation. surface.js toggles .is-sheet when the flyout is
|
|
2097
2473
|
opened on coarse pointer / narrow viewports. */
|
|
2098
2474
|
.flyout.is-sheet {
|
|
2475
|
+
--menu-item-size: var(--control-size-lg);
|
|
2099
2476
|
position: fixed;
|
|
2100
2477
|
inset-inline: 0;
|
|
2101
2478
|
inset-block-start: auto;
|
|
@@ -2106,8 +2483,8 @@ dialog.drawer nav {
|
|
|
2106
2483
|
max-block-size: min(calc(var(--viewport-block) * 0.75), 32rem);
|
|
2107
2484
|
overflow: auto;
|
|
2108
2485
|
grid-template-columns: 1fr;
|
|
2109
|
-
padding: max(var(--space-
|
|
2110
|
-
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));
|
|
2111
2488
|
border-end-start-radius: 0;
|
|
2112
2489
|
border-end-end-radius: 0;
|
|
2113
2490
|
}
|
|
@@ -2120,23 +2497,23 @@ dialog.drawer nav {
|
|
|
2120
2497
|
|
|
2121
2498
|
.flyout.is-sheet[hidden] {
|
|
2122
2499
|
scale: 1;
|
|
2123
|
-
translate: 0 var(--space-
|
|
2500
|
+
translate: 0 var(--space-30);
|
|
2124
2501
|
}
|
|
2125
2502
|
|
|
2126
2503
|
@starting-style {
|
|
2127
2504
|
.flyout.is-sheet.is-open {
|
|
2128
2505
|
scale: 1;
|
|
2129
|
-
translate: 0 var(--space-
|
|
2506
|
+
translate: 0 var(--space-30);
|
|
2130
2507
|
}
|
|
2131
2508
|
}
|
|
2132
2509
|
|
|
2133
2510
|
@supports (transition-behavior: allow-discrete) {
|
|
2134
2511
|
.flyout {
|
|
2135
2512
|
transition:
|
|
2136
|
-
opacity var(--duration-fast
|
|
2137
|
-
scale var(--duration-fast
|
|
2138
|
-
translate var(--duration-fast
|
|
2139
|
-
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;
|
|
2140
2517
|
}
|
|
2141
2518
|
}
|
|
2142
2519
|
|
|
@@ -2146,7 +2523,7 @@ dialog.drawer nav {
|
|
|
2146
2523
|
|
|
2147
2524
|
.flyout > :where(section, footer) {
|
|
2148
2525
|
min-inline-size: 0;
|
|
2149
|
-
padding: var(--space-
|
|
2526
|
+
padding: var(--space-20);
|
|
2150
2527
|
}
|
|
2151
2528
|
|
|
2152
2529
|
.flyout > section > :where(h1, h2, h3, h4, h5, h6) {
|
|
@@ -2158,8 +2535,8 @@ dialog.drawer nav {
|
|
|
2158
2535
|
|
|
2159
2536
|
.flyout > section > ul {
|
|
2160
2537
|
display: grid;
|
|
2161
|
-
gap: var(--space-
|
|
2162
|
-
margin: var(--space-
|
|
2538
|
+
gap: var(--space-10);
|
|
2539
|
+
margin: var(--space-10) 0 0;
|
|
2163
2540
|
padding: 0;
|
|
2164
2541
|
list-style: none;
|
|
2165
2542
|
}
|
|
@@ -2168,92 +2545,133 @@ dialog.drawer nav {
|
|
|
2168
2545
|
margin: 0;
|
|
2169
2546
|
}
|
|
2170
2547
|
|
|
2171
|
-
/*
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
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
|
+
|
|
2177
2577
|
display: flex;
|
|
2178
2578
|
align-items: center;
|
|
2179
|
-
|
|
2180
|
-
gap: var(--space-2);
|
|
2579
|
+
gap: var(--space-20);
|
|
2181
2580
|
inline-size: 100%;
|
|
2182
|
-
min-block-size: var(--control-size);
|
|
2183
|
-
padding-inline: var(--space-
|
|
2581
|
+
min-block-size: var(--menu-item-size, var(--control-size));
|
|
2582
|
+
padding-inline: var(--space-20);
|
|
2184
2583
|
border: 0;
|
|
2185
2584
|
border-radius: var(--radius-sm);
|
|
2186
|
-
background:
|
|
2585
|
+
background: transparent;
|
|
2187
2586
|
color: var(--text);
|
|
2188
2587
|
font: inherit;
|
|
2189
|
-
font-weight: var(--font-weight);
|
|
2190
|
-
text-decoration: none;
|
|
2191
2588
|
text-align: start;
|
|
2589
|
+
text-decoration: none;
|
|
2192
2590
|
white-space: nowrap;
|
|
2193
2591
|
cursor: pointer;
|
|
2194
2592
|
}
|
|
2195
2593
|
|
|
2196
|
-
|
|
2197
|
-
|
|
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;
|
|
2606
|
+
}
|
|
2607
|
+
|
|
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: "";
|
|
2198
2627
|
}
|
|
2199
2628
|
|
|
2200
|
-
|
|
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 {
|
|
2201
2646
|
background: var(--surface-subtle);
|
|
2202
|
-
outline: var(--focus-outline);
|
|
2203
|
-
outline-offset: var(--focus-outline-offset);
|
|
2204
|
-
box-shadow: var(--focus-ring-shadow);
|
|
2205
2647
|
}
|
|
2206
2648
|
|
|
2207
|
-
:
|
|
2208
|
-
|
|
2209
|
-
cursor: not-allowed;
|
|
2210
|
-
opacity: var(--disabled-opacity);
|
|
2649
|
+
.menu-item:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
2650
|
+
background: var(--surface-subtle);
|
|
2211
2651
|
}
|
|
2212
2652
|
|
|
2213
|
-
:is(
|
|
2214
|
-
|
|
2653
|
+
.menu-item:is(:disabled, [aria-disabled="true"]) {
|
|
2654
|
+
cursor: not-allowed;
|
|
2655
|
+
opacity: var(--disabled-opacity);
|
|
2215
2656
|
}
|
|
2216
2657
|
|
|
2217
|
-
|
|
2218
|
-
:
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
background: var(--surface-subtle);
|
|
2223
|
-
outline: none;
|
|
2224
|
-
}
|
|
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);
|
|
2225
2663
|
}
|
|
2226
2664
|
|
|
2227
|
-
.
|
|
2228
|
-
.flyout > li > hr {
|
|
2229
|
-
inline-size: 100%;
|
|
2665
|
+
.menu-separator {
|
|
2230
2666
|
margin: 0;
|
|
2231
|
-
margin-block: var(--space-1);
|
|
2232
2667
|
border: 0;
|
|
2233
2668
|
border-block-start: var(--border-width) solid var(--border);
|
|
2234
2669
|
}
|
|
2235
2670
|
|
|
2236
2671
|
@media (forced-colors: active) {
|
|
2237
|
-
.
|
|
2238
|
-
border-
|
|
2239
|
-
|
|
2240
|
-
color: CanvasText;
|
|
2241
|
-
box-shadow: none;
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
:is(.flyout, .flyout :where(li)) > :where(button, a, [role="menuitem"]):focus-visible {
|
|
2245
|
-
background: Canvas;
|
|
2246
|
-
color: CanvasText;
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
@media (hover: hover) {
|
|
2250
|
-
:is(.flyout, .flyout :where(li))
|
|
2251
|
-
> :where(button, a, [role="menuitem"]):hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
2252
|
-
outline: var(--border-width) solid ButtonText;
|
|
2253
|
-
outline-offset: -1px;
|
|
2254
|
-
background: Canvas;
|
|
2255
|
-
color: CanvasText;
|
|
2256
|
-
}
|
|
2672
|
+
.menu-item:focus-visible {
|
|
2673
|
+
outline: var(--border-width) solid ButtonText;
|
|
2674
|
+
outline-offset: -1px;
|
|
2257
2675
|
}
|
|
2258
2676
|
}
|
|
2259
2677
|
|
|
@@ -2263,6 +2681,15 @@ dialog.drawer nav {
|
|
|
2263
2681
|
* Position, placement, and arrow position are managed by JavaScript
|
|
2264
2682
|
* via position: fixed, left, top, data-placement, and --arrow-x.
|
|
2265
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)
|
|
2266
2693
|
*/
|
|
2267
2694
|
.tooltip {
|
|
2268
2695
|
--tooltip-bg: var(--surface-solid);
|
|
@@ -2284,8 +2711,8 @@ dialog.drawer nav {
|
|
|
2284
2711
|
opacity: 1;
|
|
2285
2712
|
scale: 1;
|
|
2286
2713
|
transition:
|
|
2287
|
-
opacity var(--duration-fast
|
|
2288
|
-
scale var(--duration-fast
|
|
2714
|
+
opacity var(--duration-fast) var(--ease),
|
|
2715
|
+
scale var(--duration-fast) var(--ease);
|
|
2289
2716
|
}
|
|
2290
2717
|
|
|
2291
2718
|
.tooltip[hidden] {
|
|
@@ -2304,9 +2731,9 @@ dialog.drawer nav {
|
|
|
2304
2731
|
@supports (transition-behavior: allow-discrete) {
|
|
2305
2732
|
.tooltip {
|
|
2306
2733
|
transition:
|
|
2307
|
-
opacity var(--duration-fast
|
|
2308
|
-
scale var(--duration-fast
|
|
2309
|
-
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;
|
|
2310
2737
|
}
|
|
2311
2738
|
}
|
|
2312
2739
|
|
|
@@ -2356,8 +2783,6 @@ dialog.drawer nav {
|
|
|
2356
2783
|
.tooltip {
|
|
2357
2784
|
--tooltip-bg: Canvas;
|
|
2358
2785
|
--tooltip-fg: CanvasText;
|
|
2359
|
-
|
|
2360
|
-
border: var(--border-width) solid ButtonText;
|
|
2361
2786
|
}
|
|
2362
2787
|
|
|
2363
2788
|
.tooltip::before {
|
|
@@ -2373,9 +2798,14 @@ dialog.drawer nav {
|
|
|
2373
2798
|
* Keeps the clickable summary affordance aligned and usable without JavaScript
|
|
2374
2799
|
* or demo styles. Decorative palettes and custom markers belong outside the
|
|
2375
2800
|
* base component.
|
|
2801
|
+
*
|
|
2802
|
+
* Public hooks:
|
|
2803
|
+
* --accordion-radius
|
|
2804
|
+
* --accordion-marker-color
|
|
2376
2805
|
*/
|
|
2377
2806
|
.accordion {
|
|
2378
2807
|
--accordion-radius: var(--radius);
|
|
2808
|
+
--accordion-marker-color: var(--text-muted);
|
|
2379
2809
|
|
|
2380
2810
|
border: var(--border-width) solid var(--border);
|
|
2381
2811
|
border-radius: var(--accordion-radius);
|
|
@@ -2389,14 +2819,18 @@ dialog.drawer nav {
|
|
|
2389
2819
|
.accordion summary {
|
|
2390
2820
|
display: flex;
|
|
2391
2821
|
align-items: center;
|
|
2392
|
-
gap: var(--space-
|
|
2822
|
+
gap: var(--space-30);
|
|
2393
2823
|
cursor: pointer;
|
|
2394
2824
|
list-style: none;
|
|
2395
|
-
padding: var(--space-
|
|
2825
|
+
padding: var(--space-40);
|
|
2396
2826
|
color: var(--text);
|
|
2397
2827
|
font-weight: var(--font-weight-bold);
|
|
2398
2828
|
}
|
|
2399
2829
|
|
|
2830
|
+
.accordion summary:hover {
|
|
2831
|
+
color: var(--primary);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2400
2834
|
.accordion summary::-webkit-details-marker {
|
|
2401
2835
|
display: none;
|
|
2402
2836
|
}
|
|
@@ -2407,15 +2841,9 @@ dialog.drawer nav {
|
|
|
2407
2841
|
margin-inline-start: auto;
|
|
2408
2842
|
inline-size: 1rem;
|
|
2409
2843
|
block-size: 1rem;
|
|
2410
|
-
background-color: var(--
|
|
2411
|
-
-webkit-mask
|
|
2412
|
-
mask
|
|
2413
|
-
-webkit-mask-repeat: no-repeat;
|
|
2414
|
-
mask-repeat: no-repeat;
|
|
2415
|
-
-webkit-mask-size: contain;
|
|
2416
|
-
mask-size: contain;
|
|
2417
|
-
-webkit-mask-position: center;
|
|
2418
|
-
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;
|
|
2419
2847
|
transition: transform var(--duration);
|
|
2420
2848
|
}
|
|
2421
2849
|
|
|
@@ -2424,14 +2852,14 @@ dialog.drawer nav {
|
|
|
2424
2852
|
}
|
|
2425
2853
|
|
|
2426
2854
|
.accordion details > summary + * {
|
|
2427
|
-
padding-block: 0 var(--space-
|
|
2428
|
-
padding-inline: var(--space-
|
|
2855
|
+
padding-block: 0 var(--space-40);
|
|
2856
|
+
padding-inline: var(--space-40);
|
|
2429
2857
|
color: var(--text-muted);
|
|
2430
2858
|
margin-block: 0;
|
|
2431
2859
|
}
|
|
2432
2860
|
|
|
2433
2861
|
.accordion details[open] > summary + * {
|
|
2434
|
-
padding-block-start: var(--space-
|
|
2862
|
+
padding-block-start: var(--space-10);
|
|
2435
2863
|
}
|
|
2436
2864
|
|
|
2437
2865
|
.accordion details > summary + * > :first-child {
|
|
@@ -2446,22 +2874,7 @@ dialog.drawer nav {
|
|
|
2446
2874
|
|
|
2447
2875
|
@media (forced-colors: active) {
|
|
2448
2876
|
.accordion {
|
|
2449
|
-
|
|
2450
|
-
background: Canvas;
|
|
2451
|
-
color: CanvasText;
|
|
2452
|
-
}
|
|
2453
|
-
|
|
2454
|
-
.accordion > details + details {
|
|
2455
|
-
border-block-start-color: ButtonText;
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
.accordion summary {
|
|
2459
|
-
color: CanvasText;
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
|
-
.accordion summary::after {
|
|
2463
|
-
background-color: ButtonText;
|
|
2464
|
-
forced-color-adjust: none;
|
|
2877
|
+
--accordion-marker-color: CanvasText;
|
|
2465
2878
|
}
|
|
2466
2879
|
}
|
|
2467
2880
|
|
|
@@ -2472,23 +2885,22 @@ dialog.drawer nav {
|
|
|
2472
2885
|
*
|
|
2473
2886
|
* Uses currentColor so it adapts to buttons, alerts, badges, and text. The
|
|
2474
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
|
|
2475
2894
|
*/
|
|
2476
2895
|
/* Intent boundary — inherited intents do not tint this component.
|
|
2477
2896
|
Local intent classes still win because :where() has zero specificity. */
|
|
2478
2897
|
:where(.spinner) {
|
|
2479
|
-
/* @sync intent-boundary */
|
|
2480
2898
|
--intent: initial;
|
|
2481
|
-
--intent-fg: initial;
|
|
2482
|
-
--ui-bg: initial;
|
|
2483
|
-
--ui-fg: initial;
|
|
2484
|
-
--ui-border: initial;
|
|
2485
|
-
--ui-hover-bg: initial;
|
|
2486
|
-
/* @sync intent-boundary */
|
|
2487
2899
|
}
|
|
2488
2900
|
|
|
2489
2901
|
.spinner {
|
|
2490
|
-
--spinner-size:
|
|
2491
|
-
--spinner-width:
|
|
2902
|
+
--spinner-size: 1em;
|
|
2903
|
+
--spinner-width: 0.125em;
|
|
2492
2904
|
--spinner-track: currentColor;
|
|
2493
2905
|
--spinner-value: transparent;
|
|
2494
2906
|
|
|
@@ -2514,11 +2926,11 @@ dialog.drawer nav {
|
|
|
2514
2926
|
}
|
|
2515
2927
|
|
|
2516
2928
|
.spinner.sm {
|
|
2517
|
-
--spinner-size:
|
|
2929
|
+
--spinner-size: 0.75em;
|
|
2518
2930
|
}
|
|
2519
2931
|
|
|
2520
2932
|
.spinner.lg {
|
|
2521
|
-
--spinner-size:
|
|
2933
|
+
--spinner-size: 2rem;
|
|
2522
2934
|
--spinner-width: 3px;
|
|
2523
2935
|
}
|
|
2524
2936
|
|
|
@@ -2533,6 +2945,13 @@ dialog.drawer nav {
|
|
|
2533
2945
|
*
|
|
2534
2946
|
* Shows approximate loading structure without implying real content hierarchy.
|
|
2535
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
|
|
2536
2955
|
*/
|
|
2537
2956
|
.skeleton {
|
|
2538
2957
|
--skeleton-size: 1rem;
|
|
@@ -2602,6 +3021,12 @@ dialog.drawer nav {
|
|
|
2602
3021
|
* A non-button element with aria-busy="true" and a direct last-child .spinner
|
|
2603
3022
|
* shows that spinner as a centered overlay over a faded surface. Buttons keep
|
|
2604
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)
|
|
2605
3030
|
*/
|
|
2606
3031
|
[aria-busy] > .spinner:last-child {
|
|
2607
3032
|
display: none;
|
|
@@ -2626,6 +3051,7 @@ dialog.drawer nav {
|
|
|
2626
3051
|
}
|
|
2627
3052
|
|
|
2628
3053
|
[aria-busy="true"]:not(.btn) > .spinner:last-child {
|
|
3054
|
+
--spinner-size: 2rem;
|
|
2629
3055
|
position: absolute;
|
|
2630
3056
|
inset: 0;
|
|
2631
3057
|
z-index: 2;
|
|
@@ -2646,26 +3072,21 @@ dialog.drawer nav {
|
|
|
2646
3072
|
*
|
|
2647
3073
|
* Consumes the shared --intent/--primary for the value bar and --bar-height
|
|
2648
3074
|
* for thickness. Indeterminate state animates a shimmer using --duration-shimmer.
|
|
3075
|
+
*
|
|
3076
|
+
* Public hooks:
|
|
3077
|
+
* --progress-track
|
|
3078
|
+
* --progress-value
|
|
2649
3079
|
*/
|
|
2650
3080
|
/* Intent boundary — inherited intents do not tint this component.
|
|
2651
3081
|
Local intent classes still win because :where() has zero specificity. */
|
|
2652
3082
|
:where(.progress) {
|
|
2653
|
-
/* @sync intent-boundary */
|
|
2654
3083
|
--intent: initial;
|
|
2655
|
-
--intent-fg: initial;
|
|
2656
|
-
--ui-bg: initial;
|
|
2657
|
-
--ui-fg: initial;
|
|
2658
|
-
--ui-border: initial;
|
|
2659
|
-
--ui-hover-bg: initial;
|
|
2660
|
-
/* @sync intent-boundary */
|
|
2661
3084
|
}
|
|
2662
3085
|
|
|
2663
3086
|
.progress {
|
|
2664
3087
|
--progress-track: var(--surface-subtle);
|
|
2665
3088
|
--progress-value: var(--intent, var(--primary));
|
|
2666
|
-
}
|
|
2667
3089
|
|
|
2668
|
-
.progress {
|
|
2669
3090
|
appearance: none;
|
|
2670
3091
|
-webkit-appearance: none;
|
|
2671
3092
|
display: block;
|
|
@@ -2744,27 +3165,14 @@ dialog.drawer nav {
|
|
|
2744
3165
|
}
|
|
2745
3166
|
|
|
2746
3167
|
@media (forced-colors: active) {
|
|
2747
|
-
.progress
|
|
2748
|
-
|
|
2749
|
-
background: Canvas;
|
|
2750
|
-
}
|
|
2751
|
-
|
|
2752
|
-
.progress::-webkit-progress-bar {
|
|
2753
|
-
background: Canvas;
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
.progress::-webkit-progress-value {
|
|
3168
|
+
.progress::-webkit-progress-value,
|
|
3169
|
+
.progress:indeterminate::-webkit-progress-value {
|
|
2757
3170
|
background: Highlight;
|
|
2758
|
-
forced-color-adjust: none;
|
|
2759
3171
|
}
|
|
2760
3172
|
|
|
2761
|
-
.progress::-moz-progress-bar
|
|
3173
|
+
.progress::-moz-progress-bar,
|
|
3174
|
+
.progress:indeterminate::-moz-progress-bar {
|
|
2762
3175
|
background: Highlight;
|
|
2763
|
-
forced-color-adjust: none;
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
.progress:indeterminate {
|
|
2767
|
-
background: Canvas;
|
|
2768
3176
|
}
|
|
2769
3177
|
}
|
|
2770
3178
|
|
|
@@ -2780,23 +3188,20 @@ dialog.drawer nav {
|
|
|
2780
3188
|
* the coloring via --intent / --intent-fg. status.js applies these as plain
|
|
2781
3189
|
* classes on the target; it does not know their names or that they exist.
|
|
2782
3190
|
*
|
|
2783
|
-
*
|
|
2784
|
-
*
|
|
2785
|
-
*
|
|
2786
|
-
*
|
|
3191
|
+
* CSS API: .status-bar
|
|
3192
|
+
* JS target contract: [data-status][role="status"]
|
|
3193
|
+
* Normally combined, but they describe separate concerns.
|
|
3194
|
+
*
|
|
3195
|
+
* Internal:
|
|
3196
|
+
* --status-bg
|
|
3197
|
+
* --status-fg
|
|
2787
3198
|
*/
|
|
2788
3199
|
|
|
2789
3200
|
/* Intent boundary — inherited intents do not tint this component.
|
|
2790
3201
|
Local intent classes still win because :where() has zero specificity. */
|
|
2791
3202
|
:where(.status-bar) {
|
|
2792
|
-
/* @sync intent-boundary */
|
|
2793
3203
|
--intent: initial;
|
|
2794
3204
|
--intent-fg: initial;
|
|
2795
|
-
--ui-bg: initial;
|
|
2796
|
-
--ui-fg: initial;
|
|
2797
|
-
--ui-border: initial;
|
|
2798
|
-
--ui-hover-bg: initial;
|
|
2799
|
-
/* @sync intent-boundary */
|
|
2800
3205
|
}
|
|
2801
3206
|
|
|
2802
3207
|
.status-bar {
|
|
@@ -2804,22 +3209,22 @@ dialog.drawer nav {
|
|
|
2804
3209
|
--status-fg: var(--intent-fg, var(--surface));
|
|
2805
3210
|
|
|
2806
3211
|
position: fixed;
|
|
2807
|
-
inset-block-end: var(--space-
|
|
2808
|
-
inset-inline: var(--space-
|
|
2809
|
-
z-index: var(--z-status
|
|
3212
|
+
inset-block-end: var(--space-40);
|
|
3213
|
+
inset-inline: var(--space-40);
|
|
3214
|
+
z-index: var(--z-status);
|
|
2810
3215
|
|
|
2811
3216
|
display: inline-flex;
|
|
2812
3217
|
align-items: center;
|
|
2813
3218
|
justify-content: center;
|
|
2814
|
-
gap: var(--space-
|
|
3219
|
+
gap: var(--space-20);
|
|
2815
3220
|
|
|
2816
3221
|
inline-size: fit-content;
|
|
2817
|
-
max-inline-size: min(calc(
|
|
3222
|
+
max-inline-size: min(calc(var(--viewport-inline) - var(--space-60)), 32rem);
|
|
2818
3223
|
margin-inline: auto;
|
|
2819
3224
|
overflow-wrap: anywhere;
|
|
2820
3225
|
|
|
2821
|
-
padding-block: var(--space-
|
|
2822
|
-
padding-inline: var(--space-
|
|
3226
|
+
padding-block: var(--space-20);
|
|
3227
|
+
padding-inline: var(--space-30);
|
|
2823
3228
|
|
|
2824
3229
|
border-radius: var(--radius-full);
|
|
2825
3230
|
background: var(--status-bg);
|
|
@@ -2832,25 +3237,18 @@ dialog.drawer nav {
|
|
|
2832
3237
|
pointer-events: none;
|
|
2833
3238
|
|
|
2834
3239
|
transition:
|
|
2835
|
-
opacity var(--duration-slow) var(--ease
|
|
2836
|
-
scale var(--duration-slow) var(--ease
|
|
2837
|
-
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);
|
|
2838
3243
|
}
|
|
2839
3244
|
|
|
2840
3245
|
.status-bar:empty {
|
|
2841
3246
|
opacity: 0;
|
|
2842
3247
|
scale: 0.96;
|
|
2843
|
-
translate: 0 var(--space-
|
|
3248
|
+
translate: 0 var(--space-20);
|
|
2844
3249
|
pointer-events: none;
|
|
2845
3250
|
}
|
|
2846
3251
|
|
|
2847
|
-
@media (forced-colors: active) {
|
|
2848
|
-
.status-bar {
|
|
2849
|
-
border: var(--border-width) solid ButtonText;
|
|
2850
|
-
box-shadow: none;
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
3252
|
|
|
2855
3253
|
/* Display */
|
|
2856
3254
|
/*
|
|
@@ -2859,18 +3257,26 @@ dialog.drawer nav {
|
|
|
2859
3257
|
* The visual box stays the same across semantic elements. Grouping and status
|
|
2860
3258
|
* dots (an empty .badge child) compose around the avatar without changing its
|
|
2861
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
|
|
2862
3274
|
*/
|
|
2863
3275
|
/* Intent boundary — inherited intents do not tint this component.
|
|
2864
3276
|
Local intent classes still win because :where() has zero specificity. */
|
|
2865
3277
|
:where(.avatar) {
|
|
2866
|
-
/* @sync intent-boundary */
|
|
2867
3278
|
--intent: initial;
|
|
2868
3279
|
--intent-fg: initial;
|
|
2869
|
-
--ui-bg: initial;
|
|
2870
|
-
--ui-fg: initial;
|
|
2871
|
-
--ui-border: initial;
|
|
2872
|
-
--ui-hover-bg: initial;
|
|
2873
|
-
/* @sync intent-boundary */
|
|
2874
3280
|
}
|
|
2875
3281
|
|
|
2876
3282
|
.avatar {
|
|
@@ -2962,10 +3368,8 @@ button.avatar {
|
|
|
2962
3368
|
transition: opacity var(--duration);
|
|
2963
3369
|
}
|
|
2964
3370
|
|
|
2965
|
-
|
|
2966
|
-
:
|
|
2967
|
-
opacity: 1;
|
|
2968
|
-
}
|
|
3371
|
+
:where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):hover::after {
|
|
3372
|
+
opacity: 1;
|
|
2969
3373
|
}
|
|
2970
3374
|
|
|
2971
3375
|
:where(a.avatar, button.avatar):not(:where(:disabled, [aria-disabled="true"])):active {
|
|
@@ -3058,6 +3462,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
3058
3462
|
*
|
|
3059
3463
|
* Use .key outside prose. Plain kbd inside .prose gets the same visual
|
|
3060
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.
|
|
3061
3467
|
*/
|
|
3062
3468
|
.key {
|
|
3063
3469
|
display: inline-flex;
|
|
@@ -3089,14 +3495,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3089
3495
|
/* Intent boundary — inherited intents do not tint this component.
|
|
3090
3496
|
Local intent classes still win because :where() has zero specificity. */
|
|
3091
3497
|
:where(.overline) {
|
|
3092
|
-
/* @sync intent-boundary */
|
|
3093
3498
|
--intent: initial;
|
|
3094
|
-
--intent-fg: initial;
|
|
3095
|
-
--ui-bg: initial;
|
|
3096
|
-
--ui-fg: initial;
|
|
3097
|
-
--ui-border: initial;
|
|
3098
|
-
--ui-hover-bg: initial;
|
|
3099
|
-
/* @sync intent-boundary */
|
|
3100
3499
|
}
|
|
3101
3500
|
|
|
3102
3501
|
.overline {
|
|
@@ -3137,6 +3536,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
3137
3536
|
* .navbar is a horizontal bar; .navbar-brand is the brand link; .navbar-nav is
|
|
3138
3537
|
* the inline link list using .nav-link items. .nav-list is the vertical nav
|
|
3139
3538
|
* list shared with .drawer, fed by the shared --gap token.
|
|
3539
|
+
*
|
|
3540
|
+
* Internal:
|
|
3541
|
+
* --gap (local value for the shared gap token)
|
|
3140
3542
|
*/
|
|
3141
3543
|
.navbar {
|
|
3142
3544
|
display: flex;
|
|
@@ -3144,8 +3546,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
3144
3546
|
align-items: center;
|
|
3145
3547
|
justify-content: space-between;
|
|
3146
3548
|
gap: var(--gap);
|
|
3147
|
-
padding-block: var(--space-
|
|
3148
|
-
padding-inline: var(--space-
|
|
3549
|
+
padding-block: var(--space-30);
|
|
3550
|
+
padding-inline: var(--space-40);
|
|
3149
3551
|
border: var(--border-width) solid var(--border);
|
|
3150
3552
|
border-radius: var(--radius-lg);
|
|
3151
3553
|
background: var(--surface-raised);
|
|
@@ -3155,7 +3557,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3155
3557
|
.navbar-brand {
|
|
3156
3558
|
display: inline-flex;
|
|
3157
3559
|
align-items: center;
|
|
3158
|
-
gap: var(--space-
|
|
3560
|
+
gap: var(--space-20);
|
|
3159
3561
|
color: var(--text);
|
|
3160
3562
|
font-weight: var(--font-weight-bold);
|
|
3161
3563
|
text-decoration: none;
|
|
@@ -3169,7 +3571,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3169
3571
|
display: flex;
|
|
3170
3572
|
flex-wrap: wrap;
|
|
3171
3573
|
align-items: center;
|
|
3172
|
-
gap: var(--space-
|
|
3574
|
+
gap: var(--space-10);
|
|
3173
3575
|
margin: 0;
|
|
3174
3576
|
padding: 0;
|
|
3175
3577
|
list-style: none;
|
|
@@ -3179,7 +3581,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3179
3581
|
display: inline-flex;
|
|
3180
3582
|
align-items: center;
|
|
3181
3583
|
min-block-size: var(--control-size-sm);
|
|
3182
|
-
padding-inline: var(--space-
|
|
3584
|
+
padding-inline: var(--space-20);
|
|
3183
3585
|
border-radius: var(--radius);
|
|
3184
3586
|
color: var(--text-muted);
|
|
3185
3587
|
font-weight: var(--font-weight-medium);
|
|
@@ -3191,21 +3593,19 @@ a.avatar[aria-disabled="true"] {
|
|
|
3191
3593
|
color: var(--text);
|
|
3192
3594
|
}
|
|
3193
3595
|
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
color: var(--text);
|
|
3198
|
-
}
|
|
3596
|
+
.nav-link:hover {
|
|
3597
|
+
background: var(--surface-subtle);
|
|
3598
|
+
color: var(--text);
|
|
3199
3599
|
}
|
|
3200
3600
|
|
|
3201
3601
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links. */
|
|
3202
3602
|
.nav-list {
|
|
3203
3603
|
display: grid;
|
|
3604
|
+
--gap: var(--space-20);
|
|
3204
3605
|
gap: var(--gap);
|
|
3205
3606
|
margin: 0;
|
|
3206
3607
|
padding: 0;
|
|
3207
3608
|
list-style: none;
|
|
3208
|
-
--gap: var(--space-2);
|
|
3209
3609
|
}
|
|
3210
3610
|
|
|
3211
3611
|
.nav-list > li {
|
|
@@ -3220,13 +3620,11 @@ a.avatar[aria-disabled="true"] {
|
|
|
3220
3620
|
forced-color-adjust: none;
|
|
3221
3621
|
}
|
|
3222
3622
|
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
color: CanvasText;
|
|
3229
|
-
}
|
|
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;
|
|
3230
3628
|
}
|
|
3231
3629
|
}
|
|
3232
3630
|
|
|
@@ -3240,7 +3638,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3240
3638
|
display: flex;
|
|
3241
3639
|
flex-wrap: wrap;
|
|
3242
3640
|
align-items: center;
|
|
3243
|
-
gap: var(--space-
|
|
3641
|
+
gap: var(--space-10);
|
|
3244
3642
|
margin: 0;
|
|
3245
3643
|
padding: 0;
|
|
3246
3644
|
color: var(--text-subtle);
|
|
@@ -3251,7 +3649,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3251
3649
|
.breadcrumb li {
|
|
3252
3650
|
display: inline-flex;
|
|
3253
3651
|
align-items: center;
|
|
3254
|
-
gap: var(--space-
|
|
3652
|
+
gap: var(--space-10);
|
|
3255
3653
|
}
|
|
3256
3654
|
|
|
3257
3655
|
.breadcrumb li + li::before {
|
|
@@ -3264,25 +3662,17 @@ a.avatar[aria-disabled="true"] {
|
|
|
3264
3662
|
text-decoration: none;
|
|
3265
3663
|
}
|
|
3266
3664
|
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
color: var(--text);
|
|
3272
|
-
text-decoration: underline;
|
|
3273
|
-
}
|
|
3665
|
+
.breadcrumb a:not([aria-current]):hover,
|
|
3666
|
+
.breadcrumb a[aria-current="false"]:hover {
|
|
3667
|
+
color: var(--text);
|
|
3668
|
+
text-decoration: underline;
|
|
3274
3669
|
}
|
|
3275
3670
|
|
|
3276
3671
|
.breadcrumb a:focus-visible {
|
|
3277
3672
|
border-radius: var(--radius-sm);
|
|
3278
3673
|
}
|
|
3279
3674
|
|
|
3280
|
-
.breadcrumb a:where([aria-current]:not([aria-current="false"])) {
|
|
3281
|
-
color: var(--text);
|
|
3282
|
-
font-weight: var(--font-weight-medium);
|
|
3283
|
-
}
|
|
3284
|
-
|
|
3285
|
-
.breadcrumb li:where([aria-current]:not([aria-current="false"])) > :where(a, span) {
|
|
3675
|
+
.breadcrumb :where(a, span):where([aria-current]:not([aria-current="false"])) {
|
|
3286
3676
|
color: var(--text);
|
|
3287
3677
|
font-weight: var(--font-weight-medium);
|
|
3288
3678
|
}
|
|
@@ -3301,7 +3691,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3301
3691
|
display: flex;
|
|
3302
3692
|
flex-wrap: wrap;
|
|
3303
3693
|
align-items: center;
|
|
3304
|
-
gap: var(--space-
|
|
3694
|
+
gap: var(--space-10);
|
|
3305
3695
|
margin: 0;
|
|
3306
3696
|
padding: 0;
|
|
3307
3697
|
list-style: none;
|
|
@@ -3324,24 +3714,20 @@ a.avatar[aria-disabled="true"] {
|
|
|
3324
3714
|
* JavaScript owns roving tabindex, aria-selected, panel visibility,
|
|
3325
3715
|
* and arrow-key navigation. CSS owns the tab strip appearance and
|
|
3326
3716
|
* active indicator.
|
|
3717
|
+
*
|
|
3718
|
+
* Public hooks:
|
|
3719
|
+
* --tab-gap
|
|
3327
3720
|
*/
|
|
3328
3721
|
/* Intent boundary — inherited intents do not tint this component.
|
|
3329
3722
|
Local intent classes still win because :where() has zero specificity. */
|
|
3330
3723
|
:where(.tab) {
|
|
3331
|
-
/* @sync intent-boundary */
|
|
3332
3724
|
--intent: initial;
|
|
3333
|
-
--intent-fg: initial;
|
|
3334
|
-
--ui-bg: initial;
|
|
3335
|
-
--ui-fg: initial;
|
|
3336
|
-
--ui-border: initial;
|
|
3337
|
-
--ui-hover-bg: initial;
|
|
3338
|
-
/* @sync intent-boundary */
|
|
3339
3725
|
}
|
|
3340
3726
|
|
|
3341
3727
|
.tabs {
|
|
3342
3728
|
display: flex;
|
|
3343
3729
|
flex-wrap: wrap;
|
|
3344
|
-
gap: var(--space-
|
|
3730
|
+
gap: var(--space-10);
|
|
3345
3731
|
margin: 0;
|
|
3346
3732
|
padding: 0;
|
|
3347
3733
|
border-block-end: var(--border-width) solid var(--border);
|
|
@@ -3359,8 +3745,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
3359
3745
|
.tab {
|
|
3360
3746
|
display: inline-flex;
|
|
3361
3747
|
align-items: center;
|
|
3748
|
+
gap: var(--tab-gap, 0.375em);
|
|
3362
3749
|
min-block-size: var(--control-size);
|
|
3363
|
-
padding-inline: var(--space-
|
|
3750
|
+
padding-inline: var(--space-30);
|
|
3364
3751
|
border: 0;
|
|
3365
3752
|
border-block-end: 2px solid transparent;
|
|
3366
3753
|
border-radius: 0;
|
|
@@ -3376,7 +3763,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3376
3763
|
justify-content: flex-start;
|
|
3377
3764
|
border-block-end: 0;
|
|
3378
3765
|
border-inline-end: 2px solid transparent;
|
|
3379
|
-
padding-inline-end: var(--space-
|
|
3766
|
+
padding-inline-end: var(--space-40);
|
|
3380
3767
|
}
|
|
3381
3768
|
|
|
3382
3769
|
.tab:is(:disabled, [aria-disabled="true"]) {
|
|
@@ -3384,64 +3771,39 @@ a.avatar[aria-disabled="true"] {
|
|
|
3384
3771
|
opacity: var(--disabled-opacity);
|
|
3385
3772
|
}
|
|
3386
3773
|
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
}
|
|
3392
|
-
|
|
3774
|
+
.tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
3775
|
+
color: var(--text);
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3393
3778
|
.tab:focus-visible {
|
|
3394
3779
|
outline: var(--focus-outline);
|
|
3780
|
+
outline-color: var(--focus);
|
|
3395
3781
|
outline-offset: -2px;
|
|
3396
3782
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
3397
3783
|
}
|
|
3398
3784
|
|
|
3399
|
-
.tab[aria-selected="true"]
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
border-block-end-color: var(--intent, var(--primary));
|
|
3403
|
-
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));
|
|
3404
3788
|
}
|
|
3405
3789
|
|
|
3406
|
-
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"]
|
|
3407
|
-
.tabs[aria-orientation="vertical"] .tab[aria-current]:not([aria-current="false"]),
|
|
3408
|
-
.tabs[aria-orientation="vertical"] .tab[aria-pressed="true"] {
|
|
3790
|
+
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
|
|
3409
3791
|
border-block-end-color: transparent;
|
|
3410
|
-
border-inline-end-color: var(--intent, var(--
|
|
3792
|
+
border-inline-end-color: var(--intent, var(--state-selected));
|
|
3411
3793
|
}
|
|
3412
3794
|
|
|
3413
3795
|
@media (forced-colors: active) {
|
|
3414
|
-
.
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
.tabs[aria-orientation="vertical"] {
|
|
3419
|
-
border-block-end-color: transparent;
|
|
3420
|
-
border-inline-end-color: ButtonText;
|
|
3421
|
-
}
|
|
3422
|
-
|
|
3423
|
-
.tab {
|
|
3424
|
-
color: ButtonText;
|
|
3425
|
-
}
|
|
3426
|
-
|
|
3427
|
-
@media (hover: hover) {
|
|
3428
|
-
.tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
3429
|
-
color: ButtonText;
|
|
3430
|
-
text-decoration: underline;
|
|
3431
|
-
text-underline-offset: 0.18em;
|
|
3432
|
-
}
|
|
3796
|
+
.tab:hover:not(:where(:disabled, [aria-disabled="true"])) {
|
|
3797
|
+
text-decoration: underline;
|
|
3798
|
+
text-underline-offset: 0.18em;
|
|
3433
3799
|
}
|
|
3434
3800
|
|
|
3435
|
-
.tab[aria-selected="true"]
|
|
3436
|
-
.tab[aria-current]:not([aria-current="false"]),
|
|
3437
|
-
.tab[aria-pressed="true"] {
|
|
3801
|
+
.tab[aria-selected="true"] {
|
|
3438
3802
|
border-block-end-color: Highlight;
|
|
3439
3803
|
color: Highlight;
|
|
3440
3804
|
}
|
|
3441
3805
|
|
|
3442
|
-
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"]
|
|
3443
|
-
.tabs[aria-orientation="vertical"] .tab[aria-current]:not([aria-current="false"]),
|
|
3444
|
-
.tabs[aria-orientation="vertical"] .tab[aria-pressed="true"] {
|
|
3806
|
+
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"] {
|
|
3445
3807
|
border-block-end-color: transparent;
|
|
3446
3808
|
border-inline-end-color: Highlight;
|
|
3447
3809
|
}
|
|
@@ -3460,8 +3822,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
3460
3822
|
* Progressive enhancement: on browsers that support :target-current,
|
|
3461
3823
|
* this file enables the native target group and marks the active link.
|
|
3462
3824
|
*
|
|
3463
|
-
* Falls back gracefully: links still work when
|
|
3464
|
-
*
|
|
3825
|
+
* Falls back gracefully: links still work when :target-current is unavailable
|
|
3826
|
+
* and when the JavaScript enhancer is not loaded at all.
|
|
3465
3827
|
*/
|
|
3466
3828
|
|
|
3467
3829
|
@supports selector(.scrollspy a:target-current) {
|
|
@@ -3482,6 +3844,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
3482
3844
|
*
|
|
3483
3845
|
* .table-wrap provides the scroll container and outer chrome; .table owns row
|
|
3484
3846
|
* and cell rules. Tokens are sourced from the shared scale.
|
|
3847
|
+
*
|
|
3848
|
+
* Public hooks:
|
|
3849
|
+
* --table-min
|
|
3850
|
+
* --table-cell-pad
|
|
3485
3851
|
*/
|
|
3486
3852
|
.table-wrap {
|
|
3487
3853
|
inline-size: 100%;
|
|
@@ -3495,7 +3861,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3495
3861
|
|
|
3496
3862
|
.table {
|
|
3497
3863
|
--table-min: 100%;
|
|
3498
|
-
--table-cell-pad: var(--space-
|
|
3864
|
+
--table-cell-pad: var(--space-30);
|
|
3499
3865
|
|
|
3500
3866
|
inline-size: max(var(--table-min), 100%);
|
|
3501
3867
|
border-collapse: collapse;
|
|
@@ -3531,10 +3897,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
3531
3897
|
background: var(--surface-subtle);
|
|
3532
3898
|
}
|
|
3533
3899
|
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
background-color: var(--hover-overlay);
|
|
3537
|
-
}
|
|
3900
|
+
.table tbody tr:hover {
|
|
3901
|
+
background-color: var(--hover-overlay);
|
|
3538
3902
|
}
|
|
3539
3903
|
|
|
3540
3904
|
.table > tr:last-child > *,
|
|
@@ -3548,18 +3912,6 @@ a.avatar[aria-disabled="true"] {
|
|
|
3548
3912
|
* Maps the three standard meter zones (optimum / suboptimum / even-less-good)
|
|
3549
3913
|
* to the shared success / warning / danger intent colors.
|
|
3550
3914
|
*/
|
|
3551
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
3552
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
3553
|
-
:where(.meter) {
|
|
3554
|
-
/* @sync intent-boundary */
|
|
3555
|
-
--intent: initial;
|
|
3556
|
-
--intent-fg: initial;
|
|
3557
|
-
--ui-bg: initial;
|
|
3558
|
-
--ui-fg: initial;
|
|
3559
|
-
--ui-border: initial;
|
|
3560
|
-
--ui-hover-bg: initial;
|
|
3561
|
-
/* @sync intent-boundary */
|
|
3562
|
-
}
|
|
3563
3915
|
|
|
3564
3916
|
.meter {
|
|
3565
3917
|
appearance: none;
|
|
@@ -3605,29 +3957,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
3605
3957
|
}
|
|
3606
3958
|
|
|
3607
3959
|
@media (forced-colors: active) {
|
|
3608
|
-
.meter {
|
|
3609
|
-
border: var(--border-width) solid ButtonText;
|
|
3610
|
-
background: Canvas;
|
|
3611
|
-
}
|
|
3612
|
-
|
|
3613
|
-
.meter::-webkit-meter-bar {
|
|
3614
|
-
border: 0;
|
|
3615
|
-
background: Canvas;
|
|
3616
|
-
}
|
|
3617
|
-
|
|
3618
3960
|
.meter::-webkit-meter-optimum-value,
|
|
3619
3961
|
.meter::-webkit-meter-suboptimum-value,
|
|
3620
3962
|
.meter::-webkit-meter-even-less-good-value,
|
|
3621
3963
|
.meter::-moz-meter-bar {
|
|
3622
3964
|
background: Highlight;
|
|
3623
|
-
forced-color-adjust: none;
|
|
3624
3965
|
}
|
|
3625
3966
|
}
|
|
3626
3967
|
|
|
3627
|
-
/* Forms — import order matters: switch.css
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
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. */
|
|
3631
3972
|
/*
|
|
3632
3973
|
* Form layout and supporting text.
|
|
3633
3974
|
*
|
|
@@ -3636,23 +3977,26 @@ a.avatar[aria-disabled="true"] {
|
|
|
3636
3977
|
*
|
|
3637
3978
|
* Public API: .field, .field-label, .field-help, .field-error, .field-group,
|
|
3638
3979
|
* .required-mark.
|
|
3980
|
+
*
|
|
3981
|
+
* Public hooks:
|
|
3982
|
+
* --fieldset-legend-padding-inline
|
|
3639
3983
|
*/
|
|
3640
3984
|
.field {
|
|
3641
3985
|
display: grid;
|
|
3642
|
-
gap: var(--space-
|
|
3986
|
+
gap: var(--space-10);
|
|
3643
3987
|
color: var(--text);
|
|
3644
3988
|
}
|
|
3645
3989
|
|
|
3646
3990
|
.field-group {
|
|
3647
3991
|
min-inline-size: 0;
|
|
3648
3992
|
margin: 0;
|
|
3649
|
-
padding: var(--space-
|
|
3993
|
+
padding: var(--space-30);
|
|
3650
3994
|
border: var(--border-width) solid var(--border);
|
|
3651
3995
|
border-radius: var(--radius);
|
|
3652
3996
|
}
|
|
3653
3997
|
|
|
3654
3998
|
.field-group + .field-group {
|
|
3655
|
-
margin-block-start: var(--space-
|
|
3999
|
+
margin-block-start: var(--space-40);
|
|
3656
4000
|
}
|
|
3657
4001
|
|
|
3658
4002
|
.field-label,
|
|
@@ -3663,7 +4007,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3663
4007
|
|
|
3664
4008
|
.field-group > legend {
|
|
3665
4009
|
padding-block: 0;
|
|
3666
|
-
padding-inline: var(--fieldset-legend-padding-inline, var(--space-
|
|
4010
|
+
padding-inline: var(--fieldset-legend-padding-inline, var(--space-10));
|
|
3667
4011
|
font-size: var(--font-size-xs);
|
|
3668
4012
|
}
|
|
3669
4013
|
|
|
@@ -3702,6 +4046,12 @@ a.avatar[aria-disabled="true"] {
|
|
|
3702
4046
|
* --control-border-block-end-color/-width are unset extension points, mirroring
|
|
3703
4047
|
* --btn-border-block-end-* (button.css), for themes that give text controls an
|
|
3704
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
|
|
3705
4055
|
*/
|
|
3706
4056
|
.input,
|
|
3707
4057
|
.textarea,
|
|
@@ -3709,14 +4059,12 @@ a.avatar[aria-disabled="true"] {
|
|
|
3709
4059
|
--control-border: var(--border);
|
|
3710
4060
|
--control-bg: var(--surface);
|
|
3711
4061
|
--control-fg: var(--text);
|
|
3712
|
-
--control-ring: none;
|
|
3713
4062
|
--control-cursor: auto;
|
|
3714
|
-
--control-opacity: 1;
|
|
3715
4063
|
|
|
3716
4064
|
appearance: none;
|
|
3717
4065
|
inline-size: 100%;
|
|
3718
4066
|
min-block-size: var(--control-size);
|
|
3719
|
-
padding-inline: var(--space-
|
|
4067
|
+
padding-inline: var(--space-30);
|
|
3720
4068
|
border: var(--border-width) solid var(--form-invalid-border, var(--control-border));
|
|
3721
4069
|
border-block-end-color: var(
|
|
3722
4070
|
--form-invalid-border,
|
|
@@ -3727,9 +4075,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3727
4075
|
background: var(--control-bg);
|
|
3728
4076
|
color: var(--control-fg);
|
|
3729
4077
|
font-size: var(--control-font-size);
|
|
3730
|
-
box-shadow: var(--control-ring);
|
|
3731
4078
|
cursor: var(--control-cursor);
|
|
3732
|
-
opacity: var(--control-opacity);
|
|
3733
4079
|
outline: none;
|
|
3734
4080
|
transition:
|
|
3735
4081
|
border-color var(--duration),
|
|
@@ -3740,27 +4086,24 @@ a.avatar[aria-disabled="true"] {
|
|
|
3740
4086
|
|
|
3741
4087
|
.textarea {
|
|
3742
4088
|
min-block-size: 6rem;
|
|
3743
|
-
padding-block: var(--space-
|
|
4089
|
+
padding-block: var(--space-20);
|
|
3744
4090
|
resize: vertical;
|
|
3745
4091
|
}
|
|
3746
4092
|
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
}
|
|
4093
|
+
.input:hover:not(:disabled),
|
|
4094
|
+
.textarea:hover:not(:disabled) {
|
|
4095
|
+
--control-border: var(--text-subtle);
|
|
4096
|
+
}
|
|
3752
4097
|
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
}
|
|
4098
|
+
.select:hover:not(:disabled) {
|
|
4099
|
+
--control-bg: var(--surface-subtle);
|
|
3756
4100
|
}
|
|
3757
4101
|
|
|
3758
4102
|
.input:focus-visible,
|
|
3759
4103
|
.textarea:focus-visible,
|
|
3760
4104
|
.select:focus-visible {
|
|
3761
4105
|
--control-border: var(--focus);
|
|
3762
|
-
outline:
|
|
3763
|
-
outline-offset: var(--focus-outline-offset);
|
|
4106
|
+
outline: 2px solid transparent;
|
|
3764
4107
|
box-shadow: var(--focus-ring-shadow);
|
|
3765
4108
|
}
|
|
3766
4109
|
|
|
@@ -3774,10 +4117,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
3774
4117
|
.textarea:disabled,
|
|
3775
4118
|
.select:disabled {
|
|
3776
4119
|
--control-bg: var(--surface-subtle);
|
|
3777
|
-
--control-fg: var(--
|
|
4120
|
+
--control-fg: var(--state-disabled);
|
|
3778
4121
|
--control-border: var(--border);
|
|
3779
4122
|
--control-cursor: not-allowed;
|
|
3780
|
-
--control-opacity: 1;
|
|
3781
4123
|
}
|
|
3782
4124
|
|
|
3783
4125
|
.input::placeholder,
|
|
@@ -3785,49 +4127,59 @@ a.avatar[aria-disabled="true"] {
|
|
|
3785
4127
|
color: var(--text-muted);
|
|
3786
4128
|
}
|
|
3787
4129
|
|
|
3788
|
-
@media (forced-colors: active) {
|
|
3789
|
-
.input:focus-visible,
|
|
3790
|
-
.textarea:focus-visible,
|
|
3791
|
-
.select:focus-visible {
|
|
3792
|
-
box-shadow: none;
|
|
3793
|
-
}
|
|
3794
|
-
}
|
|
3795
|
-
|
|
3796
4130
|
/*
|
|
3797
|
-
* Input Icon —
|
|
4131
|
+
* Input Icon — decorative icon inside a relative wrapper, leading or trailing.
|
|
4132
|
+
*
|
|
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.
|
|
3798
4137
|
*
|
|
3799
|
-
*
|
|
3800
|
-
*
|
|
3801
|
-
* <span>. Sizes with --variant-icon-size (variants.css) so .sm/.lg pick up
|
|
3802
|
-
* the same icon scale used elsewhere (alert.css); the input's start padding
|
|
3803
|
-
* grows to match so text never runs under the icon.
|
|
4138
|
+
* Public hooks:
|
|
4139
|
+
* --input-icon-size
|
|
3804
4140
|
*/
|
|
3805
4141
|
.input-icon {
|
|
3806
|
-
--input-icon-size:
|
|
4142
|
+
--input-icon-size: 1.25rem;
|
|
3807
4143
|
|
|
3808
4144
|
display: block;
|
|
3809
4145
|
position: relative;
|
|
3810
4146
|
}
|
|
3811
4147
|
|
|
3812
|
-
|
|
4148
|
+
/* Icon — shared by leading and trailing */
|
|
4149
|
+
.input-icon > :where(:not(.input)) {
|
|
3813
4150
|
position: absolute;
|
|
3814
4151
|
inset-block-start: 50%;
|
|
3815
|
-
inset-inline-start: var(--space-3);
|
|
3816
4152
|
transform: translateY(-50%);
|
|
3817
4153
|
font-size: var(--input-icon-size);
|
|
3818
4154
|
color: var(--text-muted);
|
|
3819
4155
|
pointer-events: none;
|
|
3820
4156
|
}
|
|
3821
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
|
+
|
|
3822
4168
|
/* font-size only sizes text-metric icons (webfont <i>, <span>). An inline
|
|
3823
4169
|
<svg> with just a viewBox ignores it, so give it explicit dimensions. */
|
|
3824
|
-
.input-icon > svg:
|
|
4170
|
+
.input-icon > svg:where(:not(.input)) {
|
|
3825
4171
|
inline-size: var(--input-icon-size);
|
|
3826
4172
|
block-size: var(--input-icon-size);
|
|
3827
4173
|
}
|
|
3828
4174
|
|
|
3829
|
-
|
|
3830
|
-
|
|
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));
|
|
3831
4183
|
}
|
|
3832
4184
|
|
|
3833
4185
|
/*
|
|
@@ -3840,23 +4192,23 @@ a.avatar[aria-disabled="true"] {
|
|
|
3840
4192
|
*
|
|
3841
4193
|
* Switches (input[role="switch"]) are excluded so switch.css owns their
|
|
3842
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
|
|
3843
4201
|
*/
|
|
3844
4202
|
/* Intent boundary — inherited intents do not tint this component.
|
|
3845
4203
|
Local intent classes still win because :where() has zero specificity. */
|
|
3846
4204
|
:where(.choice, .check, .radio) {
|
|
3847
|
-
/* @sync intent-boundary */
|
|
3848
4205
|
--intent: initial;
|
|
3849
4206
|
--intent-fg: initial;
|
|
3850
|
-
--ui-bg: initial;
|
|
3851
|
-
--ui-fg: initial;
|
|
3852
|
-
--ui-border: initial;
|
|
3853
|
-
--ui-hover-bg: initial;
|
|
3854
|
-
/* @sync intent-boundary */
|
|
3855
4207
|
}
|
|
3856
4208
|
|
|
3857
|
-
/* A classed .choice is the component surface. Let its
|
|
4209
|
+
/* A classed .choice is the component surface. Let its direct input consume a
|
|
3858
4210
|
local intent without reopening inherited page-level intents. */
|
|
3859
|
-
: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"]) {
|
|
3860
4212
|
--intent: inherit;
|
|
3861
4213
|
--intent-fg: inherit;
|
|
3862
4214
|
}
|
|
@@ -3866,7 +4218,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3866
4218
|
|
|
3867
4219
|
display: inline-flex;
|
|
3868
4220
|
align-items: flex-start;
|
|
3869
|
-
gap: var(--space-
|
|
4221
|
+
gap: var(--space-20);
|
|
3870
4222
|
color: var(--text);
|
|
3871
4223
|
font-size: var(--control-font-size);
|
|
3872
4224
|
cursor: pointer;
|
|
@@ -3889,8 +4241,11 @@ a.avatar[aria-disabled="true"] {
|
|
|
3889
4241
|
display: block;
|
|
3890
4242
|
}
|
|
3891
4243
|
|
|
3892
|
-
|
|
3893
|
-
|
|
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);
|
|
3894
4249
|
cursor: not-allowed;
|
|
3895
4250
|
}
|
|
3896
4251
|
|
|
@@ -3907,8 +4262,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3907
4262
|
place-content: center;
|
|
3908
4263
|
inline-size: 1.125em;
|
|
3909
4264
|
block-size: 1.125em;
|
|
3910
|
-
margin
|
|
3911
|
-
margin-block-end: 0;
|
|
4265
|
+
margin: 0;
|
|
3912
4266
|
border: var(--border-width) solid var(--form-invalid-border, var(--choice-border));
|
|
3913
4267
|
background: var(--choice-bg);
|
|
3914
4268
|
color: var(--choice-mark);
|
|
@@ -3949,7 +4303,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3949
4303
|
background: currentColor;
|
|
3950
4304
|
}
|
|
3951
4305
|
|
|
3952
|
-
.check:checked,
|
|
4306
|
+
.check:is(:checked, :indeterminate),
|
|
3953
4307
|
.radio:checked {
|
|
3954
4308
|
--choice-border: var(--intent, var(--primary));
|
|
3955
4309
|
--choice-bg: var(--intent, var(--primary));
|
|
@@ -3960,12 +4314,6 @@ a.avatar[aria-disabled="true"] {
|
|
|
3960
4314
|
transform: translateY(-0.06em) rotate(-45deg) scale(1);
|
|
3961
4315
|
}
|
|
3962
4316
|
|
|
3963
|
-
.check:indeterminate {
|
|
3964
|
-
--choice-border: var(--intent, var(--primary));
|
|
3965
|
-
--choice-bg: var(--intent, var(--primary));
|
|
3966
|
-
--choice-mark: var(--intent-fg, var(--primary-fg));
|
|
3967
|
-
}
|
|
3968
|
-
|
|
3969
4317
|
.check:indeterminate::before {
|
|
3970
4318
|
border-inline-start: 0;
|
|
3971
4319
|
inline-size: 0.6em;
|
|
@@ -3978,18 +4326,15 @@ a.avatar[aria-disabled="true"] {
|
|
|
3978
4326
|
transform: scale(1);
|
|
3979
4327
|
}
|
|
3980
4328
|
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
--choice-border: var(--text-subtle);
|
|
3985
|
-
}
|
|
4329
|
+
.check:hover:not(:checked, :indeterminate, :disabled),
|
|
4330
|
+
.radio:hover:not(:checked, :indeterminate, :disabled) {
|
|
4331
|
+
--choice-border: var(--text-subtle);
|
|
3986
4332
|
}
|
|
3987
4333
|
|
|
3988
4334
|
.check:focus-visible,
|
|
3989
4335
|
.radio:focus-visible {
|
|
3990
4336
|
--choice-border: var(--focus);
|
|
3991
|
-
outline:
|
|
3992
|
-
outline-offset: var(--focus-outline-offset);
|
|
4337
|
+
outline: 2px solid transparent;
|
|
3993
4338
|
box-shadow: var(--focus-ring-shadow);
|
|
3994
4339
|
}
|
|
3995
4340
|
|
|
@@ -3997,7 +4342,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
3997
4342
|
.radio:disabled {
|
|
3998
4343
|
--choice-bg: var(--surface-subtle);
|
|
3999
4344
|
--choice-border: var(--border);
|
|
4000
|
-
--choice-mark: var(--
|
|
4345
|
+
--choice-mark: var(--state-disabled);
|
|
4001
4346
|
cursor: not-allowed;
|
|
4002
4347
|
opacity: 1;
|
|
4003
4348
|
}
|
|
@@ -4030,23 +4375,30 @@ a.avatar[aria-disabled="true"] {
|
|
|
4030
4375
|
/*
|
|
4031
4376
|
* Choice cards — card-style radio and checkbox labels.
|
|
4032
4377
|
*
|
|
4033
|
-
* Each .choice-card is a <label>
|
|
4034
|
-
*
|
|
4035
|
-
*
|
|
4378
|
+
* Each .choice-card is a <label> wrapping a native radio or checkbox. When
|
|
4379
|
+
* :has() is supported the input is visually hidden and the card mirrors its
|
|
4380
|
+
* state: checked border and tint, checkmark badge (round for radio, rounded
|
|
4381
|
+
* square for checkbox), focus ring, disabled dimming. Without :has() the
|
|
4382
|
+
* native input stays visible so selection state never disappears.
|
|
4383
|
+
*
|
|
4384
|
+
* The badge overhangs the top-right corner by 0.375em; ancestors with
|
|
4385
|
+
* overflow: hidden will clip it. Pair with grid or flex utilities for group
|
|
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
|
|
4036
4394
|
*/
|
|
4037
4395
|
:where(.choice-card) {
|
|
4038
|
-
/* @sync intent-boundary */
|
|
4039
4396
|
--intent: initial;
|
|
4040
4397
|
--intent-fg: initial;
|
|
4041
|
-
--ui-bg: initial;
|
|
4042
|
-
--ui-fg: initial;
|
|
4043
|
-
--ui-border: initial;
|
|
4044
|
-
--ui-hover-bg: initial;
|
|
4045
|
-
/* @sync intent-boundary */
|
|
4046
4398
|
}
|
|
4047
4399
|
|
|
4048
4400
|
.choice-card {
|
|
4049
|
-
--choice-card-pad: var(--
|
|
4401
|
+
--choice-card-pad: var(--density-space, var(--space-40));
|
|
4050
4402
|
--choice-card-radius: var(--radius);
|
|
4051
4403
|
--choice-card-border: var(--border);
|
|
4052
4404
|
--choice-card-bg: var(--surface);
|
|
@@ -4055,7 +4407,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4055
4407
|
position: relative;
|
|
4056
4408
|
display: block;
|
|
4057
4409
|
padding: var(--choice-card-pad);
|
|
4058
|
-
border: var(--border-width) solid var(--choice-card-border);
|
|
4410
|
+
border: var(--border-width) solid var(--form-invalid-border, var(--choice-card-border));
|
|
4059
4411
|
border-radius: var(--choice-card-radius);
|
|
4060
4412
|
background: var(--choice-card-bg);
|
|
4061
4413
|
color: var(--text);
|
|
@@ -4067,23 +4419,26 @@ a.avatar[aria-disabled="true"] {
|
|
|
4067
4419
|
}
|
|
4068
4420
|
|
|
4069
4421
|
/* The native input is visually hidden but stays focusable and participates in
|
|
4070
|
-
form submission and keyboard navigation (sr-only pattern).
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4422
|
+
form submission and keyboard navigation (sr-only pattern). Guarded: without
|
|
4423
|
+
:has() the card cannot mirror the input's state, so the visible native
|
|
4424
|
+
control is the fallback affordance. */
|
|
4425
|
+
@supports selector(:has(*)) {
|
|
4426
|
+
.choice-card > :is(input[type="radio"], input[type="checkbox"]) {
|
|
4427
|
+
position: absolute;
|
|
4428
|
+
inline-size: 1px;
|
|
4429
|
+
block-size: 1px;
|
|
4430
|
+
padding: 0;
|
|
4431
|
+
margin: -1px;
|
|
4432
|
+
overflow: hidden;
|
|
4433
|
+
clip: rect(0, 0, 0, 0);
|
|
4434
|
+
white-space: nowrap;
|
|
4435
|
+
border: 0;
|
|
4436
|
+
}
|
|
4081
4437
|
}
|
|
4082
4438
|
|
|
4083
4439
|
/* Focus ring on the label follows the hidden input's focus. */
|
|
4084
4440
|
.choice-card:has(:focus-visible) {
|
|
4085
|
-
outline:
|
|
4086
|
-
outline-offset: var(--focus-outline-offset);
|
|
4441
|
+
outline: 2px solid transparent;
|
|
4087
4442
|
box-shadow: var(--focus-ring-shadow);
|
|
4088
4443
|
}
|
|
4089
4444
|
|
|
@@ -4092,10 +4447,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4092
4447
|
cursor: not-allowed;
|
|
4093
4448
|
}
|
|
4094
4449
|
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
--choice-card-border: var(--text-subtle);
|
|
4098
|
-
}
|
|
4450
|
+
.choice-card:hover:not(:has(:disabled)) {
|
|
4451
|
+
--choice-card-border: var(--text-subtle);
|
|
4099
4452
|
}
|
|
4100
4453
|
|
|
4101
4454
|
/* Checked border — shared by radio and checkbox */
|
|
@@ -4110,40 +4463,48 @@ a.avatar[aria-disabled="true"] {
|
|
|
4110
4463
|
}
|
|
4111
4464
|
}
|
|
4112
4465
|
|
|
4113
|
-
/* Checkmark badge —
|
|
4114
|
-
|
|
4466
|
+
/* Checkmark badge — always rendered at scale(0) and scaled in on check, so
|
|
4467
|
+
the entrance can transition (a pseudo created on :checked could not). */
|
|
4468
|
+
.choice-card::before,
|
|
4469
|
+
.choice-card::after {
|
|
4115
4470
|
content: "";
|
|
4116
4471
|
position: absolute;
|
|
4117
4472
|
inset-inline-end: -0.375em;
|
|
4118
4473
|
inset-block-start: -0.375em;
|
|
4119
4474
|
inline-size: var(--choice-card-check-size);
|
|
4120
4475
|
block-size: var(--choice-card-check-size);
|
|
4476
|
+
transform: scale(0);
|
|
4477
|
+
transition: transform var(--duration);
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
/* Badge disc — circle for radio, rounded square for checkbox */
|
|
4481
|
+
.choice-card::before {
|
|
4121
4482
|
background: var(--intent, var(--primary));
|
|
4122
|
-
z-index: 1;
|
|
4123
4483
|
box-shadow: 0 0 0 2px var(--surface);
|
|
4484
|
+
z-index: 1;
|
|
4124
4485
|
}
|
|
4125
4486
|
|
|
4126
|
-
.choice-card:has(input[type="radio"]
|
|
4487
|
+
.choice-card:has(input[type="radio"])::before {
|
|
4127
4488
|
border-radius: var(--radius-full);
|
|
4128
4489
|
}
|
|
4129
4490
|
|
|
4130
|
-
.choice-card:has(input[type="checkbox"]
|
|
4491
|
+
.choice-card:has(input[type="checkbox"])::before {
|
|
4131
4492
|
border-radius: var(--radius-sm);
|
|
4132
4493
|
}
|
|
4133
4494
|
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
position: absolute;
|
|
4137
|
-
inset-inline-end: -0.375em;
|
|
4138
|
-
inset-block-start: -0.375em;
|
|
4139
|
-
inline-size: var(--choice-card-check-size);
|
|
4140
|
-
block-size: var(--choice-card-check-size);
|
|
4495
|
+
/* Check mark */
|
|
4496
|
+
.choice-card::after {
|
|
4141
4497
|
background: var(--intent-fg, var(--primary-fg));
|
|
4142
|
-
mask: var(--icon-check
|
|
4143
|
-
|
|
4498
|
+
-webkit-mask: var(--icon-check) center / 60% no-repeat;
|
|
4499
|
+
mask: var(--icon-check) center / 60% no-repeat;
|
|
4144
4500
|
z-index: 2;
|
|
4145
4501
|
}
|
|
4146
4502
|
|
|
4503
|
+
.choice-card:has(:checked)::before,
|
|
4504
|
+
.choice-card:has(:checked)::after {
|
|
4505
|
+
transform: scale(1);
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4147
4508
|
@media (forced-colors: active) {
|
|
4148
4509
|
.choice-card {
|
|
4149
4510
|
--choice-card-border: ButtonText;
|
|
@@ -4152,6 +4513,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
4152
4513
|
|
|
4153
4514
|
.choice-card:has(:checked) {
|
|
4154
4515
|
--choice-card-border: Highlight;
|
|
4516
|
+
/* Pin the background: with forced-color-adjust: none the color-mix tint
|
|
4517
|
+
above would actually render, uncontrolled in high-contrast themes. */
|
|
4518
|
+
--choice-card-bg: Canvas;
|
|
4155
4519
|
forced-color-adjust: none;
|
|
4156
4520
|
}
|
|
4157
4521
|
|
|
@@ -4160,14 +4524,6 @@ a.avatar[aria-disabled="true"] {
|
|
|
4160
4524
|
box-shadow: 0 0 0 2px Canvas;
|
|
4161
4525
|
}
|
|
4162
4526
|
|
|
4163
|
-
.choice-card:has(input[type="radio"]:checked)::before {
|
|
4164
|
-
border-radius: var(--radius-full);
|
|
4165
|
-
}
|
|
4166
|
-
|
|
4167
|
-
.choice-card:has(input[type="checkbox"]:checked)::before {
|
|
4168
|
-
border-radius: var(--radius-sm);
|
|
4169
|
-
}
|
|
4170
|
-
|
|
4171
4527
|
.choice-card:has(:checked)::after {
|
|
4172
4528
|
background: HighlightText;
|
|
4173
4529
|
}
|
|
@@ -4183,19 +4539,19 @@ a.avatar[aria-disabled="true"] {
|
|
|
4183
4539
|
* custom-select.css can be opted out independently. Add .native to a specific
|
|
4184
4540
|
* select to opt out of the enhancement.
|
|
4185
4541
|
*/
|
|
4186
|
-
.select:
|
|
4542
|
+
.select:not([multiple], [size]:not([size="1"])) {
|
|
4187
4543
|
appearance: none;
|
|
4188
|
-
padding-inline-end: calc(var(--space-
|
|
4189
|
-
background-image: var(--icon-chevron
|
|
4190
|
-
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);
|
|
4191
4547
|
background-size: 1rem auto;
|
|
4192
4548
|
background-repeat: no-repeat;
|
|
4193
4549
|
}
|
|
4194
4550
|
|
|
4195
4551
|
/* The chevron sits on the inline-end side; flip it in RTL so it follows
|
|
4196
4552
|
the padding-inline-end space reserved for it. */
|
|
4197
|
-
.select:
|
|
4198
|
-
background-position: center left var(--space-
|
|
4553
|
+
.select:not([multiple], [size]:not([size="1"])):dir(rtl) {
|
|
4554
|
+
background-position: center left var(--space-30);
|
|
4199
4555
|
}
|
|
4200
4556
|
|
|
4201
4557
|
/* Both forced-colors and prefers-contrast hand the arrow back to the
|
|
@@ -4203,10 +4559,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
4203
4559
|
simpler than maintaining contrast variants, and native rendering already
|
|
4204
4560
|
matches whatever contrast mode the user picked. */
|
|
4205
4561
|
@media (forced-colors: active), (prefers-contrast: more) {
|
|
4206
|
-
.select:
|
|
4562
|
+
.select:not([multiple], [size]:not([size="1"])) {
|
|
4207
4563
|
appearance: auto;
|
|
4208
4564
|
background-image: none;
|
|
4209
|
-
padding-inline-end: var(--space-
|
|
4565
|
+
padding-inline-end: var(--space-30);
|
|
4210
4566
|
}
|
|
4211
4567
|
}
|
|
4212
4568
|
|
|
@@ -4219,6 +4575,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
4219
4575
|
*
|
|
4220
4576
|
* No layout or interaction contract may depend on appearance: base-select.
|
|
4221
4577
|
* Arbitrary menu and popover behavior belongs elsewhere.
|
|
4578
|
+
*
|
|
4579
|
+
* Internal:
|
|
4580
|
+
* --control-border
|
|
4581
|
+
* --control-bg
|
|
4222
4582
|
*/
|
|
4223
4583
|
@media (forced-colors: none) and (prefers-contrast: no-preference) and (hover: hover) and (
|
|
4224
4584
|
pointer: fine
|
|
@@ -4231,7 +4591,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4231
4591
|
&::picker(select) {
|
|
4232
4592
|
appearance: base-select;
|
|
4233
4593
|
max-height: min(24rem, calc(var(--viewport-block) * 0.7));
|
|
4234
|
-
margin-block: var(--space-
|
|
4594
|
+
margin-block: var(--space-20);
|
|
4235
4595
|
padding: 0;
|
|
4236
4596
|
border: var(--border-width) solid var(--border);
|
|
4237
4597
|
border-radius: var(--radius);
|
|
@@ -4253,8 +4613,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4253
4613
|
|
|
4254
4614
|
& option {
|
|
4255
4615
|
position: relative;
|
|
4256
|
-
padding-block: var(--space-
|
|
4257
|
-
padding-inline: var(--space-
|
|
4616
|
+
padding-block: var(--space-20);
|
|
4617
|
+
padding-inline: var(--space-30);
|
|
4258
4618
|
white-space: normal;
|
|
4259
4619
|
transition-property: color, background-color;
|
|
4260
4620
|
transition-duration: var(--duration);
|
|
@@ -4282,14 +4642,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4282
4642
|
inline-size: 1rem;
|
|
4283
4643
|
block-size: 1rem;
|
|
4284
4644
|
background-color: currentColor;
|
|
4285
|
-
-webkit-mask
|
|
4286
|
-
mask
|
|
4287
|
-
-webkit-mask-repeat: no-repeat;
|
|
4288
|
-
mask-repeat: no-repeat;
|
|
4289
|
-
-webkit-mask-size: contain;
|
|
4290
|
-
mask-size: contain;
|
|
4291
|
-
-webkit-mask-position: center;
|
|
4292
|
-
mask-position: center;
|
|
4645
|
+
-webkit-mask: var(--icon-check) center / contain no-repeat;
|
|
4646
|
+
mask: var(--icon-check) center / contain no-repeat;
|
|
4293
4647
|
}
|
|
4294
4648
|
}
|
|
4295
4649
|
|
|
@@ -4305,31 +4659,21 @@ a.avatar[aria-disabled="true"] {
|
|
|
4305
4659
|
}
|
|
4306
4660
|
|
|
4307
4661
|
/*
|
|
4308
|
-
* Native-only control polish: file and
|
|
4309
|
-
* styling, so the framework stays out of
|
|
4310
|
-
* and 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.
|
|
4665
|
+
*
|
|
4666
|
+
* Range has its own custom-styled file (range.css) so the focus ring can
|
|
4667
|
+
* target the thumb pseudo-element rather than the rectangular CSS box.
|
|
4311
4668
|
*/
|
|
4312
|
-
/* Intent boundary — inherited intents do not tint this component.
|
|
4313
|
-
Local intent classes still win because :where() has zero specificity. */
|
|
4314
|
-
:where(.range) {
|
|
4315
|
-
/* @sync intent-boundary */
|
|
4316
|
-
--intent: initial;
|
|
4317
|
-
--intent-fg: initial;
|
|
4318
|
-
--ui-bg: initial;
|
|
4319
|
-
--ui-fg: initial;
|
|
4320
|
-
--ui-border: initial;
|
|
4321
|
-
--ui-hover-bg: initial;
|
|
4322
|
-
/* @sync intent-boundary */
|
|
4323
|
-
}
|
|
4324
|
-
|
|
4325
4669
|
.file {
|
|
4326
4670
|
color: var(--text-muted);
|
|
4327
4671
|
}
|
|
4328
4672
|
|
|
4329
4673
|
.file::file-selector-button {
|
|
4330
|
-
margin-inline-end: var(--space-
|
|
4331
|
-
padding-block: var(--space-
|
|
4332
|
-
padding-inline: var(--space-
|
|
4674
|
+
margin-inline-end: var(--space-30);
|
|
4675
|
+
padding-block: var(--space-10);
|
|
4676
|
+
padding-inline: var(--space-20);
|
|
4333
4677
|
border: var(--border-width) solid var(--border);
|
|
4334
4678
|
border-radius: var(--radius-sm);
|
|
4335
4679
|
background: var(--surface-subtle);
|
|
@@ -4348,28 +4692,157 @@ a.avatar[aria-disabled="true"] {
|
|
|
4348
4692
|
cursor: not-allowed;
|
|
4349
4693
|
}
|
|
4350
4694
|
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4695
|
+
.file:not(:disabled)::file-selector-button:hover {
|
|
4696
|
+
background: var(--border);
|
|
4697
|
+
}
|
|
4698
|
+
|
|
4699
|
+
.file:focus-visible {
|
|
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;
|
|
4734
|
+
box-shadow: var(--focus-ring-shadow);
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4737
|
+
@media (forced-colors: active) {
|
|
4738
|
+
.color {
|
|
4739
|
+
border-color: ButtonText;
|
|
4740
|
+
background: ButtonFace;
|
|
4741
|
+
forced-color-adjust: none;
|
|
4354
4742
|
}
|
|
4355
4743
|
}
|
|
4356
4744
|
|
|
4745
|
+
/*
|
|
4746
|
+
* Range slider — custom-styled so the focus ring always targets the
|
|
4747
|
+
* user-manipulated surface (the thumb) rather than the CSS box.
|
|
4748
|
+
*
|
|
4749
|
+
* appearance: none gives us full control over track and thumb; without it,
|
|
4750
|
+
* pseudo-element rules for ::-webkit-slider-thumb and ::-moz-range-thumb
|
|
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
|
|
4758
|
+
*/
|
|
4759
|
+
|
|
4760
|
+
/* Intent boundary — inherited intents do not tint this component.
|
|
4761
|
+
Local intent classes still win because :where() has zero specificity. */
|
|
4762
|
+
:where(.range) {
|
|
4763
|
+
--intent: initial;
|
|
4764
|
+
}
|
|
4765
|
+
|
|
4357
4766
|
.range {
|
|
4767
|
+
--range-thumb-size: 1.25rem;
|
|
4768
|
+
--range-track-height: 0.25rem;
|
|
4769
|
+
--range-thumb-bg: var(--intent, var(--primary));
|
|
4770
|
+
--range-track-bg: var(--border);
|
|
4771
|
+
|
|
4772
|
+
appearance: none;
|
|
4358
4773
|
inline-size: 100%;
|
|
4359
|
-
|
|
4774
|
+
block-size: var(--range-thumb-size);
|
|
4775
|
+
background: transparent;
|
|
4776
|
+
cursor: pointer;
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
.range::-webkit-slider-runnable-track {
|
|
4780
|
+
block-size: var(--range-track-height);
|
|
4781
|
+
border-radius: var(--radius-full);
|
|
4782
|
+
background: var(--range-track-bg);
|
|
4783
|
+
}
|
|
4784
|
+
|
|
4785
|
+
.range::-moz-range-track {
|
|
4786
|
+
block-size: var(--range-track-height);
|
|
4787
|
+
border-radius: var(--radius-full);
|
|
4788
|
+
background: var(--range-track-bg);
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4791
|
+
.range::-webkit-slider-thumb {
|
|
4792
|
+
appearance: none;
|
|
4793
|
+
inline-size: var(--range-thumb-size);
|
|
4794
|
+
block-size: var(--range-thumb-size);
|
|
4795
|
+
border-radius: var(--radius-full);
|
|
4796
|
+
border: 0;
|
|
4797
|
+
background: var(--range-thumb-bg);
|
|
4798
|
+
margin-block-start: calc((var(--range-track-height) - var(--range-thumb-size)) / 2);
|
|
4799
|
+
cursor: inherit;
|
|
4800
|
+
transition: box-shadow var(--duration);
|
|
4801
|
+
}
|
|
4802
|
+
|
|
4803
|
+
.range::-moz-range-thumb {
|
|
4804
|
+
inline-size: var(--range-thumb-size);
|
|
4805
|
+
block-size: var(--range-thumb-size);
|
|
4806
|
+
border-radius: var(--radius-full);
|
|
4807
|
+
border: 0;
|
|
4808
|
+
background: var(--range-thumb-bg);
|
|
4809
|
+
cursor: inherit;
|
|
4810
|
+
transition: box-shadow var(--duration);
|
|
4360
4811
|
}
|
|
4361
4812
|
|
|
4362
|
-
.file:focus-visible,
|
|
4363
4813
|
.range:focus-visible {
|
|
4364
|
-
outline:
|
|
4365
|
-
|
|
4366
|
-
|
|
4814
|
+
outline: 2px solid transparent;
|
|
4815
|
+
}
|
|
4816
|
+
|
|
4817
|
+
/*
|
|
4818
|
+
* The 1px surface-coloured separator keeps the ring visually distinct from
|
|
4819
|
+
* the small, coloured thumb — unlike a full-size input where the same gap
|
|
4820
|
+
* would read as an unwanted void.
|
|
4821
|
+
*/
|
|
4822
|
+
.range:focus-visible::-webkit-slider-thumb {
|
|
4823
|
+
box-shadow:
|
|
4824
|
+
0 0 0 1px var(--surface),
|
|
4825
|
+
0 0 0 calc(1px + var(--focus-ring-width)) var(--focus-ring);
|
|
4826
|
+
}
|
|
4827
|
+
|
|
4828
|
+
.range:focus-visible::-moz-range-thumb {
|
|
4829
|
+
box-shadow:
|
|
4830
|
+
0 0 0 1px var(--surface),
|
|
4831
|
+
0 0 0 calc(1px + var(--focus-ring-width)) var(--focus-ring);
|
|
4832
|
+
}
|
|
4833
|
+
|
|
4834
|
+
.range:disabled {
|
|
4835
|
+
--range-thumb-bg: var(--state-disabled);
|
|
4836
|
+
cursor: not-allowed;
|
|
4837
|
+
opacity: var(--disabled-opacity);
|
|
4367
4838
|
}
|
|
4368
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. */
|
|
4369
4842
|
@media (forced-colors: active) {
|
|
4370
|
-
.
|
|
4371
|
-
|
|
4372
|
-
|
|
4843
|
+
.range {
|
|
4844
|
+
--range-thumb-bg: ButtonText;
|
|
4845
|
+
--range-track-bg: Canvas;
|
|
4373
4846
|
}
|
|
4374
4847
|
}
|
|
4375
4848
|
|
|
@@ -4378,18 +4851,22 @@ a.avatar[aria-disabled="true"] {
|
|
|
4378
4851
|
*
|
|
4379
4852
|
* The switch remains an input control, not a decorative toggle. Checked, hover,
|
|
4380
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
|
|
4381
4864
|
*/
|
|
4382
4865
|
/* Intent boundary — inherited intents do not tint this component.
|
|
4383
4866
|
Local intent classes still win because :where() has zero specificity. */
|
|
4384
4867
|
:where(.switch) {
|
|
4385
|
-
/* @sync intent-boundary */
|
|
4386
4868
|
--intent: initial;
|
|
4387
4869
|
--intent-fg: initial;
|
|
4388
|
-
--ui-bg: initial;
|
|
4389
|
-
--ui-fg: initial;
|
|
4390
|
-
--ui-border: initial;
|
|
4391
|
-
--ui-hover-bg: initial;
|
|
4392
|
-
/* @sync intent-boundary */
|
|
4393
4870
|
}
|
|
4394
4871
|
|
|
4395
4872
|
.switch {
|
|
@@ -4437,10 +4914,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
4437
4914
|
box-shadow var(--duration);
|
|
4438
4915
|
}
|
|
4439
4916
|
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
--switch-border: var(--intent, var(--primary));
|
|
4443
|
-
}
|
|
4917
|
+
.switch:hover:not(:disabled) {
|
|
4918
|
+
--switch-border: var(--intent, var(--primary));
|
|
4444
4919
|
}
|
|
4445
4920
|
|
|
4446
4921
|
.switch:checked {
|
|
@@ -4461,15 +4936,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
4461
4936
|
|
|
4462
4937
|
.switch:focus-visible {
|
|
4463
4938
|
--switch-border: var(--focus);
|
|
4464
|
-
outline:
|
|
4465
|
-
outline-offset: var(--focus-outline-offset);
|
|
4939
|
+
outline: 2px solid transparent;
|
|
4466
4940
|
box-shadow: var(--focus-ring-shadow);
|
|
4467
4941
|
}
|
|
4468
4942
|
|
|
4469
4943
|
.switch:disabled {
|
|
4470
4944
|
--switch-bg: var(--surface-subtle);
|
|
4471
4945
|
--switch-border: var(--border);
|
|
4472
|
-
--switch-knob: var(--
|
|
4946
|
+
--switch-knob: var(--state-disabled);
|
|
4473
4947
|
opacity: 1;
|
|
4474
4948
|
cursor: not-allowed;
|
|
4475
4949
|
}
|
|
@@ -4506,6 +4980,17 @@ a.avatar[aria-disabled="true"] {
|
|
|
4506
4980
|
*
|
|
4507
4981
|
* Imported by default through forms/index.css. Custom builds may omit this
|
|
4508
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
|
|
4509
4994
|
*/
|
|
4510
4995
|
|
|
4511
4996
|
.field.danger {
|
|
@@ -4528,17 +5013,43 @@ a.avatar[aria-disabled="true"] {
|
|
|
4528
5013
|
--form-invalid-border: var(--danger);
|
|
4529
5014
|
}
|
|
4530
5015
|
|
|
4531
|
-
/* Override checked/indeterminate
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
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 {
|
|
4535
5024
|
--choice-bg: var(--danger);
|
|
4536
5025
|
}
|
|
4537
5026
|
|
|
4538
|
-
.switch[aria-invalid="true"]
|
|
5027
|
+
.switch:checked[aria-invalid="true"],
|
|
5028
|
+
.needs-validation.was-validated .switch:checked:invalid {
|
|
4539
5029
|
--switch-bg: var(--danger);
|
|
4540
5030
|
}
|
|
4541
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
|
+
|
|
5043
|
+
/* Choice cards hide their native input, so invalid state must surface on the
|
|
5044
|
+
card itself. Sets the same --form-invalid-border hook the card's border
|
|
5045
|
+
consumes. */
|
|
5046
|
+
@supports selector(:has(*)) {
|
|
5047
|
+
.choice-card:has([aria-invalid="true"]),
|
|
5048
|
+
.needs-validation.was-validated .choice-card:has(:invalid) {
|
|
5049
|
+
--form-invalid-border: var(--danger);
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
|
|
4542
5053
|
@supports selector(:user-invalid) {
|
|
4543
5054
|
.needs-validation :is(.input, .textarea, .select, .check, .radio, .switch):user-invalid {
|
|
4544
5055
|
--form-invalid-border: var(--danger);
|
|
@@ -4549,6 +5060,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
4549
5060
|
.needs-validation .field:has(:user-invalid) > .field-error {
|
|
4550
5061
|
display: block;
|
|
4551
5062
|
}
|
|
5063
|
+
|
|
5064
|
+
.needs-validation .choice-card:has(:user-invalid) {
|
|
5065
|
+
--form-invalid-border: var(--danger);
|
|
5066
|
+
}
|
|
4552
5067
|
}
|
|
4553
5068
|
|
|
4554
5069
|
/*
|
|
@@ -4557,13 +5072,24 @@ a.avatar[aria-disabled="true"] {
|
|
|
4557
5072
|
* A flex cluster of buttons/actions that closes a form. The sticky variant
|
|
4558
5073
|
* docks to the viewport bottom with a translucent blur where supported; at
|
|
4559
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)
|
|
4560
5085
|
*/
|
|
4561
5086
|
.form-actions {
|
|
4562
5087
|
display: flex;
|
|
4563
5088
|
flex-wrap: wrap;
|
|
4564
5089
|
gap: var(--gap);
|
|
4565
|
-
align-items: center;
|
|
4566
|
-
|
|
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));
|
|
4567
5093
|
}
|
|
4568
5094
|
|
|
4569
5095
|
/* Opt-in sticky variant. When the bar is not stuck (at rest) the translucent
|
|
@@ -4577,7 +5103,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4577
5103
|
isolation: isolate;
|
|
4578
5104
|
|
|
4579
5105
|
margin-inline: calc(var(--form-actions-sticky-inline-offset, 0px) * -1);
|
|
4580
|
-
padding: var(--form-actions-sticky-padding, var(--space-
|
|
5106
|
+
padding: var(--form-actions-sticky-padding, var(--space-30));
|
|
4581
5107
|
|
|
4582
5108
|
border-block-start: var(--border-width) solid var(--border);
|
|
4583
5109
|
background: var(--surface);
|
|
@@ -4627,7 +5153,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4627
5153
|
min-inline-size: 0;
|
|
4628
5154
|
}
|
|
4629
5155
|
|
|
4630
|
-
.nowrap {
|
|
5156
|
+
.text-nowrap {
|
|
4631
5157
|
white-space: nowrap;
|
|
4632
5158
|
}
|
|
4633
5159
|
|
|
@@ -4722,15 +5248,13 @@ a.avatar[aria-disabled="true"] {
|
|
|
4722
5248
|
text-decoration: none;
|
|
4723
5249
|
}
|
|
4724
5250
|
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
color: var(--text);
|
|
4728
|
-
}
|
|
5251
|
+
.link-muted:hover {
|
|
5252
|
+
color: var(--text);
|
|
4729
5253
|
}
|
|
4730
5254
|
|
|
4731
5255
|
/* Spacing step helpers — semantic scale (sm / default / lg).
|
|
4732
5256
|
Names map to logical properties for writing-direction safety. The default
|
|
4733
|
-
step uses --space-
|
|
5257
|
+
step uses --space-40, which matches the framework's built-in padding on
|
|
4734
5258
|
cards, dialogs, and form-actions. */
|
|
4735
5259
|
|
|
4736
5260
|
/* Gap */
|
|
@@ -4740,22 +5264,22 @@ a.avatar[aria-disabled="true"] {
|
|
|
4740
5264
|
|
|
4741
5265
|
/* Padding — block (vertical) */
|
|
4742
5266
|
.py {
|
|
4743
|
-
padding-block: var(--
|
|
5267
|
+
padding-block: var(--density-space);
|
|
4744
5268
|
}
|
|
4745
5269
|
|
|
4746
5270
|
/* Padding — inline (horizontal) */
|
|
4747
5271
|
.px {
|
|
4748
|
-
padding-inline: var(--
|
|
5272
|
+
padding-inline: var(--density-space);
|
|
4749
5273
|
}
|
|
4750
5274
|
|
|
4751
5275
|
/* Margin — block-start */
|
|
4752
5276
|
.mbs {
|
|
4753
|
-
margin-block-start: var(--
|
|
5277
|
+
margin-block-start: var(--density-space);
|
|
4754
5278
|
}
|
|
4755
5279
|
|
|
4756
5280
|
/* Margin — block-end */
|
|
4757
5281
|
.mbe {
|
|
4758
|
-
margin-block-end: var(--
|
|
5282
|
+
margin-block-end: var(--density-space);
|
|
4759
5283
|
}
|
|
4760
5284
|
|
|
4761
5285
|
/* Joined controls are a component, but must load after the controls it groups. */
|
|
@@ -4768,6 +5292,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
4768
5292
|
* Cascade note: .join writes border-radius longhands on its direct children.
|
|
4769
5293
|
* Keep this file loaded after control/component styles so those corners win at
|
|
4770
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
|
|
4771
5307
|
*/
|
|
4772
5308
|
.join {
|
|
4773
5309
|
--join-radius: var(--radius);
|
|
@@ -4795,7 +5331,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4795
5331
|
display: inline-flex;
|
|
4796
5332
|
justify-content: center;
|
|
4797
5333
|
min-block-size: var(--control-size);
|
|
4798
|
-
padding-inline: var(--space-
|
|
5334
|
+
padding-inline: var(--space-30);
|
|
4799
5335
|
border: var(--border-width) solid var(--control-border);
|
|
4800
5336
|
border-radius: var(--radius);
|
|
4801
5337
|
background: var(--control-bg);
|
|
@@ -4856,8 +5392,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
4856
5392
|
focus, and unsupported :has() browsers retain the control's normal ring. */
|
|
4857
5393
|
.join:has(> :is(.input, .textarea, .select):focus-visible) {
|
|
4858
5394
|
border-radius: var(--join-radius);
|
|
4859
|
-
outline:
|
|
4860
|
-
outline-offset: var(--focus-outline-offset);
|
|
5395
|
+
outline: 2px solid transparent;
|
|
4861
5396
|
box-shadow: var(--focus-ring-shadow);
|
|
4862
5397
|
}
|
|
4863
5398
|
|
|
@@ -4866,9 +5401,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
4866
5401
|
box-shadow: none;
|
|
4867
5402
|
}
|
|
4868
5403
|
|
|
4869
|
-
/* Focus baseline —
|
|
4870
|
-
|
|
4871
|
-
|
|
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. */
|
|
4872
5412
|
:focus {
|
|
4873
5413
|
outline: var(--focus-outline);
|
|
4874
5414
|
outline-offset: var(--focus-outline-offset);
|
|
@@ -4878,6 +5418,14 @@ a.avatar[aria-disabled="true"] {
|
|
|
4878
5418
|
:focus:not(:focus-visible) {
|
|
4879
5419
|
outline: none;
|
|
4880
5420
|
}
|
|
5421
|
+
|
|
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
|
+
}
|
|
5428
|
+
}
|
|
4881
5429
|
}
|
|
4882
5430
|
|
|
4883
5431
|
@media print {
|
|
@@ -4927,3 +5475,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
4927
5475
|
break-after: avoid;
|
|
4928
5476
|
}
|
|
4929
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
|
+
*/
|