actual-css 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +214 -1
- package/README.md +13 -11
- package/components.json +668 -0
- package/dist/actual.css +166 -37
- package/dist/actual.full.css +1005 -550
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.js +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/accessibility-decisions.md +33 -0
- package/docs/design-notes/browser-support.md +82 -25
- package/docs/design-notes/column-layout.md +3 -3
- package/docs/design-notes/css-anchor.md +75 -21
- package/docs/design-notes/enhancement-contract.md +14 -6
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +6 -1
- package/docs/design-notes/platform-alignment.md +99 -2
- package/docs/design-notes/popover-manual-poc.md +328 -0
- package/docs/design-notes/surface-reparenting.md +131 -0
- package/docs/design-notes/tooltip-coordinate-space.md +132 -0
- package/docs/design-notes/widget-primitives.md +9 -6
- package/docs/pages/components/accordion.md +41 -2
- package/docs/pages/components/alert.md +80 -30
- package/docs/pages/components/app-navigation.md +6 -0
- package/docs/pages/components/avatar.md +6 -6
- package/docs/pages/components/badge.md +101 -16
- package/docs/pages/components/button.md +16 -8
- package/docs/pages/components/card.md +10 -10
- package/docs/pages/components/dialog.md +16 -6
- package/docs/pages/components/fab.md +4 -3
- package/docs/pages/components/list.md +42 -4
- package/docs/pages/components/navbar.md +17 -1
- package/docs/pages/components/pagination.md +2 -2
- package/docs/pages/components/steps.md +19 -0
- package/docs/pages/components/table.md +3 -2
- package/docs/pages/enhancements/flyout.md +144 -24
- package/docs/pages/enhancements/overview.md +25 -0
- package/docs/pages/enhancements/status-bar.md +20 -0
- package/docs/pages/enhancements/tabs.md +45 -37
- package/docs/pages/enhancements/tooltip.md +40 -2
- package/docs/pages/examples/overview.md +38 -5
- package/docs/pages/forms/choice-cards.md +6 -2
- package/docs/pages/forms/combobox.md +1 -1
- package/docs/pages/forms/floating-field.md +4 -3
- package/docs/pages/forms/joined-controls.md +22 -0
- package/docs/pages/forms/otp.md +2 -1
- package/docs/pages/forms/overview.md +1 -1
- package/docs/pages/forms/states-and-sizes.md +16 -18
- package/docs/pages/forms/sticky-actions.md +2 -2
- package/docs/pages/forms/switch.md +1 -1
- package/docs/pages/foundations/philosophy.md +67 -22
- package/docs/pages/foundations/theming.md +10 -2
- package/docs/pages/foundations/tokens.md +118 -21
- package/docs/pages/foundations/typography.md +35 -14
- package/docs/pages/guides/introduction.md +11 -9
- package/docs/pages/guides/progressive-enhancement.md +136 -49
- package/docs/pages/guides/tailwind.md +3 -2
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +11 -1
- package/docs/pages/layout/scroll-snap.md +3 -3
- package/docs/pages/layout/stack.md +17 -9
- package/docs/pages/patterns/icon-input.md +4 -2
- package/docs/pages/patterns/icon-slot.md +2 -0
- package/docs/pages/patterns/intent-nesting.md +1 -1
- package/docs/pages/utilities/overview.md +23 -2
- package/docs/pages/utilities/sizing-wrapping.md +1 -1
- package/docs/pages/utilities/spacing.md +10 -8
- package/llms.txt +2 -1
- package/package.json +19 -8
- package/{scripts/reserved-classes.json → reserved-classes.json} +4 -6
- package/src/css/components/accordion.css +42 -3
- package/src/css/components/alert.css +25 -13
- package/src/css/components/app-nav.css +18 -8
- package/src/css/components/avatar.css +13 -1
- package/src/css/components/badge.css +78 -12
- package/src/css/components/breadcrumb.css +2 -3
- package/src/css/components/button.css +9 -16
- package/src/css/components/card.css +9 -2
- package/src/css/components/chat.css +1 -0
- package/src/css/components/data-list.css +1 -1
- package/src/css/components/drawer.css +79 -25
- package/src/css/components/fab.css +0 -6
- package/src/css/components/flyout.css +46 -63
- package/src/css/components/index.css +0 -1
- package/src/css/components/indicator.css +21 -0
- package/src/css/components/join.css +7 -0
- package/src/css/components/key.css +10 -0
- package/src/css/components/list.css +40 -4
- package/src/css/components/menu.css +18 -11
- package/src/css/components/modal.css +48 -33
- package/src/css/components/navbar.css +37 -4
- package/src/css/components/pagination.css +1 -0
- package/src/css/components/spinner.css +6 -6
- package/src/css/components/status-bar.css +0 -5
- package/src/css/components/steps.css +45 -59
- package/src/css/components/tab.css +29 -9
- package/src/css/components/table.css +5 -5
- package/src/css/components/tooltip.css +45 -13
- package/src/css/core/base.css +23 -1
- package/src/css/core/intents.css +17 -6
- package/src/css/core/theme.css +27 -0
- package/src/css/core/tokens.css +35 -15
- package/src/css/core/variants.css +64 -15
- package/src/css/forms/choice-card.css +4 -4
- package/src/css/forms/choice.css +6 -3
- package/src/css/forms/control.css +2 -1
- package/src/css/forms/floating-field.css +3 -3
- package/src/css/forms/form.css +4 -3
- package/src/css/forms/input-icon.css +20 -14
- package/src/css/forms/native.css +6 -1
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +2 -0
- package/src/css/forms/select.css +29 -9
- package/src/css/forms/switch.css +19 -5
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/grid.css +50 -76
- package/src/css/layout/scroll-snap.css +11 -7
- package/src/css/layout/scroller.css +3 -4
- package/src/css/layout/stack.css +2 -2
- package/src/css/typography/fluid.css +4 -3
- package/src/css/typography/prose.css +6 -5
- package/src/css/utilities/base.css +2 -1
- package/src/css/utilities/extra.css +20 -17
- package/src/js/context-menu.js +27 -26
- package/src/js/dialog.js +2 -1
- package/src/js/dismiss.js +5 -0
- package/src/js/enhance.js +34 -2
- package/src/js/enhancement-loader.js +36 -43
- package/src/js/events.js +0 -3
- package/src/js/filter.js +0 -1
- package/src/js/floating-space.js +42 -0
- package/src/js/flyout.js +9 -44
- package/src/js/focus-group.js +7 -0
- package/src/js/focus.js +6 -0
- package/src/js/menu.js +10 -1
- package/src/js/selectors.js +5 -9
- package/src/js/status.js +44 -7
- package/src/js/surface.js +106 -132
- package/src/js/tab.js +6 -1
- package/src/js/tooltip.js +152 -53
- package/src/js/validation.js +1 -1
- package/src/css/components/surface.css +0 -49
- package/src/js/floating.js +0 -432
package/dist/actual.full.css
CHANGED
|
@@ -74,9 +74,9 @@ select {
|
|
|
74
74
|
--line-height: 1.5;
|
|
75
75
|
--line-height-tight: 1.25;
|
|
76
76
|
--line-height-relaxed: 1.75;
|
|
77
|
-
--font-weight: 400;
|
|
77
|
+
--font-weight-normal: 400;
|
|
78
78
|
--font-weight-medium: 500;
|
|
79
|
-
--font-weight-
|
|
79
|
+
--font-weight-semibold: 600;
|
|
80
80
|
--font-weight-bold: 700;
|
|
81
81
|
|
|
82
82
|
/* Typography scale */
|
|
@@ -91,7 +91,6 @@ select {
|
|
|
91
91
|
applications, and no future framework step can collide with them. */
|
|
92
92
|
--gap: 0.75rem;
|
|
93
93
|
--density-space: var(--space-40);
|
|
94
|
-
--density-compact-size: 1.5rem;
|
|
95
94
|
--space-10: 0.25rem;
|
|
96
95
|
--space-20: 0.5rem;
|
|
97
96
|
--space-30: 0.75rem;
|
|
@@ -117,7 +116,10 @@ select {
|
|
|
117
116
|
beyond this vocabulary. Toggles that share one transition list at
|
|
118
117
|
--duration-fast (flyout, tooltip) and pure-opacity backdrops stay on
|
|
119
118
|
`ease`: at 100ms the curve has almost no resolving time, and a scrim is
|
|
120
|
-
not traveling anywhere.
|
|
119
|
+
not traveling anywhere. Animation policy: reset.css is the global safety
|
|
120
|
+
net; every meaningful @keyframes is explicitly gated behind
|
|
121
|
+
prefers-reduced-motion: no-preference; ordinary transitions rely on the
|
|
122
|
+
reset alone. */
|
|
121
123
|
--duration: 150ms;
|
|
122
124
|
--duration-fast: 100ms;
|
|
123
125
|
--duration-slow: 200ms;
|
|
@@ -148,20 +150,29 @@ select {
|
|
|
148
150
|
--bar-height: 0.65rem;
|
|
149
151
|
|
|
150
152
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
151
|
-
.
|
|
152
|
-
|
|
153
|
+
.compact/.spacious (variants.css) switch geometry without changing
|
|
154
|
+
--control-font-size. Local .sm/.lg component sizes switch all three.
|
|
153
155
|
|
|
154
156
|
Inline choices (checkbox, radio, switch) are typographic, not field-sized:
|
|
155
157
|
they sit on a line of text, so they follow --control-font-size through
|
|
156
158
|
--choice-control-size instead of --control-size. Being an em value in
|
|
157
159
|
:root is deliberate — an unregistered custom property resolves its units
|
|
158
160
|
at the element that consumes it, so one declaration tracks whatever font
|
|
159
|
-
size the choice actually renders at.
|
|
161
|
+
size the choice actually renders at.
|
|
162
|
+
|
|
163
|
+
--control-pad-x is the opposite kind of token: a resolved geometry length,
|
|
164
|
+
not an em ratio. Every part of one control must see the same horizontal
|
|
165
|
+
distance, so it is stated in the typographic tokens rather than an em
|
|
166
|
+
whose value would change on a descendant with its own font size (the
|
|
167
|
+
input-icon glyph, made larger by --input-icon-size). It mirrors
|
|
168
|
+
--control-font-size and is rebound beside it at the .sm/.lg boundaries
|
|
169
|
+
(variants.css); declaring it only in :root would freeze the md value,
|
|
170
|
+
because a var() inside a custom property resolves where it is declared. */
|
|
160
171
|
--control-size-sm: 2rem;
|
|
161
172
|
--control-size-md: 2.375rem;
|
|
162
173
|
--control-size-lg: 2.75rem;
|
|
163
174
|
--control-size: var(--control-size-md);
|
|
164
|
-
--control-pad-x:
|
|
175
|
+
--control-pad-x: var(--font-size-md);
|
|
165
176
|
--control-font-size: var(--font-size-md);
|
|
166
177
|
--choice-control-size: 1.125em;
|
|
167
178
|
--disabled-opacity: 0.65;
|
|
@@ -194,7 +205,10 @@ select {
|
|
|
194
205
|
--soft-hover-alpha: 12%;
|
|
195
206
|
/* Soft foreground: the share of raw intent in soft text. 100% keeps the
|
|
196
207
|
pure intent color; lower values rebate it toward --text, which vivid or
|
|
197
|
-
light intent palettes need to stay legible on their own soft surface.
|
|
208
|
+
light intent palettes need to stay legible on their own soft surface.
|
|
209
|
+
The default palette ships per-role --*-soft-fg hooks calibrated against
|
|
210
|
+
the hovered soft fill instead (theme.css); this mix is the fallback a
|
|
211
|
+
theme without hooks falls back to. */
|
|
198
212
|
--soft-fg-mix: 100%;
|
|
199
213
|
|
|
200
214
|
/* Shared icon assets. Override these URLs when a strict CSP disallows data:. */
|
|
@@ -210,13 +224,14 @@ select {
|
|
|
210
224
|
--viewport-block: 100vh;
|
|
211
225
|
--viewport-inline: 100vw;
|
|
212
226
|
|
|
213
|
-
/* Z-index scale —
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
/* Z-index scale — orders document-layer components only. Native top-layer
|
|
228
|
+
elements, dialogs and popovers alike, do not participate in this ordering:
|
|
229
|
+
they always paint above the document, and their order among themselves is
|
|
230
|
+
their top-layer promotion order. No z-index value here can reach them.
|
|
231
|
+
Actual CSS reserves multiples of 10 for framework layers; applications may
|
|
232
|
+
use intermediate values for local layering. */
|
|
217
233
|
--z-sticky: 10;
|
|
218
234
|
--z-menu: 20;
|
|
219
|
-
--z-tooltip: 50;
|
|
220
235
|
--z-status: 60;
|
|
221
236
|
}
|
|
222
237
|
|
|
@@ -251,7 +266,12 @@ select {
|
|
|
251
266
|
--shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent);
|
|
252
267
|
}
|
|
253
268
|
|
|
254
|
-
|
|
269
|
+
/* Theme-derived like the shadows above, so it is declared on the same
|
|
270
|
+
boundaries: an alias resolved only on :root is inherited already composed,
|
|
271
|
+
and a [data-theme] island overriding --backdrop-color or
|
|
272
|
+
--backdrop-opacity would not change the scrim of a dialog inside it. */
|
|
273
|
+
:root,
|
|
274
|
+
[data-theme] {
|
|
255
275
|
--backdrop-fill: color-mix(
|
|
256
276
|
in oklch,
|
|
257
277
|
var(--backdrop-color) calc(var(--backdrop-opacity) * 100%),
|
|
@@ -363,6 +383,33 @@ select {
|
|
|
363
383
|
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
364
384
|
}
|
|
365
385
|
|
|
386
|
+
/* Soft foreground hooks — the soft ink is the intent rebated toward the local
|
|
387
|
+
text so it keeps 4.5:1 against the soft hover fill (the resting fill carries
|
|
388
|
+
the extra margin; variants.css soft recipe). Deliberately scoped to the
|
|
389
|
+
default theme: a custom property bakes its var() references where it is
|
|
390
|
+
declared, so these resolve against root/light/dark tokens on every boundary.
|
|
391
|
+
Presets and custom islands keep their --soft-fg-mix / raw-intent ink unless
|
|
392
|
+
they declare hooks themselves — :where() keeps this reset from outranking a
|
|
393
|
+
theme's own declaration (e.g. brutalist's 45% soft-fg-mix). */
|
|
394
|
+
:where([data-theme]:not([data-theme="light"]):not([data-theme="dark"])) {
|
|
395
|
+
--primary-soft-fg: initial;
|
|
396
|
+
--secondary-soft-fg: initial;
|
|
397
|
+
--success-soft-fg: initial;
|
|
398
|
+
--warning-soft-fg: initial;
|
|
399
|
+
--danger-soft-fg: initial;
|
|
400
|
+
--neutral-soft-fg: initial;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
:root,
|
|
404
|
+
[data-theme="light"],
|
|
405
|
+
[data-theme="dark"] {
|
|
406
|
+
--secondary-soft-fg: color-mix(in oklab, var(--text) 10%, var(--secondary) 90%);
|
|
407
|
+
--success-soft-fg: color-mix(in oklab, var(--text) 15%, var(--success) 85%);
|
|
408
|
+
--warning-soft-fg: color-mix(in oklab, var(--text) 25%, var(--warning) 75%);
|
|
409
|
+
--danger-soft-fg: color-mix(in oklab, var(--text) 15%, var(--danger) 85%);
|
|
410
|
+
--neutral-soft-fg: color-mix(in oklab, var(--text) 20%, var(--neutral) 80%);
|
|
411
|
+
}
|
|
412
|
+
|
|
366
413
|
/* Native platform controls (checkbox, radio, range, progress) that are not
|
|
367
414
|
restyled by a component class still follow the theme accent. Declared on
|
|
368
415
|
every island: accent-color inherits as a computed value, so islands must
|
|
@@ -471,6 +518,7 @@ select {
|
|
|
471
518
|
*
|
|
472
519
|
* Public hooks:
|
|
473
520
|
* --link
|
|
521
|
+
* --hr-space
|
|
474
522
|
*/
|
|
475
523
|
|
|
476
524
|
body {
|
|
@@ -512,10 +560,23 @@ hgroup > * {
|
|
|
512
560
|
margin-block: 0;
|
|
513
561
|
}
|
|
514
562
|
|
|
563
|
+
/* Section break.
|
|
564
|
+
|
|
565
|
+
margin-inline: 0 overrides the UA's `margin: 0.5em auto`. In block flow the
|
|
566
|
+
auto margins resolve to zero and nothing changes, but as a flex or grid item
|
|
567
|
+
an inline auto margin replaces the stretch — an <hr> in a .stack computed to
|
|
568
|
+
width 0 and vanished. .stack deliberately leaves the inline axis free, so
|
|
569
|
+
the reset belongs here.
|
|
570
|
+
|
|
571
|
+
--hr-space tunes the block rhythm on the instance: a rule closing a page
|
|
572
|
+
section and a rule separating two rows in a panel are the same element at
|
|
573
|
+
very different scales. It applies to an hr in document flow; inside .stack
|
|
574
|
+
or .prose the parent owns the spacing between siblings and wins. */
|
|
515
575
|
hr {
|
|
516
576
|
border: 0;
|
|
517
577
|
border-block-start: var(--border-width) solid var(--border);
|
|
518
|
-
margin-block: var(--space-60);
|
|
578
|
+
margin-block: var(--hr-space, var(--space-60));
|
|
579
|
+
margin-inline: 0;
|
|
519
580
|
}
|
|
520
581
|
|
|
521
582
|
code,
|
|
@@ -525,6 +586,14 @@ pre {
|
|
|
525
586
|
font-family: var(--font-mono);
|
|
526
587
|
}
|
|
527
588
|
|
|
589
|
+
/* Overflow containment only. One unbreakable line — a URL, a minified token, a
|
|
590
|
+
wide ASCII table — otherwise widens the whole page, and an author cannot see
|
|
591
|
+
it coming from the markup. Background, padding and radius stay out: those
|
|
592
|
+
belong to .prose or to an application code-block component. */
|
|
593
|
+
pre {
|
|
594
|
+
overflow-x: auto;
|
|
595
|
+
}
|
|
596
|
+
|
|
528
597
|
small {
|
|
529
598
|
font-size: 0.875em;
|
|
530
599
|
}
|
|
@@ -536,36 +605,47 @@ small {
|
|
|
536
605
|
|
|
537
606
|
/*
|
|
538
607
|
* Intent classes do not style elements directly.
|
|
539
|
-
* They only expose --intent
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
608
|
+
* They only expose --intent, --intent-fg, and the optional per-intent soft
|
|
609
|
+
* foreground --intent-soft-fg, relayed from the per-role --*-soft-fg hooks.
|
|
610
|
+
* The default theme declares those hooks for its soft ink (see theme.css);
|
|
611
|
+
* a theme without them lets the relay resolve to an invalid value, so the soft
|
|
612
|
+
* recipe falls back to its --soft-fg-mix derivation and a theme opts in one
|
|
613
|
+
* role at a time (docs/pages/foundations/tokens.md). Components and variants
|
|
614
|
+
* decide how to consume them. Each component that consumes --intent,
|
|
615
|
+
* --intent-soft-fg or --ui-* opens a local intent boundary in a :where() block
|
|
616
|
+
* that resets exactly the tokens it consumes to initial. Parent intents do not
|
|
617
|
+
* leak into nested components. Local intent classes (.primary, .danger, etc.)
|
|
618
|
+
* still apply because they have higher specificity than :where().
|
|
545
619
|
*/
|
|
546
620
|
.primary {
|
|
547
621
|
--intent: var(--primary);
|
|
548
622
|
--intent-fg: var(--primary-fg);
|
|
623
|
+
--intent-soft-fg: var(--primary-soft-fg);
|
|
549
624
|
}
|
|
550
625
|
.secondary {
|
|
551
626
|
--intent: var(--secondary);
|
|
552
627
|
--intent-fg: var(--secondary-fg);
|
|
628
|
+
--intent-soft-fg: var(--secondary-soft-fg);
|
|
553
629
|
}
|
|
554
630
|
.success {
|
|
555
631
|
--intent: var(--success);
|
|
556
632
|
--intent-fg: var(--success-fg);
|
|
633
|
+
--intent-soft-fg: var(--success-soft-fg);
|
|
557
634
|
}
|
|
558
635
|
.warning {
|
|
559
636
|
--intent: var(--warning);
|
|
560
637
|
--intent-fg: var(--warning-fg);
|
|
638
|
+
--intent-soft-fg: var(--warning-soft-fg);
|
|
561
639
|
}
|
|
562
640
|
.danger {
|
|
563
641
|
--intent: var(--danger);
|
|
564
642
|
--intent-fg: var(--danger-fg);
|
|
643
|
+
--intent-soft-fg: var(--danger-soft-fg);
|
|
565
644
|
}
|
|
566
645
|
.neutral {
|
|
567
646
|
--intent: var(--neutral);
|
|
568
647
|
--intent-fg: var(--neutral-fg);
|
|
648
|
+
--intent-soft-fg: var(--neutral-soft-fg);
|
|
569
649
|
}
|
|
570
650
|
|
|
571
651
|
/* Paint an inherited or local intent on a simple foreground element without
|
|
@@ -591,16 +671,15 @@ small {
|
|
|
591
671
|
* components/badge.css — .badge default soft behind explicit --ui-*
|
|
592
672
|
* components/alert.css — .alert default soft behind explicit --ui-*
|
|
593
673
|
*
|
|
594
|
-
* .
|
|
595
|
-
* Density never changes typography
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
* three-step scale.
|
|
674
|
+
* .compact/.spacious set inherited density tokens: spacing rhythm and geometry
|
|
675
|
+
* only. Density never changes typography or icon size. Controls consume
|
|
676
|
+
* --control-size; non-control components may consume --density-* when the
|
|
677
|
+
* context should affect their local rhythm.
|
|
678
|
+
*
|
|
679
|
+
* .sm/.lg are local size roles, not shared measurements. Shared control
|
|
680
|
+
* participants below map both typography and geometry; components with
|
|
681
|
+
* bespoke geometry (badge, spinner, avatar, rating, key, prose) own those
|
|
682
|
+
* mappings locally. Components without an explicit mapping do not participate.
|
|
604
683
|
*
|
|
605
684
|
* Intent list source of truth: intents.css. Components consume --intent* tokens
|
|
606
685
|
* instead of re-listing intent classes.
|
|
@@ -656,17 +735,64 @@ small {
|
|
|
656
735
|
border-color: var(--ui-border);
|
|
657
736
|
}
|
|
658
737
|
|
|
659
|
-
.
|
|
738
|
+
.compact {
|
|
660
739
|
--gap: var(--space-20);
|
|
661
740
|
--density-space: var(--space-20);
|
|
662
741
|
--control-size: var(--control-size-sm);
|
|
663
|
-
--density-compact-size: 1.25rem;
|
|
664
742
|
}
|
|
665
|
-
|
|
743
|
+
|
|
744
|
+
.spacious {
|
|
666
745
|
--gap: var(--space-50);
|
|
667
746
|
--density-space: var(--space-50);
|
|
668
747
|
--control-size: var(--control-size-lg);
|
|
669
|
-
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
751
|
+
component (field, input-icon, join, pagination, tabs, flyout). A local size
|
|
752
|
+
declaration beats inherited density because it is declared on the sized
|
|
753
|
+
component itself. */
|
|
754
|
+
:where(
|
|
755
|
+
.btn,
|
|
756
|
+
.input,
|
|
757
|
+
.textarea,
|
|
758
|
+
.select,
|
|
759
|
+
.color,
|
|
760
|
+
.file,
|
|
761
|
+
.field,
|
|
762
|
+
.choice,
|
|
763
|
+
.input-icon,
|
|
764
|
+
.otp,
|
|
765
|
+
.join,
|
|
766
|
+
.pagination,
|
|
767
|
+
.tabs,
|
|
768
|
+
.tab,
|
|
769
|
+
.flyout
|
|
770
|
+
).sm {
|
|
771
|
+
--control-size: var(--control-size-sm);
|
|
772
|
+
--control-font-size: var(--font-size-sm);
|
|
773
|
+
--control-pad-x: var(--font-size-sm);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
:where(
|
|
777
|
+
.btn,
|
|
778
|
+
.input,
|
|
779
|
+
.textarea,
|
|
780
|
+
.select,
|
|
781
|
+
.color,
|
|
782
|
+
.file,
|
|
783
|
+
.field,
|
|
784
|
+
.choice,
|
|
785
|
+
.input-icon,
|
|
786
|
+
.otp,
|
|
787
|
+
.join,
|
|
788
|
+
.pagination,
|
|
789
|
+
.tabs,
|
|
790
|
+
.tab,
|
|
791
|
+
.flyout
|
|
792
|
+
).lg {
|
|
793
|
+
--control-size: var(--control-size-lg);
|
|
794
|
+
--control-font-size: var(--font-size-lg);
|
|
795
|
+
--control-pad-x: var(--font-size-lg);
|
|
670
796
|
}
|
|
671
797
|
|
|
672
798
|
/* Modern color enhancement. Baseline variant values above remain usable
|
|
@@ -680,7 +806,10 @@ small {
|
|
|
680
806
|
var(--surface) var(--soft-border-mix),
|
|
681
807
|
var(--intent, var(--neutral))
|
|
682
808
|
);
|
|
683
|
-
--ui-fg:
|
|
809
|
+
--ui-fg: var(
|
|
810
|
+
--intent-soft-fg,
|
|
811
|
+
color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix))
|
|
812
|
+
);
|
|
684
813
|
/* @sync soft-recipe */
|
|
685
814
|
}
|
|
686
815
|
|
|
@@ -784,7 +913,7 @@ small {
|
|
|
784
913
|
.prose :where(h1, h2, h3, h4, h5, h6) {
|
|
785
914
|
margin-block: 1.5em 0;
|
|
786
915
|
color: var(--heading, var(--text));
|
|
787
|
-
font-weight: var(--font-weight-
|
|
916
|
+
font-weight: var(--font-weight-semibold);
|
|
788
917
|
line-height: var(--prose-heading-line-height);
|
|
789
918
|
}
|
|
790
919
|
|
|
@@ -825,12 +954,12 @@ small {
|
|
|
825
954
|
text-underline-offset: 0.18em;
|
|
826
955
|
}
|
|
827
956
|
|
|
828
|
-
.prose :where(a:hover) {
|
|
957
|
+
.prose :where(a:hover, a:focus-visible) {
|
|
829
958
|
text-decoration-thickness: 0.12em;
|
|
830
959
|
}
|
|
831
960
|
|
|
832
961
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
833
|
-
.prose :where(a:hover) {
|
|
962
|
+
.prose :where(a:hover, a:focus-visible) {
|
|
834
963
|
color: color-mix(in oklab, var(--link, var(--primary)), var(--text) 15%);
|
|
835
964
|
}
|
|
836
965
|
}
|
|
@@ -848,7 +977,7 @@ small {
|
|
|
848
977
|
/* Description lists keep document flow: a valid <dl> may pair several <dt>
|
|
849
978
|
with one <dd> or the reverse, so no grid layout is imposed here. */
|
|
850
979
|
.prose :where(dt) {
|
|
851
|
-
font-weight: var(--font-weight-
|
|
980
|
+
font-weight: var(--font-weight-semibold);
|
|
852
981
|
}
|
|
853
982
|
|
|
854
983
|
.prose :where(dd) {
|
|
@@ -890,8 +1019,8 @@ small {
|
|
|
890
1019
|
}
|
|
891
1020
|
/* @sync key-recipe */
|
|
892
1021
|
|
|
1022
|
+
/* Overflow containment is global on `pre` (base.css); only the box is prose's. */
|
|
893
1023
|
.prose :where(pre) {
|
|
894
|
-
overflow-x: auto;
|
|
895
1024
|
padding: 1em;
|
|
896
1025
|
border: var(--border-width) solid var(--border);
|
|
897
1026
|
border-radius: var(--radius);
|
|
@@ -904,6 +1033,7 @@ small {
|
|
|
904
1033
|
}
|
|
905
1034
|
|
|
906
1035
|
.prose :where(blockquote) {
|
|
1036
|
+
margin-inline: 0;
|
|
907
1037
|
padding-inline-start: 1em;
|
|
908
1038
|
border-inline-start: var(--border-width) solid var(--border);
|
|
909
1039
|
color: var(--text-muted);
|
|
@@ -1007,9 +1137,10 @@ small {
|
|
|
1007
1137
|
/*
|
|
1008
1138
|
* Optional fluid typography module.
|
|
1009
1139
|
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1140
|
+
* Part of the typography family (typography/index.css) and the full bundle; it
|
|
1141
|
+
* is not in the minimal core, so a core-only import adds it explicitly when
|
|
1142
|
+
* display, title, and lead sizes must scale with the viewport. Typical use
|
|
1143
|
+
* cases: marketing pages, landing pages, hero sections, documentation
|
|
1013
1144
|
* homepages.
|
|
1014
1145
|
*
|
|
1015
1146
|
* Do not use this for body text, app UI, or the default prose scale. Fluid
|
|
@@ -1134,8 +1265,8 @@ small {
|
|
|
1134
1265
|
--gap is inherited, so a container that composes other primitives sets its
|
|
1135
1266
|
own rhythm on the `gap` property, not on the token: setting --gap on a
|
|
1136
1267
|
region re-spaces every nested stack, cluster and grid at any depth. Setting
|
|
1137
|
-
the token stays correct on the element that consumes it, and
|
|
1138
|
-
deliberately so a density context reaches nested layouts. See
|
|
1268
|
+
the token stays correct on the element that consumes it, and
|
|
1269
|
+
.compact/.spacious set it deliberately so a density context reaches nested layouts. See
|
|
1139
1270
|
docs/pages/layout/stack.md. */
|
|
1140
1271
|
.stack {
|
|
1141
1272
|
display: flex;
|
|
@@ -1297,7 +1428,7 @@ small {
|
|
|
1297
1428
|
text-align: start;
|
|
1298
1429
|
}
|
|
1299
1430
|
|
|
1300
|
-
/*
|
|
1431
|
+
/* In the side layout the FAB owns the viewport's bottom safe-area inset. */
|
|
1301
1432
|
.app-layout > .fab {
|
|
1302
1433
|
margin-block-end: max(var(--fab-offset), env(safe-area-inset-bottom));
|
|
1303
1434
|
}
|
|
@@ -1410,7 +1541,7 @@ small {
|
|
|
1410
1541
|
*
|
|
1411
1542
|
* .grid is space-driven: it fits as many tracks as --grid-min allows.
|
|
1412
1543
|
* .grid-N is count-driven: it declares a structural density of N and collapses
|
|
1413
|
-
* through natural divisors of N
|
|
1544
|
+
* through natural divisors of N.
|
|
1414
1545
|
*
|
|
1415
1546
|
* Public hooks:
|
|
1416
1547
|
* --grid-min
|
|
@@ -1421,22 +1552,15 @@ small {
|
|
|
1421
1552
|
*
|
|
1422
1553
|
* Child contract: each direct child is a grid item placed in the tracks the
|
|
1423
1554
|
* recipe generates.
|
|
1555
|
+
*
|
|
1556
|
+
* Choosing between these and the other layout primitives:
|
|
1557
|
+
* docs/pages/layout/grid.md. Why divisors, why a *named* container, and how
|
|
1558
|
+
* the thresholds were arrived at: docs/design-notes/grid.md.
|
|
1424
1559
|
*/
|
|
1425
1560
|
|
|
1426
|
-
/* --grid-columns replaces the intrinsic auto-fit template: once set, the
|
|
1427
|
-
|
|
1428
|
-
collapse.
|
|
1429
|
-
--grid-columns for exact custom track templates. */
|
|
1430
|
-
|
|
1431
|
-
/* Recipe:
|
|
1432
|
-
- collection whose item width is the contract -> .grid, tune --grid-min
|
|
1433
|
-
- collection whose column density is the contract -> .grid-2/3/4/6
|
|
1434
|
-
- balanced subdivision of that density -> place it inside any size query
|
|
1435
|
-
container (.container-query is the convenience helper for that context)
|
|
1436
|
-
- peer regions that switch as a group -> .switcher
|
|
1437
|
-
- content + aside -> .sidebar-layout
|
|
1438
|
-
- exact custom track templates -> --grid-columns with an explicit collapse
|
|
1439
|
-
rule of your own */
|
|
1561
|
+
/* --grid-columns replaces the intrinsic auto-fit template: once set, the grid
|
|
1562
|
+
no longer collapses on its own and the author owns narrow-container
|
|
1563
|
+
collapse. */
|
|
1440
1564
|
.grid {
|
|
1441
1565
|
display: grid;
|
|
1442
1566
|
gap: var(--gap);
|
|
@@ -1446,16 +1570,14 @@ small {
|
|
|
1446
1570
|
);
|
|
1447
1571
|
}
|
|
1448
1572
|
|
|
1449
|
-
/*
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
partial final page holds fewer items than the grid's capacity.
|
|
1573
|
+
/* Baseline: auto-fill bounded at N, so a preset is responsive and
|
|
1574
|
+
overflow-safe with no wrapper at all. auto-fill rather than auto-fit keeps
|
|
1575
|
+
item width stable when a partial final page holds fewer items than the
|
|
1576
|
+
grid's capacity.
|
|
1454
1577
|
|
|
1455
|
-
--grid-min is deliberately NOT read here
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
added. Item width is .grid's contract; column density is .grid-N's. */
|
|
1578
|
+
--grid-min is deliberately NOT read here: @container thresholds cannot
|
|
1579
|
+
resolve a custom property, so a hook honored in the baseline would fall
|
|
1580
|
+
silent the moment a query container is added. */
|
|
1459
1581
|
:where(.grid-2, .grid-3, .grid-4, .grid-6) {
|
|
1460
1582
|
display: grid;
|
|
1461
1583
|
gap: var(--gap);
|
|
@@ -1484,80 +1606,63 @@ small {
|
|
|
1484
1606
|
--grid-count: 6;
|
|
1485
1607
|
}
|
|
1486
1608
|
|
|
1487
|
-
/* Grid items keep an automatic
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
need .min-inline-size-0 outside primitives that do not already remove this
|
|
1491
|
-
floor. */
|
|
1609
|
+
/* Grid items keep an automatic min-content floor that can overflow a
|
|
1610
|
+
minmax(0, 1fr) track. Layout primitives remove it so their overflow-safe
|
|
1611
|
+
track sizing holds; .min-inline-size-0 is for the ones that do not. */
|
|
1492
1612
|
:where(.grid, .grid-2, .grid-3, .grid-4, .grid-6) > * {
|
|
1493
1613
|
min-inline-size: 0;
|
|
1494
1614
|
}
|
|
1495
1615
|
|
|
1496
|
-
/* Balanced subdivision,
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
* or a region that sets container-type: inline-size for an unrelated reason
|
|
1500
|
-
* would silently change the grid. The author opts the grid in by naming the
|
|
1501
|
-
* context — .container-query is the official helper, or declare
|
|
1502
|
-
* container: actual-container / inline-size on your own region.
|
|
1616
|
+
/* Balanced subdivision, keyed on an ancestor container *named*
|
|
1617
|
+
* actual-container — never an anonymous one, so a region that sets
|
|
1618
|
+
* container-type: inline-size for an unrelated reason cannot change a grid.
|
|
1503
1619
|
*
|
|
1504
1620
|
* .grid-2 2 -> 1
|
|
1505
1621
|
* .grid-3 3 -> 1
|
|
1506
1622
|
* .grid-4 4 -> 2 -> 1
|
|
1507
1623
|
* .grid-6 6 -> 3 -> 2 -> 1
|
|
1508
1624
|
*
|
|
1509
|
-
* Every state is a divisor of N
|
|
1510
|
-
*
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1520
|
-
*
|
|
1521
|
-
*
|
|
1522
|
-
* threshold fires (a bare grid would otherwise show a 2 + 1 orphan at 28-48rem).
|
|
1523
|
-
*
|
|
1524
|
-
* Three shared thresholds, not one calibrated per preset: 28rem opens the
|
|
1525
|
-
* first horizontal density, 48rem opens three columns, 64rem opens the dense
|
|
1526
|
-
* 4 and 6. N already carries the author's statement about item size, so
|
|
1527
|
-
* tuning every step to land the same item width would erase that signal —
|
|
1528
|
-
* .grid-6 at 64rem gives ~161px per tile, which is the point of choosing it.
|
|
1529
|
-
* Probed with the content each preset targets: large cards for .grid-2 down
|
|
1530
|
-
* to stats, avatars, and thumbnails for .grid-6.
|
|
1625
|
+
* Every state is a divisor of N. Do not complete these chains with the
|
|
1626
|
+
* missing integers and do not replace them with an unbounded auto-fit: both
|
|
1627
|
+
* bring back uneven splits such as 5 + 1.
|
|
1628
|
+
*
|
|
1629
|
+
* The container must be an ANCESTOR. An element cannot query its own size
|
|
1630
|
+
* without inline-size containment, which destroys its intrinsic inline
|
|
1631
|
+
* contribution and collapses it inside .cluster, .topbar, floats and any
|
|
1632
|
+
* shrink-to-fit box.
|
|
1633
|
+
*
|
|
1634
|
+
* With no such ancestor none of these resolve and the baseline above stays.
|
|
1635
|
+
* min-width: 0 is the lowest step: it pins the grid to one column before any
|
|
1636
|
+
* larger threshold fires, which a bare grid would otherwise show as a 2 + 1
|
|
1637
|
+
* orphan between 28 and 48rem.
|
|
1531
1638
|
*
|
|
1532
1639
|
* Ascending min-width only: the last matching step wins by cascade order.
|
|
1533
1640
|
*/
|
|
1534
|
-
@
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
grid-template-columns: 1fr;
|
|
1538
|
-
}
|
|
1641
|
+
@container actual-container (min-width: 0px) {
|
|
1642
|
+
:where(.grid-2, .grid-3, .grid-4, .grid-6) {
|
|
1643
|
+
grid-template-columns: 1fr;
|
|
1539
1644
|
}
|
|
1645
|
+
}
|
|
1540
1646
|
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
}
|
|
1647
|
+
@container actual-container (min-width: 28rem) {
|
|
1648
|
+
:where(.grid-2, .grid-4, .grid-6) {
|
|
1649
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1545
1650
|
}
|
|
1651
|
+
}
|
|
1546
1652
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
}
|
|
1653
|
+
@container actual-container (min-width: 48rem) {
|
|
1654
|
+
:where(.grid-3, .grid-6) {
|
|
1655
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1551
1656
|
}
|
|
1657
|
+
}
|
|
1552
1658
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1659
|
+
@container actual-container (min-width: 64rem) {
|
|
1660
|
+
:where(.grid-4) {
|
|
1661
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1662
|
+
}
|
|
1557
1663
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
}
|
|
1664
|
+
:where(.grid-6) {
|
|
1665
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
1561
1666
|
}
|
|
1562
1667
|
}
|
|
1563
1668
|
|
|
@@ -1716,10 +1821,9 @@ small {
|
|
|
1716
1821
|
* the light/dark adaptation that color-scheme already gives for free, and the
|
|
1717
1822
|
* two never match anyway.
|
|
1718
1823
|
*
|
|
1719
|
-
* Above
|
|
1720
|
-
*
|
|
1721
|
-
*
|
|
1722
|
-
* not a broken state.
|
|
1824
|
+
* Above every tier: scrollbar-color lands in Chromium 121 and Safari 18.2, so
|
|
1825
|
+
* even a Recommended engine may lack it. Engines that ignore both declarations
|
|
1826
|
+
* draw their native scrollbar — the intended fallback, not a broken state.
|
|
1723
1827
|
*
|
|
1724
1828
|
* Styles the scrollbar only; it does not create the overflow. Pair it with
|
|
1725
1829
|
* .overflow-auto or a component that already scrolls.
|
|
@@ -1781,24 +1885,28 @@ small {
|
|
|
1781
1885
|
.scroll-snap {
|
|
1782
1886
|
overscroll-behavior-inline: contain;
|
|
1783
1887
|
scroll-padding-inline: var(--scroll-snap-padding, var(--scroll-snap-gap));
|
|
1888
|
+
/* x then inline: an engine that lacks the logical keyword keeps x.
|
|
1889
|
+
proximity is the default, so a short rail cannot trap the pointer. */
|
|
1890
|
+
scroll-snap-type: x proximity;
|
|
1891
|
+
scroll-snap-type: inline proximity;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/* Strict snapping is opt-in; proximity keeps a rail from fighting the
|
|
1895
|
+
pointer on a short strip. */
|
|
1896
|
+
.scroll-snap[data-snap="mandatory"] {
|
|
1784
1897
|
scroll-snap-type: x mandatory;
|
|
1898
|
+
scroll-snap-type: inline mandatory;
|
|
1785
1899
|
}
|
|
1786
1900
|
|
|
1787
1901
|
.scroll-snap > * {
|
|
1788
1902
|
scroll-snap-align: var(--scroll-snap-align);
|
|
1789
1903
|
}
|
|
1790
1904
|
|
|
1791
|
-
.scroll-snap[data-snap="center"] {
|
|
1905
|
+
.scroll-snap[data-snap-align="center"] {
|
|
1792
1906
|
--scroll-snap-align: center;
|
|
1793
1907
|
}
|
|
1794
1908
|
}
|
|
1795
1909
|
|
|
1796
|
-
@supports (scroll-snap-type: inline mandatory) {
|
|
1797
|
-
.scroll-snap {
|
|
1798
|
-
scroll-snap-type: inline mandatory;
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
1910
|
.scroll-snap[data-scrollbar="hidden"] {
|
|
1803
1911
|
scrollbar-width: none;
|
|
1804
1912
|
}
|
|
@@ -1899,6 +2007,7 @@ small {
|
|
|
1899
2007
|
display: grid;
|
|
1900
2008
|
gap: var(--space-10);
|
|
1901
2009
|
color: var(--text);
|
|
2010
|
+
font-size: var(--control-font-size);
|
|
1902
2011
|
}
|
|
1903
2012
|
|
|
1904
2013
|
.field-group {
|
|
@@ -1912,7 +2021,7 @@ small {
|
|
|
1912
2021
|
.field-label,
|
|
1913
2022
|
.field-group > legend {
|
|
1914
2023
|
color: var(--text);
|
|
1915
|
-
font-weight: var(--font-weight-
|
|
2024
|
+
font-weight: var(--font-weight-semibold);
|
|
1916
2025
|
}
|
|
1917
2026
|
|
|
1918
2027
|
.field-group > legend {
|
|
@@ -1923,13 +2032,13 @@ small {
|
|
|
1923
2032
|
|
|
1924
2033
|
.field-help {
|
|
1925
2034
|
color: var(--text-muted);
|
|
1926
|
-
font-size:
|
|
2035
|
+
font-size: 0.875em;
|
|
1927
2036
|
}
|
|
1928
2037
|
|
|
1929
2038
|
.field-error {
|
|
1930
2039
|
display: none;
|
|
1931
2040
|
color: var(--danger);
|
|
1932
|
-
font-size:
|
|
2041
|
+
font-size: 0.875em;
|
|
1933
2042
|
}
|
|
1934
2043
|
|
|
1935
2044
|
.required-mark {
|
|
@@ -1974,9 +2083,10 @@ small {
|
|
|
1974
2083
|
--control-cursor: auto;
|
|
1975
2084
|
|
|
1976
2085
|
appearance: none;
|
|
2086
|
+
-webkit-appearance: none;
|
|
1977
2087
|
inline-size: 100%;
|
|
1978
2088
|
min-block-size: var(--control-size);
|
|
1979
|
-
padding-inline: var(--
|
|
2089
|
+
padding-inline: var(--control-pad-x);
|
|
1980
2090
|
border: var(--border-width) solid var(--form-invalid-border, var(--control-border));
|
|
1981
2091
|
border-block-end-color: var(
|
|
1982
2092
|
--form-invalid-border,
|
|
@@ -2159,6 +2269,7 @@ small {
|
|
|
2159
2269
|
background: var(--choice-bg);
|
|
2160
2270
|
color: var(--choice-mark);
|
|
2161
2271
|
appearance: none;
|
|
2272
|
+
-webkit-appearance: none;
|
|
2162
2273
|
cursor: pointer;
|
|
2163
2274
|
}
|
|
2164
2275
|
|
|
@@ -2197,9 +2308,9 @@ small {
|
|
|
2197
2308
|
|
|
2198
2309
|
.check:is(:checked, :indeterminate),
|
|
2199
2310
|
.radio:checked {
|
|
2200
|
-
--choice-border: var(--intent, var(--
|
|
2201
|
-
--choice-bg: var(--intent, var(--
|
|
2202
|
-
--choice-mark: var(--intent-fg, var(--
|
|
2311
|
+
--choice-border: var(--intent, var(--state-selected));
|
|
2312
|
+
--choice-bg: var(--intent, var(--state-selected));
|
|
2313
|
+
--choice-mark: var(--intent-fg, var(--state-selected-fg));
|
|
2203
2314
|
}
|
|
2204
2315
|
|
|
2205
2316
|
.check:checked::before {
|
|
@@ -2218,6 +2329,8 @@ small {
|
|
|
2218
2329
|
transform: scale(1);
|
|
2219
2330
|
}
|
|
2220
2331
|
|
|
2332
|
+
/* Progressive: without :not() selector lists, the normal unchecked border
|
|
2333
|
+
remains visible and the control stays usable. */
|
|
2221
2334
|
.check:hover:not(:checked, :indeterminate, :disabled),
|
|
2222
2335
|
.radio:hover:not(:checked, :indeterminate, :disabled) {
|
|
2223
2336
|
--choice-border: var(--text-subtle);
|
|
@@ -2253,20 +2366,40 @@ small {
|
|
|
2253
2366
|
* the CSS chevron, so the picker (appearance: base-select) enhancement in
|
|
2254
2367
|
* custom-select.css can be opted out independently. Add .native to a specific
|
|
2255
2368
|
* select to opt out of the enhancement.
|
|
2369
|
+
*
|
|
2370
|
+
* Public hooks:
|
|
2371
|
+
* --select-icon-size
|
|
2256
2372
|
*/
|
|
2257
|
-
|
|
2373
|
+
/* --select-icon-size is typographic (em), so the reserved slot and the
|
|
2374
|
+
pictogram scale with the control's font size, not a fixed rem sliver. */
|
|
2375
|
+
.select:not(:is([multiple], [size]:not([size="1"]))) {
|
|
2376
|
+
--select-icon-size: 1em;
|
|
2377
|
+
|
|
2258
2378
|
appearance: none;
|
|
2259
|
-
|
|
2379
|
+
-webkit-appearance: none;
|
|
2380
|
+
padding-inline-end: calc(var(--control-pad-x) * 2 + var(--select-icon-size));
|
|
2260
2381
|
background-image: var(--icon-chevron);
|
|
2261
|
-
background-position: center right var(--
|
|
2262
|
-
background-size:
|
|
2382
|
+
background-position: center right var(--control-pad-x);
|
|
2383
|
+
background-size: var(--select-icon-size) auto;
|
|
2263
2384
|
background-repeat: no-repeat;
|
|
2264
2385
|
}
|
|
2265
2386
|
|
|
2266
2387
|
/* The chevron sits on the inline-end side; flip it in RTL so it follows
|
|
2267
|
-
the padding-inline-end space reserved for it.
|
|
2268
|
-
|
|
2269
|
-
|
|
2388
|
+
the padding-inline-end space reserved for it. Safari before 16.4 has no
|
|
2389
|
+
:dir(); the ancestor-attribute path is the Degraded-tier fallback and the
|
|
2390
|
+
:dir() rules are dropped on their own there. :dir() follows inherited
|
|
2391
|
+
directionality, so the explicit ltr rule neutralizes an outer RTL fallback
|
|
2392
|
+
for a nested direction change. */
|
|
2393
|
+
[dir="rtl"] .select:not(:is([multiple], [size]:not([size="1"]))) {
|
|
2394
|
+
background-position: center left var(--control-pad-x);
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
.select:not(:is([multiple], [size]:not([size="1"]))):dir(ltr) {
|
|
2398
|
+
background-position: center right var(--control-pad-x);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
.select:not(:is([multiple], [size]:not([size="1"]))):dir(rtl) {
|
|
2402
|
+
background-position: center left var(--control-pad-x);
|
|
2270
2403
|
}
|
|
2271
2404
|
|
|
2272
2405
|
/* Both forced-colors and prefers-contrast hand the arrow back to the
|
|
@@ -2274,10 +2407,10 @@ small {
|
|
|
2274
2407
|
simpler than maintaining contrast variants, and native rendering already
|
|
2275
2408
|
matches whatever contrast mode the user picked. */
|
|
2276
2409
|
@media (forced-colors: active), (prefers-contrast: more) {
|
|
2277
|
-
.select:not([multiple], [size]:not([size="1"])) {
|
|
2410
|
+
.select:not(:is([multiple], [size]:not([size="1"]))) {
|
|
2278
2411
|
appearance: auto;
|
|
2279
2412
|
background-image: none;
|
|
2280
|
-
padding-inline-end: var(--
|
|
2413
|
+
padding-inline-end: var(--control-pad-x);
|
|
2281
2414
|
}
|
|
2282
2415
|
}
|
|
2283
2416
|
|
|
@@ -2291,18 +2424,23 @@ small {
|
|
|
2291
2424
|
*/
|
|
2292
2425
|
.file {
|
|
2293
2426
|
color: var(--text-muted);
|
|
2427
|
+
font-size: var(--control-font-size);
|
|
2294
2428
|
}
|
|
2295
2429
|
|
|
2296
2430
|
.file::file-selector-button {
|
|
2297
2431
|
margin-inline-end: var(--space-30);
|
|
2432
|
+
/* Consume the shared control scale like .btn, so .sm/.lg and density resize
|
|
2433
|
+
the whole control, not just the filename text. */
|
|
2434
|
+
min-block-size: var(--control-size);
|
|
2298
2435
|
padding-block: var(--space-10);
|
|
2436
|
+
padding-block: max(var(--space-10), calc((var(--control-size) - 1lh) / 2));
|
|
2299
2437
|
padding-inline: var(--space-20);
|
|
2300
2438
|
border: var(--border-width) solid var(--border);
|
|
2301
2439
|
border-radius: var(--radius-sm);
|
|
2302
2440
|
background: var(--surface-subtle);
|
|
2303
2441
|
color: var(--text);
|
|
2304
2442
|
font: inherit;
|
|
2305
|
-
font-size: var(--font-size
|
|
2443
|
+
font-size: var(--control-font-size);
|
|
2306
2444
|
font-weight: var(--font-weight-medium);
|
|
2307
2445
|
cursor: pointer;
|
|
2308
2446
|
transition:
|
|
@@ -2529,18 +2667,19 @@ small {
|
|
|
2529
2667
|
* Input Icon — decorative icon inside a relative wrapper, leading or trailing.
|
|
2530
2668
|
*
|
|
2531
2669
|
* Icon-library agnostic: detects position by DOM order. First child that is
|
|
2532
|
-
* not .input = leading icon; icon after .input = trailing. The icon
|
|
2533
|
-
*
|
|
2534
|
-
* the pictogram.
|
|
2670
|
+
* not .input = leading icon; icon after .input = trailing. The icon follows
|
|
2671
|
+
* local typography; density contexts shrink the control geometry around it
|
|
2672
|
+
* without changing the pictogram.
|
|
2535
2673
|
*
|
|
2536
2674
|
* Public hooks:
|
|
2537
2675
|
* --input-icon-size
|
|
2538
2676
|
*/
|
|
2539
2677
|
.input-icon {
|
|
2540
|
-
--input-icon-size: 1.
|
|
2678
|
+
--input-icon-size: 1.25em;
|
|
2541
2679
|
|
|
2542
2680
|
display: block;
|
|
2543
2681
|
position: relative;
|
|
2682
|
+
font-size: var(--control-font-size);
|
|
2544
2683
|
}
|
|
2545
2684
|
|
|
2546
2685
|
/* Icon — shared by leading and trailing */
|
|
@@ -2555,29 +2694,34 @@ small {
|
|
|
2555
2694
|
|
|
2556
2695
|
/* Leading: icon is the first child */
|
|
2557
2696
|
.input-icon > :first-child:where(:not(.input)) {
|
|
2558
|
-
inset-inline-start: var(--
|
|
2697
|
+
inset-inline-start: var(--control-pad-x);
|
|
2559
2698
|
}
|
|
2560
2699
|
|
|
2561
|
-
/* Trailing:
|
|
2562
|
-
.input-icon > .input
|
|
2563
|
-
inset-inline-end: var(--
|
|
2700
|
+
/* Trailing: an icon follows the input, with or without a leading icon. */
|
|
2701
|
+
.input-icon > .input ~ :where(:not(.input)) {
|
|
2702
|
+
inset-inline-end: var(--control-pad-x);
|
|
2564
2703
|
}
|
|
2565
2704
|
|
|
2566
2705
|
/* font-size only sizes text-metric icons (webfont <i>, <span>). An inline
|
|
2567
2706
|
<svg> with just a viewBox ignores it, so give it explicit dimensions. */
|
|
2568
2707
|
.input-icon > svg:where(:not(.input)) {
|
|
2569
|
-
|
|
2570
|
-
|
|
2708
|
+
/* The shared rule already maps --input-icon-size onto font-size. One em is
|
|
2709
|
+
therefore the requested optical size; reusing the em-valued hook here
|
|
2710
|
+
would apply its factor a second time. */
|
|
2711
|
+
inline-size: 1em;
|
|
2712
|
+
block-size: 1em;
|
|
2571
2713
|
}
|
|
2572
2714
|
|
|
2573
2715
|
/* Leading: input gets start padding to clear the icon */
|
|
2574
2716
|
.input-icon > :first-child:where(:not(.input)) ~ .input {
|
|
2575
|
-
padding-inline-start: calc(var(--
|
|
2717
|
+
padding-inline-start: calc(var(--control-pad-x) * 2 + var(--input-icon-size));
|
|
2576
2718
|
}
|
|
2577
2719
|
|
|
2578
|
-
/* Trailing: input
|
|
2579
|
-
|
|
2580
|
-
|
|
2720
|
+
/* Trailing: the input has a following sibling → end padding. Not tied to the
|
|
2721
|
+
input being first child, so a leading icon does not disable the trailing
|
|
2722
|
+
icon's reserved space. */
|
|
2723
|
+
.input-icon > .input:where(:not(:last-child)) {
|
|
2724
|
+
padding-inline-end: calc(var(--control-pad-x) * 2 + var(--input-icon-size));
|
|
2581
2725
|
}
|
|
2582
2726
|
|
|
2583
2727
|
/*
|
|
@@ -2647,6 +2791,7 @@ small {
|
|
|
2647
2791
|
border-radius: var(--radius-full);
|
|
2648
2792
|
background: var(--switch-bg);
|
|
2649
2793
|
appearance: none;
|
|
2794
|
+
-webkit-appearance: none;
|
|
2650
2795
|
cursor: pointer;
|
|
2651
2796
|
}
|
|
2652
2797
|
|
|
@@ -2667,14 +2812,17 @@ small {
|
|
|
2667
2812
|
background-color var(--duration);
|
|
2668
2813
|
}
|
|
2669
2814
|
|
|
2670
|
-
|
|
2815
|
+
/* Hover is not selected: an unchecked switch keeps the brand fallback on hover,
|
|
2816
|
+
so a theme that separates --primary from --state-selected never tints an
|
|
2817
|
+
OFF control with the selection accent before it is checked. */
|
|
2818
|
+
.switch:not(:checked):hover:not(:disabled) {
|
|
2671
2819
|
--switch-border: var(--intent, var(--primary));
|
|
2672
2820
|
}
|
|
2673
2821
|
|
|
2674
2822
|
.switch:checked {
|
|
2675
|
-
--switch-bg: var(--intent, var(--
|
|
2676
|
-
--switch-border: var(--intent, var(--
|
|
2677
|
-
--switch-knob: var(--intent-fg, var(--
|
|
2823
|
+
--switch-bg: var(--intent, var(--state-selected));
|
|
2824
|
+
--switch-border: var(--intent, var(--state-selected));
|
|
2825
|
+
--switch-knob: var(--intent-fg, var(--state-selected-fg));
|
|
2678
2826
|
}
|
|
2679
2827
|
|
|
2680
2828
|
.switch:checked::before {
|
|
@@ -2682,7 +2830,17 @@ small {
|
|
|
2682
2830
|
}
|
|
2683
2831
|
|
|
2684
2832
|
/* The knob travels toward inline-end; in RTL that is leftward, so the
|
|
2685
|
-
translateX sign flips. translateX is physical, not logical.
|
|
2833
|
+
translateX sign flips. translateX is physical, not logical. Safari before
|
|
2834
|
+
16.4 has no :dir(); the ancestor-attribute path is the Degraded-tier
|
|
2835
|
+
fallback and the :dir() rules are dropped on their own there. */
|
|
2836
|
+
[dir="rtl"] .switch:checked::before {
|
|
2837
|
+
transform: translateX(calc(var(--switch-travel) * -1));
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
.switch:checked:dir(ltr)::before {
|
|
2841
|
+
transform: translateX(var(--switch-travel));
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2686
2844
|
.switch:checked:dir(rtl)::before {
|
|
2687
2845
|
transform: translateX(calc(var(--switch-travel) * -1));
|
|
2688
2846
|
}
|
|
@@ -2739,6 +2897,7 @@ small {
|
|
|
2739
2897
|
--range-track-bg: var(--border);
|
|
2740
2898
|
|
|
2741
2899
|
appearance: none;
|
|
2900
|
+
-webkit-appearance: none;
|
|
2742
2901
|
inline-size: 100%;
|
|
2743
2902
|
block-size: var(--range-thumb-size);
|
|
2744
2903
|
background: transparent;
|
|
@@ -2759,6 +2918,7 @@ small {
|
|
|
2759
2918
|
|
|
2760
2919
|
.range::-webkit-slider-thumb {
|
|
2761
2920
|
appearance: none;
|
|
2921
|
+
-webkit-appearance: none;
|
|
2762
2922
|
inline-size: var(--range-thumb-size);
|
|
2763
2923
|
block-size: var(--range-thumb-size);
|
|
2764
2924
|
border-radius: var(--radius-full);
|
|
@@ -2906,13 +3066,13 @@ small {
|
|
|
2906
3066
|
|
|
2907
3067
|
/* Checked border — shared by radio and checkbox */
|
|
2908
3068
|
.choice-card:has(:checked) {
|
|
2909
|
-
--choice-card-border: var(--intent, var(--
|
|
3069
|
+
--choice-card-border: var(--intent, var(--state-selected));
|
|
2910
3070
|
}
|
|
2911
3071
|
|
|
2912
3072
|
/* Subtle background tint on checked — modern browsers only */
|
|
2913
3073
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
2914
3074
|
.choice-card:has(:checked) {
|
|
2915
|
-
--choice-card-bg: color-mix(in oklab, var(--surface) 96%, var(--intent, var(--
|
|
3075
|
+
--choice-card-bg: color-mix(in oklab, var(--surface) 96%, var(--intent, var(--state-selected)));
|
|
2916
3076
|
}
|
|
2917
3077
|
}
|
|
2918
3078
|
|
|
@@ -2932,7 +3092,7 @@ small {
|
|
|
2932
3092
|
|
|
2933
3093
|
/* Badge disc — circle for radio, rounded square for checkbox */
|
|
2934
3094
|
.choice-card::before {
|
|
2935
|
-
background: var(--intent, var(--
|
|
3095
|
+
background: var(--intent, var(--state-selected));
|
|
2936
3096
|
box-shadow: 0 0 0 2px var(--surface);
|
|
2937
3097
|
z-index: 1;
|
|
2938
3098
|
}
|
|
@@ -2947,7 +3107,7 @@ small {
|
|
|
2947
3107
|
|
|
2948
3108
|
/* Check mark */
|
|
2949
3109
|
.choice-card::after {
|
|
2950
|
-
background: var(--intent-fg, var(--
|
|
3110
|
+
background: var(--intent-fg, var(--state-selected-fg));
|
|
2951
3111
|
-webkit-mask: var(--icon-check) center / 60% no-repeat;
|
|
2952
3112
|
mask: var(--icon-check) center / 60% no-repeat;
|
|
2953
3113
|
z-index: 2;
|
|
@@ -3084,8 +3244,8 @@ small {
|
|
|
3084
3244
|
* placeholder=" " to expose a reliable empty state — without it the label
|
|
3085
3245
|
* only reacts to focus.
|
|
3086
3246
|
*
|
|
3087
|
-
* Density flows through --control-size: .
|
|
3088
|
-
* and the reserved headroom with it, leaving typography untouched.
|
|
3247
|
+
* Density flows through --control-size: .compact/.spacious change the control
|
|
3248
|
+
* geometry and the reserved headroom with it, leaving typography untouched.
|
|
3089
3249
|
*
|
|
3090
3250
|
* Public hooks:
|
|
3091
3251
|
* --floating-label-top
|
|
@@ -3116,7 +3276,7 @@ small {
|
|
|
3116
3276
|
inset-inline-start: calc(var(--space-30) + var(--border-width));
|
|
3117
3277
|
color: var(--text-muted);
|
|
3118
3278
|
font-size: var(--control-font-size);
|
|
3119
|
-
font-weight: var(--font-weight);
|
|
3279
|
+
font-weight: var(--font-weight-normal);
|
|
3120
3280
|
opacity: var(--floating-label-opacity);
|
|
3121
3281
|
pointer-events: none;
|
|
3122
3282
|
transition:
|
|
@@ -3196,7 +3356,7 @@ small {
|
|
|
3196
3356
|
.otp {
|
|
3197
3357
|
--otp-cell-size: var(--control-size);
|
|
3198
3358
|
--otp-gap: var(--space-10);
|
|
3199
|
-
--otp-font-size: 1.
|
|
3359
|
+
--otp-font-size: 1.5em;
|
|
3200
3360
|
--otp-cell-border: var(--border);
|
|
3201
3361
|
--otp-cell-bg: var(--surface);
|
|
3202
3362
|
--otp-caret-space: calc((var(--otp-cell-size) - 1ch) / 2 + var(--otp-gap) + 1px);
|
|
@@ -3209,6 +3369,7 @@ small {
|
|
|
3209
3369
|
max-inline-size: 100%;
|
|
3210
3370
|
gap: var(--otp-gap);
|
|
3211
3371
|
border-radius: var(--radius);
|
|
3372
|
+
font-size: var(--control-font-size);
|
|
3212
3373
|
/* Unlike hidden, clip cannot become a scroll container when the caret moves
|
|
3213
3374
|
into the input extension. */
|
|
3214
3375
|
overflow: clip;
|
|
@@ -3250,6 +3411,7 @@ small {
|
|
|
3250
3411
|
letter-spacing: calc(var(--otp-cell-size) + var(--otp-gap) - 1ch);
|
|
3251
3412
|
text-align: start;
|
|
3252
3413
|
appearance: none;
|
|
3414
|
+
-webkit-appearance: none;
|
|
3253
3415
|
}
|
|
3254
3416
|
|
|
3255
3417
|
/* Deliberately no group ring: a box around all the cells reads as a second
|
|
@@ -3284,6 +3446,12 @@ small {
|
|
|
3284
3446
|
cursor: not-allowed;
|
|
3285
3447
|
}
|
|
3286
3448
|
|
|
3449
|
+
/* biome-ignore lint/style/noDescendingSpecificity: disabled must come last of
|
|
3450
|
+
the cell states, and the reorder the rule asks for would invert that. A
|
|
3451
|
+
disabled input is barred from constraint validation, so `:invalid` and
|
|
3452
|
+
`:focus` cannot reach it at all; the only overlap left is an authored
|
|
3453
|
+
aria-invalid, where equal specificity and source order are what make
|
|
3454
|
+
disabled — not danger — the colour a dead field reads as. */
|
|
3287
3455
|
.otp > input:disabled ~ span {
|
|
3288
3456
|
--otp-cell-bg: var(--surface-subtle);
|
|
3289
3457
|
--otp-cell-border: var(--state-disabled);
|
|
@@ -3343,6 +3511,7 @@ small {
|
|
|
3343
3511
|
:where(.btn) {
|
|
3344
3512
|
--intent: initial;
|
|
3345
3513
|
--intent-fg: initial;
|
|
3514
|
+
--intent-soft-fg: initial;
|
|
3346
3515
|
--ui-bg: initial;
|
|
3347
3516
|
--ui-fg: initial;
|
|
3348
3517
|
--ui-border: initial;
|
|
@@ -3414,13 +3583,16 @@ small {
|
|
|
3414
3583
|
--ui-border: transparent;
|
|
3415
3584
|
}
|
|
3416
3585
|
|
|
3417
|
-
/* Solid
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3586
|
+
/* Solid keeps the overlay darkening on hover; soft/outline/ghost/link stay
|
|
3587
|
+
flat. The soft hover is its own fill change (--soft-hover-alpha) — a second
|
|
3588
|
+
overlay on top would double the fill step and drag its ink below the resting
|
|
3589
|
+
contrast on every intent. Routed through a variable instead of a
|
|
3590
|
+
:not(.outline, .ghost, .link) exclusion so the hover rule below only has to
|
|
3591
|
+
state the interactive element list once. */
|
|
3421
3592
|
.btn.outline,
|
|
3422
3593
|
.btn.ghost,
|
|
3423
|
-
.btn.link
|
|
3594
|
+
.btn.link,
|
|
3595
|
+
.btn.soft {
|
|
3424
3596
|
--btn-hover-overlay: transparent;
|
|
3425
3597
|
}
|
|
3426
3598
|
|
|
@@ -3544,17 +3716,6 @@ a.btn[aria-disabled="true"] {
|
|
|
3544
3716
|
}
|
|
3545
3717
|
}
|
|
3546
3718
|
|
|
3547
|
-
@media print {
|
|
3548
|
-
:is(button, input[type="button"], input[type="submit"], input[type="reset"]).btn {
|
|
3549
|
-
display: none !important;
|
|
3550
|
-
}
|
|
3551
|
-
|
|
3552
|
-
a.btn[href^="http"]::after {
|
|
3553
|
-
content: " (" attr(href) ")";
|
|
3554
|
-
font-size: 0.875em;
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
|
|
3558
3719
|
/*
|
|
3559
3720
|
* Badges for compact labels, counts, and state hints.
|
|
3560
3721
|
*
|
|
@@ -3566,8 +3727,10 @@ a.btn[aria-disabled="true"] {
|
|
|
3566
3727
|
* --badge-size
|
|
3567
3728
|
* --badge-pad-x
|
|
3568
3729
|
* --badge-font-size
|
|
3730
|
+
* --badge-icon-size
|
|
3569
3731
|
* --badge-dot-size
|
|
3570
3732
|
* --badge-radius
|
|
3733
|
+
* --badge-dismiss-icon-size
|
|
3571
3734
|
*
|
|
3572
3735
|
* Internal:
|
|
3573
3736
|
* --badge-default-bg
|
|
@@ -3583,6 +3746,7 @@ a.btn[aria-disabled="true"] {
|
|
|
3583
3746
|
:where(.badge) {
|
|
3584
3747
|
--intent: initial;
|
|
3585
3748
|
--intent-fg: initial;
|
|
3749
|
+
--intent-soft-fg: initial;
|
|
3586
3750
|
--ui-bg: initial;
|
|
3587
3751
|
--ui-fg: initial;
|
|
3588
3752
|
--ui-border: initial;
|
|
@@ -3590,10 +3754,14 @@ a.btn[aria-disabled="true"] {
|
|
|
3590
3754
|
}
|
|
3591
3755
|
|
|
3592
3756
|
.badge {
|
|
3593
|
-
--badge-size:
|
|
3594
|
-
|
|
3595
|
-
|
|
3757
|
+
--badge-size: 1.5rem;
|
|
3758
|
+
/* A third of the height keeps the pill proportional across local sizes
|
|
3759
|
+
without coupling it to the label typography. */
|
|
3760
|
+
--badge-pad-x: calc(var(--badge-size) / 3);
|
|
3761
|
+
--badge-font-size: var(--font-size-sm);
|
|
3762
|
+
--badge-icon-size: 1em;
|
|
3596
3763
|
--badge-dot-size: 0.5rem;
|
|
3764
|
+
--badge-dismiss-icon-size: 0.875em;
|
|
3597
3765
|
--badge-default-bg: var(--surface-subtle);
|
|
3598
3766
|
--badge-default-fg: var(--intent, var(--text));
|
|
3599
3767
|
--badge-default-border: var(--border);
|
|
@@ -3606,7 +3774,11 @@ a.btn[aria-disabled="true"] {
|
|
|
3606
3774
|
display: inline-flex;
|
|
3607
3775
|
align-items: center;
|
|
3608
3776
|
justify-content: center;
|
|
3777
|
+
vertical-align: middle;
|
|
3609
3778
|
gap: 0.35em;
|
|
3779
|
+
/* Content-sized: a flex parent must not squeeze the pill and push its
|
|
3780
|
+
nowrap label outside the background. */
|
|
3781
|
+
flex: none;
|
|
3610
3782
|
inline-size: fit-content;
|
|
3611
3783
|
max-inline-size: 100%;
|
|
3612
3784
|
min-block-size: var(--badge-size);
|
|
@@ -3616,7 +3788,7 @@ a.btn[aria-disabled="true"] {
|
|
|
3616
3788
|
background: var(--badge-bg);
|
|
3617
3789
|
color: var(--badge-fg);
|
|
3618
3790
|
font-size: var(--badge-font-size);
|
|
3619
|
-
font-weight: var(--font-weight-
|
|
3791
|
+
font-weight: var(--font-weight-medium);
|
|
3620
3792
|
line-height: 1;
|
|
3621
3793
|
text-decoration: none;
|
|
3622
3794
|
white-space: nowrap;
|
|
@@ -3627,6 +3799,31 @@ a.btn[aria-disabled="true"] {
|
|
|
3627
3799
|
box-shadow var(--duration);
|
|
3628
3800
|
}
|
|
3629
3801
|
|
|
3802
|
+
.badge.sm {
|
|
3803
|
+
--badge-size: 1.25rem;
|
|
3804
|
+
--badge-font-size: var(--font-size-xs);
|
|
3805
|
+
--badge-dot-size: 0.375rem;
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
.badge.lg {
|
|
3809
|
+
--badge-size: 1.875rem;
|
|
3810
|
+
--badge-font-size: var(--font-size-md);
|
|
3811
|
+
--badge-dot-size: 0.625rem;
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
/* Decorative children follow the badge's optical size. Text and interactive
|
|
3815
|
+
dismiss buttons retain their own boxes. */
|
|
3816
|
+
.badge > :where(svg, img, [aria-hidden="true"]:not(.dot)) {
|
|
3817
|
+
flex: none;
|
|
3818
|
+
/* The shared rule maps --badge-icon-size onto font-size. One em is therefore
|
|
3819
|
+
the requested optical size; reusing the em-valued hook here would apply its
|
|
3820
|
+
factor a second time. */
|
|
3821
|
+
inline-size: 1em;
|
|
3822
|
+
block-size: 1em;
|
|
3823
|
+
font-size: var(--badge-icon-size);
|
|
3824
|
+
line-height: 1;
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3630
3827
|
/* Soft by default. Local defaults remain behind --ui-* in the resolution
|
|
3631
3828
|
chain, so explicit shared treatments compose without selector exclusions. */
|
|
3632
3829
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
@@ -3642,10 +3839,9 @@ a.btn[aria-disabled="true"] {
|
|
|
3642
3839
|
var(--surface) var(--soft-border-mix),
|
|
3643
3840
|
var(--intent, var(--neutral))
|
|
3644
3841
|
);
|
|
3645
|
-
--badge-default-fg:
|
|
3646
|
-
|
|
3647
|
-
var(--text),
|
|
3648
|
-
var(--intent, var(--text)) var(--soft-fg-mix)
|
|
3842
|
+
--badge-default-fg: var(
|
|
3843
|
+
--intent-soft-fg,
|
|
3844
|
+
color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix))
|
|
3649
3845
|
);
|
|
3650
3846
|
/* @sync soft-recipe */
|
|
3651
3847
|
}
|
|
@@ -3662,12 +3858,22 @@ a.btn[aria-disabled="true"] {
|
|
|
3662
3858
|
block-size: var(--badge-size);
|
|
3663
3859
|
}
|
|
3664
3860
|
|
|
3861
|
+
/* The shared .dot separator doubles as a status dot inside a badge.
|
|
3862
|
+
currentColor follows the badge's resolved ink, so the dot matches a soft
|
|
3863
|
+
intent without repeating the intent class on it. */
|
|
3864
|
+
.badge > .dot {
|
|
3865
|
+
background-color: currentColor;
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3665
3868
|
.badge > button {
|
|
3666
3869
|
display: inline-grid;
|
|
3667
3870
|
place-items: center;
|
|
3668
3871
|
flex: 0 0 auto;
|
|
3669
|
-
|
|
3670
|
-
|
|
3872
|
+
/* WCAG 2.5.8 floors pointer targets at 24 CSS px. Density and local size may
|
|
3873
|
+
keep the badge visually compact through the negative margins, but never
|
|
3874
|
+
shrink its action target below that literal floor. */
|
|
3875
|
+
min-inline-size: max(var(--badge-size), 24px);
|
|
3876
|
+
min-block-size: max(var(--badge-size), 24px);
|
|
3671
3877
|
margin-block: -0.25rem;
|
|
3672
3878
|
margin-inline-start: -0.15em;
|
|
3673
3879
|
margin-inline-end: -0.45em;
|
|
@@ -3681,6 +3887,21 @@ a.btn[aria-disabled="true"] {
|
|
|
3681
3887
|
cursor: pointer;
|
|
3682
3888
|
}
|
|
3683
3889
|
|
|
3890
|
+
/* Dismiss mark — an empty dismiss button paints its own X from the shared
|
|
3891
|
+
--icon-close mask, so no icon font or text glyph is required in the markup
|
|
3892
|
+
(same contract as .alert-dismiss and .dialog-close). A mask box is a block
|
|
3893
|
+
box, so place-items centers the ink itself; a text glyph would only center
|
|
3894
|
+
its line box and land the ink low, because a lowercase x has no descender
|
|
3895
|
+
and only x-height of ascent. A button with its own content keeps it. */
|
|
3896
|
+
.badge > button:empty::before {
|
|
3897
|
+
content: "";
|
|
3898
|
+
inline-size: var(--badge-dismiss-icon-size);
|
|
3899
|
+
block-size: var(--badge-dismiss-icon-size);
|
|
3900
|
+
background: currentColor;
|
|
3901
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
3902
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3684
3905
|
.badge > button:not(:disabled):hover {
|
|
3685
3906
|
background: var(--hover-overlay);
|
|
3686
3907
|
}
|
|
@@ -3713,7 +3934,14 @@ a.btn[aria-disabled="true"] {
|
|
|
3713
3934
|
background: var(--ui-hover-bg, var(--badge-default-hover-bg));
|
|
3714
3935
|
}
|
|
3715
3936
|
|
|
3716
|
-
|
|
3937
|
+
/* Solid and empty badges keep the overlay darkening on hover; soft badges do
|
|
3938
|
+
not — their hover fill (--soft-hover-alpha) is already the state change, and
|
|
3939
|
+
stacking a second darkening drags the soft ink below its resting contrast.
|
|
3940
|
+
Soft here means :not(.solid, .outline, :empty), mirroring the soft hover-bg
|
|
3941
|
+
recipe above. */
|
|
3942
|
+
:where(a.badge, button.badge):is(.solid, :empty):not(
|
|
3943
|
+
:where(:disabled, [aria-disabled="true"])
|
|
3944
|
+
):hover {
|
|
3717
3945
|
/* Huge inset spread paints the overlay over the pill radius (see button.css). */
|
|
3718
3946
|
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
3719
3947
|
}
|
|
@@ -3745,7 +3973,6 @@ a.badge[aria-disabled="true"] {
|
|
|
3745
3973
|
@media print {
|
|
3746
3974
|
.badge {
|
|
3747
3975
|
border: var(--border-width) solid CanvasText;
|
|
3748
|
-
break-inside: avoid;
|
|
3749
3976
|
}
|
|
3750
3977
|
}
|
|
3751
3978
|
|
|
@@ -3783,6 +4010,7 @@ a.badge[aria-disabled="true"] {
|
|
|
3783
4010
|
:where(.alert) {
|
|
3784
4011
|
--intent: initial;
|
|
3785
4012
|
--intent-fg: initial;
|
|
4013
|
+
--intent-soft-fg: initial;
|
|
3786
4014
|
--ui-bg: initial;
|
|
3787
4015
|
--ui-fg: initial;
|
|
3788
4016
|
--ui-border: initial;
|
|
@@ -3815,12 +4043,23 @@ a.badge[aria-disabled="true"] {
|
|
|
3815
4043
|
min-inline-size: 0;
|
|
3816
4044
|
}
|
|
3817
4045
|
|
|
3818
|
-
|
|
3819
|
-
|
|
4046
|
+
/* The alert owns the block spacing between its direct content blocks, the way
|
|
4047
|
+
.stack and .prose own theirs, so a title and a description are plain
|
|
4048
|
+
siblings instead of a wrapper carrying an inline --gap. What made that pair
|
|
4049
|
+
read as detached is the UA paragraph margin: trimming only the outer edges
|
|
4050
|
+
left it in place between them. Content keeps its own internal rhythm — a
|
|
4051
|
+
nested list keeps its bullets and padding, a .prose child keeps its flow.
|
|
4052
|
+
The slots are excluded because they are anatomy, not content: an
|
|
4053
|
+
admonition's title bar has to stay flush against its body. The list follows
|
|
4054
|
+
the anatomy, so it grows with it. */
|
|
4055
|
+
.alert > :not(:where(.alert-icon, .alert-dismiss, .alert-title, .alert-body)) {
|
|
4056
|
+
margin-block: 0;
|
|
3820
4057
|
}
|
|
3821
4058
|
|
|
3822
|
-
.alert
|
|
3823
|
-
|
|
4059
|
+
.alert
|
|
4060
|
+
> :not(:where(.alert-icon, .alert-dismiss, .alert-title, .alert-body))
|
|
4061
|
+
+ :not(:where(.alert-icon, .alert-dismiss, .alert-title, .alert-body)) {
|
|
4062
|
+
margin-block-start: var(--space-10);
|
|
3824
4063
|
}
|
|
3825
4064
|
|
|
3826
4065
|
/* Alerts are soft by default. The recipe writes local defaults rather than
|
|
@@ -3838,10 +4077,9 @@ a.badge[aria-disabled="true"] {
|
|
|
3838
4077
|
var(--surface) var(--soft-border-mix),
|
|
3839
4078
|
var(--intent, var(--neutral))
|
|
3840
4079
|
);
|
|
3841
|
-
--alert-default-fg:
|
|
3842
|
-
|
|
3843
|
-
var(--text),
|
|
3844
|
-
var(--intent, var(--text)) var(--soft-fg-mix)
|
|
4080
|
+
--alert-default-fg: var(
|
|
4081
|
+
--intent-soft-fg,
|
|
4082
|
+
color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix))
|
|
3845
4083
|
);
|
|
3846
4084
|
/* @sync soft-recipe */
|
|
3847
4085
|
}
|
|
@@ -3942,7 +4180,7 @@ a.badge[aria-disabled="true"] {
|
|
|
3942
4180
|
/* Links */
|
|
3943
4181
|
.alert :where(a:not(.btn)) {
|
|
3944
4182
|
color: inherit;
|
|
3945
|
-
font-weight: var(--font-weight-
|
|
4183
|
+
font-weight: var(--font-weight-semibold);
|
|
3946
4184
|
}
|
|
3947
4185
|
|
|
3948
4186
|
/* Forced colors: no override needed. The always-on border gets a forced
|
|
@@ -3950,11 +4188,12 @@ a.badge[aria-disabled="true"] {
|
|
|
3950
4188
|
Canvas backplate (unreadable) — so .solid intentionally falls back to the
|
|
3951
4189
|
default look. */
|
|
3952
4190
|
|
|
3953
|
-
/* Callout —
|
|
3954
|
-
|
|
4191
|
+
/* Callout — leading-edge flag treatment. It owns the border geometry only, so
|
|
4192
|
+
the surface and the ink stay with the alert's own intent and shared
|
|
4193
|
+
treatment: bare is the soft panel, .surface the page surface, .solid a
|
|
4194
|
+
filled one, and .outline has no fill and no other side. Thickness and color
|
|
4195
|
+
follow the --alert-border-inline-start-* hooks. */
|
|
3955
4196
|
.alert.callout {
|
|
3956
|
-
--alert-bg: var(--surface-subtle);
|
|
3957
|
-
--alert-fg: var(--text);
|
|
3958
4197
|
border: 0;
|
|
3959
4198
|
border-inline-start: var(--alert-border-inline-start-width, 4px) solid
|
|
3960
4199
|
var(--alert-border-inline-start-color, var(--intent, var(--neutral)));
|
|
@@ -4079,8 +4318,15 @@ a.badge[aria-disabled="true"] {
|
|
|
4079
4318
|
* --busy-overlay-bg
|
|
4080
4319
|
*/
|
|
4081
4320
|
/* Intent boundary — inherited intents do not tint this component.
|
|
4082
|
-
Local intent classes still win because :where() has zero specificity.
|
|
4321
|
+
Local intent classes still win because :where() has zero specificity.
|
|
4322
|
+
The intent tokens are reset alongside --ui-*: the shared variants
|
|
4323
|
+
(.soft/.solid/.outline/.surface) read --intent / --intent-fg / --intent-soft-fg
|
|
4324
|
+
when they paint this surface, so an ancestor intent would otherwise leak
|
|
4325
|
+
through a variant that has no local intent class. */
|
|
4083
4326
|
:where(.card) {
|
|
4327
|
+
--intent: initial;
|
|
4328
|
+
--intent-fg: initial;
|
|
4329
|
+
--intent-soft-fg: initial;
|
|
4084
4330
|
--ui-bg: initial;
|
|
4085
4331
|
--ui-fg: initial;
|
|
4086
4332
|
--ui-border: initial;
|
|
@@ -4153,7 +4399,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4153
4399
|
box-shadow: var(--shadow);
|
|
4154
4400
|
}
|
|
4155
4401
|
.card.subtle {
|
|
4156
|
-
|
|
4402
|
+
--card-bg: var(--surface-subtle);
|
|
4157
4403
|
}
|
|
4158
4404
|
.card.compact {
|
|
4159
4405
|
--card-pad: var(--space-20);
|
|
@@ -4205,56 +4451,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4205
4451
|
}
|
|
4206
4452
|
}
|
|
4207
4453
|
|
|
4208
|
-
/*
|
|
4209
|
-
* Surface — shared modal scrim and backdrop for flyout and drawer.
|
|
4210
|
-
*
|
|
4211
|
-
* Drawn by .drawer::backdrop (native dialog top-layer) and .surface-backdrop
|
|
4212
|
-
* (custom fixed div for flyout sheets on coarse pointers). The native
|
|
4213
|
-
* .modal::backdrop uses the same fill and animates opacity from 0 to 1
|
|
4214
|
-
* (modal.css), so the scrim strength stays consistent across the dialog
|
|
4215
|
-
* family.
|
|
4216
|
-
*
|
|
4217
|
-
* .surface-backdrop, .is-sheet, and .is-open are runtime markers set by
|
|
4218
|
-
* surface.js. Authors opt in via [data-flyout-mobile="sheet|auto|none|anchored"]
|
|
4219
|
-
* — the JS resolves "auto" into .is-sheet at run time.
|
|
4220
|
-
*/
|
|
4221
|
-
/* Custom backdrop for overlays that do not use the native dialog top-layer.
|
|
4222
|
-
* Its z-index sits just below the flyout it accompanies. */
|
|
4223
|
-
.surface-backdrop {
|
|
4224
|
-
position: fixed;
|
|
4225
|
-
inset: 0;
|
|
4226
|
-
z-index: calc(var(--z-menu) - 1);
|
|
4227
|
-
background: var(--backdrop-fill);
|
|
4228
|
-
opacity: 1;
|
|
4229
|
-
/* A scrim never travels; it fades on the neutral `ease`. Duration matches
|
|
4230
|
-
the flyout sheet it accompanies so it never lags the panel. */
|
|
4231
|
-
transition: opacity var(--duration-fast) ease;
|
|
4232
|
-
}
|
|
4233
|
-
|
|
4234
|
-
.surface-backdrop[hidden] {
|
|
4235
|
-
display: none;
|
|
4236
|
-
opacity: 0;
|
|
4237
|
-
}
|
|
4238
|
-
|
|
4239
|
-
@starting-style {
|
|
4240
|
-
.surface-backdrop:not([hidden]) {
|
|
4241
|
-
opacity: 0;
|
|
4242
|
-
}
|
|
4243
|
-
}
|
|
4244
|
-
|
|
4245
|
-
@supports (transition-behavior: allow-discrete) {
|
|
4246
|
-
.surface-backdrop {
|
|
4247
|
-
transition:
|
|
4248
|
-
opacity var(--duration-fast) ease,
|
|
4249
|
-
display var(--duration-fast) allow-discrete;
|
|
4250
|
-
}
|
|
4251
|
-
}
|
|
4252
|
-
@media print {
|
|
4253
|
-
.surface-backdrop {
|
|
4254
|
-
display: none !important;
|
|
4255
|
-
}
|
|
4256
|
-
}
|
|
4257
|
-
|
|
4258
4454
|
/*
|
|
4259
4455
|
* Modal shell styling for native dialog elements.
|
|
4260
4456
|
*
|
|
@@ -4269,21 +4465,40 @@ a.badge[aria-disabled="true"] {
|
|
|
4269
4465
|
*
|
|
4270
4466
|
* Internal:
|
|
4271
4467
|
* --dialog-close-icon-size
|
|
4468
|
+
* --dialog-viewport-block
|
|
4469
|
+
* --dialog-viewport-gap-default
|
|
4272
4470
|
* --dialog-viewport-gap-2x
|
|
4273
4471
|
*/
|
|
4274
4472
|
dialog.modal {
|
|
4275
|
-
--
|
|
4276
|
-
|
|
4277
|
-
|
|
4473
|
+
/* --dialog-viewport-gap stays fallback-only for the same reason as
|
|
4474
|
+
--modal-size below: declaring the public hook here would give it
|
|
4475
|
+
type+class specificity that a single author class cannot outrank. The
|
|
4476
|
+
responsive default lives in an internal token instead, so a media query
|
|
4477
|
+
can still move it without closing the hook.
|
|
4478
|
+
|
|
4479
|
+
calc() multiplication is not in the supported browser range, so the
|
|
4278
4480
|
doubled gap is spelled out with an additive calc. */
|
|
4279
|
-
--dialog-viewport-gap-
|
|
4481
|
+
--dialog-viewport-gap-default: var(--space-40);
|
|
4482
|
+
--dialog-viewport-gap-2x: calc(
|
|
4483
|
+
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default)) +
|
|
4484
|
+
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default))
|
|
4485
|
+
);
|
|
4280
4486
|
|
|
4281
4487
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
4282
4488
|
provides the containing block for the absolutely positioned close button. */
|
|
4283
4489
|
position: fixed;
|
|
4284
4490
|
|
|
4285
|
-
|
|
4286
|
-
|
|
4491
|
+
/* --modal-size stays fallback-only: declaring it on dialog.modal gives it
|
|
4492
|
+
type+class specificity, which a single author class cannot outrank, so the
|
|
4493
|
+
public hook would silently keep this default. Same reason choice.css never
|
|
4494
|
+
declares --choice-control-offset. */
|
|
4495
|
+
max-inline-size: min(calc(100% - var(--dialog-viewport-gap-2x)), var(--modal-size, 32rem));
|
|
4496
|
+
|
|
4497
|
+
/* Stable viewport cap for the panel: dvh tracks the dynamic browser toolbar
|
|
4498
|
+
and makes the dialog reflow on mobile, svh does not. App shells keep the
|
|
4499
|
+
dynamic token; only the modal pins the small viewport. */
|
|
4500
|
+
--dialog-viewport-block: var(--viewport-block);
|
|
4501
|
+
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
4287
4502
|
border: var(--border-width) solid var(--border);
|
|
4288
4503
|
border-radius: var(--radius-lg);
|
|
4289
4504
|
background: var(--surface-raised);
|
|
@@ -4293,7 +4508,7 @@ dialog.modal {
|
|
|
4293
4508
|
overflow: visible;
|
|
4294
4509
|
opacity: 0;
|
|
4295
4510
|
/*
|
|
4296
|
-
* Keep the open dialog root untransformed. Fixed overlays
|
|
4511
|
+
* Keep the open dialog root untransformed. Fixed overlays authored inside a
|
|
4297
4512
|
* modal dialog use viewport coordinates; transform/translate/scale on this
|
|
4298
4513
|
* element would make them position against the dialog instead. The closed
|
|
4299
4514
|
* state can still carry a small transform for enter/exit motion. The lift
|
|
@@ -4310,16 +4525,23 @@ dialog.modal > form,
|
|
|
4310
4525
|
dialog.modal > .stack {
|
|
4311
4526
|
display: grid;
|
|
4312
4527
|
gap: var(--space-40);
|
|
4313
|
-
max-block-size: calc(var(--viewport-block) - var(--dialog-viewport-gap-2x));
|
|
4528
|
+
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
4314
4529
|
overflow: auto;
|
|
4315
4530
|
overscroll-behavior: contain;
|
|
4316
4531
|
padding: var(--space-40);
|
|
4317
4532
|
}
|
|
4318
4533
|
|
|
4319
|
-
|
|
4534
|
+
@supports (block-size: 100svh) {
|
|
4535
|
+
dialog.modal {
|
|
4536
|
+
--dialog-viewport-block: 100svh;
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
/* Keep the dialog off the viewport edges on small screens. Moves the internal
|
|
4541
|
+
default, not the public hook, so an author override still wins here too. */
|
|
4320
4542
|
@media (max-width: 30rem) {
|
|
4321
4543
|
dialog.modal {
|
|
4322
|
-
--dialog-viewport-gap: var(--space-30);
|
|
4544
|
+
--dialog-viewport-gap-default: var(--space-30);
|
|
4323
4545
|
}
|
|
4324
4546
|
}
|
|
4325
4547
|
|
|
@@ -4329,8 +4551,8 @@ dialog.modal.scrollable > .stack {
|
|
|
4329
4551
|
overflow: hidden;
|
|
4330
4552
|
}
|
|
4331
4553
|
|
|
4332
|
-
dialog.modal.scrollable > form > :not(header, footer),
|
|
4333
|
-
dialog.modal.scrollable > .stack > :not(header, footer) {
|
|
4554
|
+
dialog.modal.scrollable > form > :not(:is(header, footer)),
|
|
4555
|
+
dialog.modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
4334
4556
|
min-block-size: 0;
|
|
4335
4557
|
overflow: auto;
|
|
4336
4558
|
overscroll-behavior: contain;
|
|
@@ -4385,6 +4607,7 @@ dialog.modal.dialog-confirmation > form > footer {
|
|
|
4385
4607
|
:where(.dialog-icon) {
|
|
4386
4608
|
--intent: initial;
|
|
4387
4609
|
--intent-fg: initial;
|
|
4610
|
+
--intent-soft-fg: initial;
|
|
4388
4611
|
--ui-bg: initial;
|
|
4389
4612
|
--ui-fg: initial;
|
|
4390
4613
|
}
|
|
@@ -4513,14 +4736,11 @@ html.has-modal-open.had-scrollbar {
|
|
|
4513
4736
|
}
|
|
4514
4737
|
}
|
|
4515
4738
|
|
|
4516
|
-
/* Close
|
|
4517
|
-
never participates in the content flow
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
required in the markup. */
|
|
4522
|
-
.dialog-close,
|
|
4523
|
-
.drawer-close {
|
|
4739
|
+
/* Close control. A close button is an action attached to a surface, so it
|
|
4740
|
+
never participates in the content flow — absolute at the panel's top
|
|
4741
|
+
inline-end, inside the surface. The glyph comes from --icon-close, masked to
|
|
4742
|
+
currentColor, so no icon font or text glyph is required in the markup. */
|
|
4743
|
+
.dialog-close {
|
|
4524
4744
|
--dialog-close-icon-size: 1rem;
|
|
4525
4745
|
|
|
4526
4746
|
display: inline-grid;
|
|
@@ -4544,8 +4764,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
4544
4764
|
background: var(--surface-subtle);
|
|
4545
4765
|
}
|
|
4546
4766
|
|
|
4547
|
-
.dialog-close::before
|
|
4548
|
-
.drawer-close::before {
|
|
4767
|
+
.dialog-close::before {
|
|
4549
4768
|
content: "";
|
|
4550
4769
|
inline-size: var(--dialog-close-icon-size);
|
|
4551
4770
|
block-size: var(--dialog-close-icon-size);
|
|
@@ -4555,28 +4774,20 @@ html.has-modal-open.had-scrollbar {
|
|
|
4555
4774
|
}
|
|
4556
4775
|
|
|
4557
4776
|
@media (hover: hover) {
|
|
4558
|
-
.dialog-close:hover
|
|
4559
|
-
.drawer-close:hover {
|
|
4777
|
+
.dialog-close:hover {
|
|
4560
4778
|
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4561
4779
|
color: var(--text);
|
|
4562
4780
|
}
|
|
4563
4781
|
}
|
|
4564
4782
|
|
|
4565
|
-
.dialog-close:active
|
|
4566
|
-
.drawer-close:active {
|
|
4783
|
+
.dialog-close:active {
|
|
4567
4784
|
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4568
4785
|
}
|
|
4569
4786
|
|
|
4570
|
-
.dialog-close:focus-visible
|
|
4571
|
-
.drawer-close:focus-visible {
|
|
4787
|
+
.dialog-close:focus-visible {
|
|
4572
4788
|
outline: 2px solid transparent;
|
|
4573
4789
|
box-shadow: var(--focus-ring-shadow);
|
|
4574
4790
|
}
|
|
4575
|
-
@media print {
|
|
4576
|
-
dialog.modal[open] {
|
|
4577
|
-
display: none !important;
|
|
4578
|
-
}
|
|
4579
|
-
}
|
|
4580
4791
|
|
|
4581
4792
|
/*
|
|
4582
4793
|
* Drawer — modal side-sheet built on the native dialog element.
|
|
@@ -4642,8 +4853,18 @@ dialog.drawer[open] {
|
|
|
4642
4853
|
transform var(--drawer-motion-duration) var(--ease-enter);
|
|
4643
4854
|
}
|
|
4644
4855
|
|
|
4645
|
-
/*
|
|
4646
|
-
|
|
4856
|
+
/* A form is the drawer's content wrapper when actions need method="dialog".
|
|
4857
|
+
Span the drawer tracks so the form can keep the header at the top, let the
|
|
4858
|
+
body absorb the available height, and keep the footer at the bottom. */
|
|
4859
|
+
dialog.drawer > form {
|
|
4860
|
+
display: grid;
|
|
4861
|
+
grid-row: 1 / -1;
|
|
4862
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
4863
|
+
gap: var(--space-40);
|
|
4864
|
+
min-block-size: 100%;
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4867
|
+
/* The shared backdrop token keeps drawer and modal scrims consistent. */
|
|
4647
4868
|
dialog.drawer::backdrop {
|
|
4648
4869
|
background: var(--backdrop-fill);
|
|
4649
4870
|
opacity: 0;
|
|
@@ -4708,33 +4929,33 @@ dialog.drawer nav {
|
|
|
4708
4929
|
}
|
|
4709
4930
|
|
|
4710
4931
|
/* Safari before 16.4 does not support :dir(); keep the ancestor-attribute
|
|
4711
|
-
path as the
|
|
4932
|
+
path as the Degraded-tier fallback. The :dir() rules are dropped on their
|
|
4933
|
+
own there, so they need no wrapper. :dir() follows inherited HTML
|
|
4934
|
+
directionality and handles nested direction changes; the explicit ltr rules
|
|
4935
|
+
neutralize an outer RTL fallback selector. Each specificity group keeps the
|
|
4936
|
+
fallback before the :dir() rules. */
|
|
4712
4937
|
[dir="rtl"] dialog.drawer {
|
|
4713
4938
|
--drawer-closed-translate: 100%;
|
|
4714
4939
|
}
|
|
4715
4940
|
|
|
4716
|
-
|
|
4941
|
+
dialog.drawer:dir(ltr) {
|
|
4717
4942
|
--drawer-closed-translate: -100%;
|
|
4718
4943
|
}
|
|
4719
4944
|
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
dialog.drawer:dir(ltr) {
|
|
4724
|
-
--drawer-closed-translate: -100%;
|
|
4725
|
-
}
|
|
4945
|
+
dialog.drawer:dir(rtl) {
|
|
4946
|
+
--drawer-closed-translate: 100%;
|
|
4947
|
+
}
|
|
4726
4948
|
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4949
|
+
[dir="rtl"] dialog.drawer[data-side="end"] {
|
|
4950
|
+
--drawer-closed-translate: -100%;
|
|
4951
|
+
}
|
|
4730
4952
|
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4953
|
+
dialog.drawer[data-side="end"]:dir(ltr) {
|
|
4954
|
+
--drawer-closed-translate: 100%;
|
|
4955
|
+
}
|
|
4734
4956
|
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
}
|
|
4957
|
+
dialog.drawer[data-side="end"]:dir(rtl) {
|
|
4958
|
+
--drawer-closed-translate: -100%;
|
|
4738
4959
|
}
|
|
4739
4960
|
|
|
4740
4961
|
@media (prefers-reduced-motion: no-preference) {
|
|
@@ -4743,32 +4964,74 @@ dialog.drawer nav {
|
|
|
4743
4964
|
}
|
|
4744
4965
|
}
|
|
4745
4966
|
|
|
4746
|
-
/* The
|
|
4747
|
-
|
|
4748
|
-
inline room so the title never runs under it. */
|
|
4967
|
+
/* The header reserves inline room so the title never runs under the close
|
|
4968
|
+
control. */
|
|
4749
4969
|
dialog.drawer > header,
|
|
4750
4970
|
dialog.drawer > form > header {
|
|
4751
4971
|
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
4752
4972
|
}
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4973
|
+
|
|
4974
|
+
/* Close control, owned here so a drawer-only build does not depend on
|
|
4975
|
+
modal.css. Mirrors the modal's .dialog-close placement: absolute at the
|
|
4976
|
+
panel's top inline-end, out of the content flow. */
|
|
4977
|
+
.drawer-close {
|
|
4978
|
+
--drawer-close-icon-size: 1rem;
|
|
4979
|
+
|
|
4980
|
+
display: inline-grid;
|
|
4981
|
+
place-items: center;
|
|
4982
|
+
padding: 0;
|
|
4983
|
+
border: 0;
|
|
4984
|
+
border-radius: var(--radius);
|
|
4985
|
+
color: var(--text-muted);
|
|
4986
|
+
cursor: pointer;
|
|
4987
|
+
|
|
4988
|
+
position: absolute;
|
|
4989
|
+
z-index: 1;
|
|
4990
|
+
inset-block-start: var(--space-20);
|
|
4991
|
+
inset-inline-end: var(--space-20);
|
|
4992
|
+
|
|
4993
|
+
inline-size: var(--control-size);
|
|
4994
|
+
block-size: var(--control-size);
|
|
4995
|
+
|
|
4996
|
+
background: var(--surface-subtle);
|
|
4997
|
+
}
|
|
4998
|
+
|
|
4999
|
+
.drawer-close::before {
|
|
5000
|
+
content: "";
|
|
5001
|
+
inline-size: var(--drawer-close-icon-size);
|
|
5002
|
+
block-size: var(--drawer-close-icon-size);
|
|
5003
|
+
background: currentColor;
|
|
5004
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
5005
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
5006
|
+
}
|
|
5007
|
+
|
|
5008
|
+
@media (hover: hover) {
|
|
5009
|
+
.drawer-close:hover {
|
|
5010
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
5011
|
+
color: var(--text);
|
|
4756
5012
|
}
|
|
4757
5013
|
}
|
|
4758
5014
|
|
|
5015
|
+
.drawer-close:active {
|
|
5016
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
5017
|
+
}
|
|
5018
|
+
|
|
5019
|
+
.drawer-close:focus-visible {
|
|
5020
|
+
outline: 2px solid transparent;
|
|
5021
|
+
box-shadow: var(--focus-ring-shadow);
|
|
5022
|
+
}
|
|
5023
|
+
|
|
4759
5024
|
|
|
4760
5025
|
/* Overlays */
|
|
4761
5026
|
/*
|
|
4762
5027
|
* Flyout — floating panel for actions and links.
|
|
4763
5028
|
*
|
|
4764
|
-
* JavaScript
|
|
4765
|
-
*
|
|
4766
|
-
* panel appearance, item styles,
|
|
5029
|
+
* JavaScript promotes the panel to the top layer and chooses viewport or
|
|
5030
|
+
* document coordinates from its anchor. It also owns state (.is-open), focus,
|
|
5031
|
+
* and keyboard behavior. CSS owns the surfaced panel appearance, item styles,
|
|
5032
|
+
* and animation.
|
|
4767
5033
|
*
|
|
4768
|
-
* Runtime
|
|
4769
|
-
* .is-sheet marks "auto" resolved to bottom-sheet presentation on coarse
|
|
4770
|
-
* pointers. Authors opt into mobile behavior with
|
|
4771
|
-
* [data-flyout-mobile="sheet|auto|none|anchored"].
|
|
5034
|
+
* Runtime marker (managed by surface.js): .is-open marks an open flyout.
|
|
4772
5035
|
* --surface-anchor-width is set by surface.js while an open flyout is positioned.
|
|
4773
5036
|
*
|
|
4774
5037
|
* Public hooks:
|
|
@@ -4777,7 +5040,7 @@ dialog.drawer > form > header {
|
|
|
4777
5040
|
*
|
|
4778
5041
|
* Internal:
|
|
4779
5042
|
* --menu-item-size (derived from --control-size for menu rows)
|
|
4780
|
-
* --available-height (measured by
|
|
5043
|
+
* --available-height (measured by the positioner)
|
|
4781
5044
|
* --surface-anchor-width (measured by surface.js)
|
|
4782
5045
|
*/
|
|
4783
5046
|
|
|
@@ -4804,8 +5067,8 @@ dialog.drawer > form > header {
|
|
|
4804
5067
|
--menu-item-size: var(--control-size);
|
|
4805
5068
|
z-index: var(--z-menu);
|
|
4806
5069
|
/* No-JS fallback: below the trigger inside the relative .flyout-trigger.
|
|
4807
|
-
surface.js
|
|
4808
|
-
|
|
5070
|
+
surface.js replaces this with the anchor's coupled position/coordinate
|
|
5071
|
+
mode; this only covers a flyout revealed without the runtime. */
|
|
4809
5072
|
position: absolute;
|
|
4810
5073
|
inset-block-start: calc(100% + var(--space-10));
|
|
4811
5074
|
inset-inline-start: 0;
|
|
@@ -4833,73 +5096,63 @@ dialog.drawer > form > header {
|
|
|
4833
5096
|
box-shadow: var(--shadow-popout);
|
|
4834
5097
|
list-style: none;
|
|
4835
5098
|
opacity: 1;
|
|
4836
|
-
/*
|
|
4837
|
-
|
|
4838
|
-
below adds its own translate motion. */
|
|
5099
|
+
/* Flyouts fade in and out; scaling around the center would detach the
|
|
5100
|
+
anchored edge from its trigger on wide panels. */
|
|
4839
5101
|
transition: opacity var(--duration-fast) ease;
|
|
4840
5102
|
}
|
|
4841
5103
|
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
opacity: 0;
|
|
4849
|
-
}
|
|
4850
|
-
}
|
|
4851
|
-
|
|
4852
|
-
/* Bottom-sheet presentation. surface.js toggles .is-sheet when the flyout is
|
|
4853
|
-
opened on coarse pointer / narrow viewports. */
|
|
4854
|
-
.flyout.is-sheet {
|
|
4855
|
-
--menu-item-size: var(--control-size-lg);
|
|
5104
|
+
/* A popover enters the top layer with UA geometry that conflicts with anchored
|
|
5105
|
+
positioning. surface.js sets popover="manual" on every surface it manages,
|
|
5106
|
+
so this is the transport's own geometry rather than an opt-in. Kept scoped to
|
|
5107
|
+
Actual surfaces so bare [popover] elements retain their platform
|
|
5108
|
+
presentation. */
|
|
5109
|
+
.flyout[popover] {
|
|
4856
5110
|
position: fixed;
|
|
4857
|
-
inset
|
|
4858
|
-
|
|
4859
|
-
inset-block-end: 0;
|
|
4860
|
-
inline-size: auto;
|
|
4861
|
-
min-inline-size: 0;
|
|
4862
|
-
max-inline-size: none;
|
|
4863
|
-
max-block-size: min(calc(var(--viewport-block) * 0.75), 32rem);
|
|
4864
|
-
overflow: auto;
|
|
4865
|
-
grid-template-columns: 1fr;
|
|
4866
|
-
padding: max(var(--space-30), env(safe-area-inset-top)) var(--space-30)
|
|
4867
|
-
max(var(--space-30), env(safe-area-inset-bottom));
|
|
4868
|
-
border-end-start-radius: 0;
|
|
4869
|
-
border-end-end-radius: 0;
|
|
4870
|
-
/* Sheet motion is a directional slide from the viewport edge; it replaces
|
|
4871
|
-
the base fade-only recipe. */
|
|
4872
|
-
transition:
|
|
4873
|
-
opacity var(--duration-fast) ease,
|
|
4874
|
-
translate var(--duration-fast) ease;
|
|
5111
|
+
inset: auto;
|
|
5112
|
+
margin: 0;
|
|
4875
5113
|
}
|
|
4876
5114
|
|
|
4877
|
-
|
|
5115
|
+
/* A presentation class may own its *visible* display type, but must not
|
|
5116
|
+
override the platform's hidden state. The UA closes a popover with
|
|
5117
|
+
display: none, and an author declaration beats the UA origin whatever the
|
|
5118
|
+
specificity — so .flyout's own display: grid would leave a closed popover
|
|
5119
|
+
painted at its static position. The opacity is the exit target: with nothing
|
|
5120
|
+
to animate, the overlay/display allow-discrete transition below would hold a
|
|
5121
|
+
fully opaque panel in the top layer and then snap it away. */
|
|
5122
|
+
.flyout[popover]:not(:popover-open) {
|
|
4878
5123
|
display: none;
|
|
4879
5124
|
opacity: 0;
|
|
4880
5125
|
}
|
|
4881
5126
|
|
|
4882
|
-
.
|
|
4883
|
-
|
|
5127
|
+
/* Both lifecycles in one rule: .is-open for a surface.js panel, :popover-open
|
|
5128
|
+
for one an author drives natively with popovertarget. */
|
|
5129
|
+
.flyout:is(.is-open, :popover-open) {
|
|
5130
|
+
inset: auto;
|
|
4884
5131
|
}
|
|
4885
5132
|
|
|
5133
|
+
/* @starting-style is above the floor (Safari 17.5), so where it is missing the
|
|
5134
|
+
entry simply has no animated origin and the panel appears at full opacity. */
|
|
4886
5135
|
@starting-style {
|
|
4887
|
-
.flyout
|
|
4888
|
-
|
|
5136
|
+
.flyout:is(.is-open, :popover-open) {
|
|
5137
|
+
opacity: 0;
|
|
4889
5138
|
}
|
|
4890
5139
|
}
|
|
4891
5140
|
|
|
5141
|
+
.flyout[hidden] {
|
|
5142
|
+
display: none;
|
|
5143
|
+
opacity: 0;
|
|
5144
|
+
}
|
|
5145
|
+
|
|
5146
|
+
/* display keeps the exit transition alive for the [hidden] path; overlay does
|
|
5147
|
+
the same for the native popover path, where hidePopover() would otherwise
|
|
5148
|
+
drop the panel out of the top layer on the first frame and let a parent
|
|
5149
|
+
overflow or a dialog clip the fade. An engine that cannot transition either
|
|
5150
|
+
property ignores it and the exit is instant. */
|
|
4892
5151
|
@supports (transition-behavior: allow-discrete) {
|
|
4893
5152
|
.flyout {
|
|
4894
5153
|
transition:
|
|
4895
5154
|
opacity var(--duration-fast) ease,
|
|
4896
|
-
|
|
4897
|
-
}
|
|
4898
|
-
|
|
4899
|
-
.flyout.is-sheet {
|
|
4900
|
-
transition:
|
|
4901
|
-
opacity var(--duration-fast) ease,
|
|
4902
|
-
translate var(--duration-fast) ease,
|
|
5155
|
+
overlay var(--duration-fast) allow-discrete,
|
|
4903
5156
|
display var(--duration-fast) allow-discrete;
|
|
4904
5157
|
}
|
|
4905
5158
|
}
|
|
@@ -4917,7 +5170,7 @@ dialog.drawer > form > header {
|
|
|
4917
5170
|
margin: 0;
|
|
4918
5171
|
color: var(--text-muted);
|
|
4919
5172
|
font-size: var(--font-size-xs);
|
|
4920
|
-
font-weight: var(--font-weight-
|
|
5173
|
+
font-weight: var(--font-weight-semibold);
|
|
4921
5174
|
}
|
|
4922
5175
|
|
|
4923
5176
|
.flyout > section > ul {
|
|
@@ -4935,11 +5188,6 @@ dialog.drawer > form > header {
|
|
|
4935
5188
|
/* Vertical dividers — .menu-separator (menu.css) is the canonical way to
|
|
4936
5189
|
separate menu items. Flyout panels that are not menus use auto grid gap
|
|
4937
5190
|
declared on .flyout itself. */
|
|
4938
|
-
@media print {
|
|
4939
|
-
.flyout {
|
|
4940
|
-
display: none !important;
|
|
4941
|
-
}
|
|
4942
|
-
}
|
|
4943
5191
|
|
|
4944
5192
|
/*
|
|
4945
5193
|
* Menu — action list primitive consumed by flyout and context-menu.
|
|
@@ -4987,6 +5235,7 @@ dialog.drawer > form > header {
|
|
|
4987
5235
|
background: transparent;
|
|
4988
5236
|
color: var(--intent, var(--text));
|
|
4989
5237
|
font: inherit;
|
|
5238
|
+
font-size: var(--control-font-size);
|
|
4990
5239
|
text-align: start;
|
|
4991
5240
|
text-decoration: none;
|
|
4992
5241
|
white-space: nowrap;
|
|
@@ -5018,30 +5267,36 @@ dialog.drawer > form > header {
|
|
|
5018
5267
|
/* Checkable menu items are one command with one accessible state. Their
|
|
5019
5268
|
indicator is generated so authors do not add a second interactive input. */
|
|
5020
5269
|
.menu-item:is([role="menuitemcheckbox"], [role="menuitemradio"])::before {
|
|
5021
|
-
|
|
5270
|
+
content: "";
|
|
5271
|
+
display: block;
|
|
5022
5272
|
flex: 0 0 var(--menu-item-icon-size);
|
|
5023
5273
|
inline-size: var(--menu-item-icon-size);
|
|
5024
|
-
|
|
5025
|
-
}
|
|
5026
|
-
|
|
5027
|
-
.menu-item[role="menuitemcheckbox"]::before {
|
|
5028
|
-
content: "";
|
|
5274
|
+
block-size: var(--menu-item-icon-size);
|
|
5029
5275
|
}
|
|
5030
5276
|
|
|
5031
5277
|
.menu-item[role="menuitemcheckbox"][aria-checked="true"]::before {
|
|
5032
|
-
|
|
5278
|
+
background: currentColor;
|
|
5279
|
+
-webkit-mask: var(--icon-check) center / contain no-repeat;
|
|
5280
|
+
mask: var(--icon-check) center / contain no-repeat;
|
|
5033
5281
|
}
|
|
5034
5282
|
|
|
5035
5283
|
.menu-item[role="menuitemcheckbox"][aria-checked="mixed"]::before {
|
|
5036
|
-
|
|
5284
|
+
background:
|
|
5285
|
+
linear-gradient(currentColor 0 0) center / 60% max(var(--border-width), 0.125em) no-repeat;
|
|
5037
5286
|
}
|
|
5038
5287
|
|
|
5039
5288
|
.menu-item[role="menuitemradio"]::before {
|
|
5040
|
-
|
|
5289
|
+
background: radial-gradient(circle, transparent 0 46%, currentColor 48% 58%, transparent 60%);
|
|
5041
5290
|
}
|
|
5042
5291
|
|
|
5043
5292
|
.menu-item[role="menuitemradio"][aria-checked="true"]::before {
|
|
5044
|
-
|
|
5293
|
+
background: radial-gradient(
|
|
5294
|
+
circle,
|
|
5295
|
+
currentColor 0 25%,
|
|
5296
|
+
transparent 27% 46%,
|
|
5297
|
+
currentColor 48% 58%,
|
|
5298
|
+
transparent 60%
|
|
5299
|
+
);
|
|
5045
5300
|
}
|
|
5046
5301
|
|
|
5047
5302
|
.menu-item:focus-visible {
|
|
@@ -5061,7 +5316,7 @@ dialog.drawer > form > header {
|
|
|
5061
5316
|
padding: var(--space-20);
|
|
5062
5317
|
color: var(--text-muted);
|
|
5063
5318
|
font-size: var(--font-size-xs);
|
|
5064
|
-
font-weight: var(--font-weight-
|
|
5319
|
+
font-weight: var(--font-weight-semibold);
|
|
5065
5320
|
}
|
|
5066
5321
|
|
|
5067
5322
|
.menu-separator {
|
|
@@ -5073,9 +5328,13 @@ dialog.drawer > form > header {
|
|
|
5073
5328
|
/*
|
|
5074
5329
|
* Tooltip — supplemental label overlaid near a trigger.
|
|
5075
5330
|
*
|
|
5076
|
-
* Position, placement, and arrow position are managed by JavaScript
|
|
5077
|
-
*
|
|
5078
|
-
*
|
|
5331
|
+
* Position, placement, and arrow position are managed by JavaScript via left,
|
|
5332
|
+
* top, data-placement, and --arrow-x. The runtime also owns `position`: it
|
|
5333
|
+
* writes `absolute` for a tip whose trigger scrolls with the page, so the
|
|
5334
|
+
* browser carries the tip along instead of the positioner correcting it a
|
|
5335
|
+
* frame later, and `fixed` — the declaration below — for every other trigger.
|
|
5336
|
+
* Visibility is the Popover lifecycle: tooltip.js promotes the tip to the top
|
|
5337
|
+
* layer with popover="manual". CSS owns entry and exit animation.
|
|
5079
5338
|
*
|
|
5080
5339
|
* Public hooks:
|
|
5081
5340
|
* --tooltip-bg
|
|
@@ -5083,8 +5342,8 @@ dialog.drawer > form > header {
|
|
|
5083
5342
|
* --tooltip-arrow-size
|
|
5084
5343
|
*
|
|
5085
5344
|
* Internal:
|
|
5086
|
-
* --arrow-x (arrow position, written by
|
|
5087
|
-
* --arrow-y (arrow position, written by
|
|
5345
|
+
* --arrow-x (arrow position, written by the positioner)
|
|
5346
|
+
* --arrow-y (arrow position, written by the positioner)
|
|
5088
5347
|
*/
|
|
5089
5348
|
.tooltip {
|
|
5090
5349
|
--tooltip-bg: var(--surface-solid);
|
|
@@ -5092,7 +5351,6 @@ dialog.drawer > form > header {
|
|
|
5092
5351
|
--tooltip-arrow-size: 5px;
|
|
5093
5352
|
|
|
5094
5353
|
position: fixed;
|
|
5095
|
-
z-index: var(--z-tooltip);
|
|
5096
5354
|
max-inline-size: min(24rem, calc(var(--viewport-inline) - 2rem));
|
|
5097
5355
|
padding: 0.25em 0.6em;
|
|
5098
5356
|
border-radius: var(--radius-sm);
|
|
@@ -5111,21 +5369,56 @@ dialog.drawer > form > header {
|
|
|
5111
5369
|
transition: opacity var(--duration-fast) ease;
|
|
5112
5370
|
}
|
|
5113
5371
|
|
|
5372
|
+
/* Neutralize only the native geometry that conflicts with a positioned
|
|
5373
|
+
tooltip. In particular, the UA overflow would turn the arrow into scroll
|
|
5374
|
+
overflow instead of letting it project beyond the bubble.
|
|
5375
|
+
|
|
5376
|
+
One rule for both lifecycles: the runtime writes popover="manual" on the
|
|
5377
|
+
tips it manages, and an application driving a .tooltip[popover] itself lands
|
|
5378
|
+
on the same declarations. Who owns which is in
|
|
5379
|
+
docs/pages/enhancements/overview.md. */
|
|
5380
|
+
.tooltip[popover] {
|
|
5381
|
+
inset: auto;
|
|
5382
|
+
margin: 0;
|
|
5383
|
+
overflow: visible;
|
|
5384
|
+
}
|
|
5385
|
+
|
|
5386
|
+
/* The closed state, for either lifecycle. .tooltip declares no display of its
|
|
5387
|
+
own so the UA would already hide it, but the opacity target is what makes
|
|
5388
|
+
the exit fade at all — see the overlay note below. */
|
|
5389
|
+
.tooltip[popover]:not(:popover-open) {
|
|
5390
|
+
display: none;
|
|
5391
|
+
opacity: 0;
|
|
5392
|
+
}
|
|
5393
|
+
|
|
5394
|
+
/* The state before enhancement, and the author's to write — not the runtime's.
|
|
5395
|
+
Wiring is lazy: tooltip.js adopts an explicit tip on its trigger's first
|
|
5396
|
+
hover, so a tooltip nobody hovers keeps [hidden] for the whole life of the
|
|
5397
|
+
page. Without this rule it would sit on screen at full opacity until then.
|
|
5398
|
+
The runtime removes the attribute at the moment it takes the lifecycle over,
|
|
5399
|
+
because an author declaration outranks the platform's own hidden state. */
|
|
5114
5400
|
.tooltip[hidden] {
|
|
5115
5401
|
display: none;
|
|
5116
5402
|
opacity: 0;
|
|
5117
5403
|
}
|
|
5118
5404
|
|
|
5405
|
+
/* Entry opacity, for either lifecycle — both are :popover-open.
|
|
5406
|
+
@starting-style is above the floor (Safari 17.5); where it is missing the
|
|
5407
|
+
tip simply appears at full opacity. */
|
|
5119
5408
|
@starting-style {
|
|
5120
|
-
.tooltip:
|
|
5409
|
+
.tooltip:popover-open {
|
|
5121
5410
|
opacity: 0;
|
|
5122
5411
|
}
|
|
5123
5412
|
}
|
|
5124
5413
|
|
|
5414
|
+
/* overlay is the native popover counterpart of display: it holds the tip in
|
|
5415
|
+
the top layer until the fade finishes, instead of letting hidePopover()
|
|
5416
|
+
demote it on the first frame. Ignored where unsupported. */
|
|
5125
5417
|
@supports (transition-behavior: allow-discrete) {
|
|
5126
5418
|
.tooltip {
|
|
5127
5419
|
transition:
|
|
5128
5420
|
opacity var(--duration-fast) ease,
|
|
5421
|
+
overlay var(--duration-fast) allow-discrete,
|
|
5129
5422
|
display var(--duration-fast) allow-discrete;
|
|
5130
5423
|
}
|
|
5131
5424
|
}
|
|
@@ -5172,12 +5465,6 @@ dialog.drawer > form > header {
|
|
|
5172
5465
|
clip-path: polygon(0 50%, 100% 0, 100% 100%);
|
|
5173
5466
|
}
|
|
5174
5467
|
|
|
5175
|
-
@media print {
|
|
5176
|
-
.tooltip {
|
|
5177
|
-
display: none !important;
|
|
5178
|
-
}
|
|
5179
|
-
}
|
|
5180
|
-
|
|
5181
5468
|
|
|
5182
5469
|
/* Disclosure */
|
|
5183
5470
|
/*
|
|
@@ -5187,6 +5474,12 @@ dialog.drawer > form > header {
|
|
|
5187
5474
|
* or demo styles. Decorative palettes and custom markers belong outside the
|
|
5188
5475
|
* base component.
|
|
5189
5476
|
*
|
|
5477
|
+
* Container treatments (structural chrome, not skins):
|
|
5478
|
+
* - default — the common raised shell;
|
|
5479
|
+
* - .flush — the shell removed, only the item separators remain, so the group
|
|
5480
|
+
* can sit directly inside an existing surface (card, drawer, page section);
|
|
5481
|
+
* - .separated — each item becomes its own card.
|
|
5482
|
+
*
|
|
5190
5483
|
* Public hooks:
|
|
5191
5484
|
* --accordion-radius
|
|
5192
5485
|
* --accordion-marker-color
|
|
@@ -5212,11 +5505,14 @@ dialog.drawer > form > header {
|
|
|
5212
5505
|
list-style: none;
|
|
5213
5506
|
padding: var(--space-40);
|
|
5214
5507
|
color: var(--text);
|
|
5215
|
-
font-weight: var(--font-weight-
|
|
5508
|
+
font-weight: var(--font-weight-medium);
|
|
5216
5509
|
}
|
|
5217
5510
|
|
|
5218
|
-
.accordion summary:hover {
|
|
5219
|
-
|
|
5511
|
+
.accordion summary:hover::after {
|
|
5512
|
+
/* The whole row is one focus target; reinforcing only the end marker keeps
|
|
5513
|
+
the affordance on the disclosure control instead of making the whole
|
|
5514
|
+
label read as a link. */
|
|
5515
|
+
--accordion-marker-color: currentColor;
|
|
5220
5516
|
}
|
|
5221
5517
|
|
|
5222
5518
|
.accordion summary::-webkit-details-marker {
|
|
@@ -5260,6 +5556,36 @@ dialog.drawer > form > header {
|
|
|
5260
5556
|
padding-inline-start: 2rem;
|
|
5261
5557
|
}
|
|
5262
5558
|
|
|
5559
|
+
/* Flush — the shell is dropped and only the shared item separators remain.
|
|
5560
|
+
Zero inline padding puts the group edge to edge inside the surface that
|
|
5561
|
+
already frames it, so no override is repeated per embedding. */
|
|
5562
|
+
.accordion.flush {
|
|
5563
|
+
border: 0;
|
|
5564
|
+
border-radius: 0;
|
|
5565
|
+
background: transparent;
|
|
5566
|
+
}
|
|
5567
|
+
|
|
5568
|
+
.accordion.flush summary,
|
|
5569
|
+
.accordion.flush details > summary + * {
|
|
5570
|
+
padding-inline: 0;
|
|
5571
|
+
}
|
|
5572
|
+
|
|
5573
|
+
/* Separated — each item owns a full card. The item's own border beats the
|
|
5574
|
+
shared details + details separator on specificity alone, so nothing needs
|
|
5575
|
+
undoing for the second item onwards. */
|
|
5576
|
+
.accordion.separated {
|
|
5577
|
+
display: grid;
|
|
5578
|
+
gap: var(--space-20);
|
|
5579
|
+
border: 0;
|
|
5580
|
+
background: transparent;
|
|
5581
|
+
}
|
|
5582
|
+
|
|
5583
|
+
.accordion.separated > details {
|
|
5584
|
+
border: var(--border-width) solid var(--border);
|
|
5585
|
+
border-radius: var(--accordion-radius);
|
|
5586
|
+
background: var(--surface-raised);
|
|
5587
|
+
}
|
|
5588
|
+
|
|
5263
5589
|
@media (forced-colors: active) {
|
|
5264
5590
|
.accordion {
|
|
5265
5591
|
--accordion-marker-color: CanvasText;
|
|
@@ -5301,7 +5627,6 @@ dialog.drawer > form > header {
|
|
|
5301
5627
|
border-inline-end-color: var(--spinner-value);
|
|
5302
5628
|
border-radius: var(--radius-full);
|
|
5303
5629
|
opacity: 0.85;
|
|
5304
|
-
animation: actual-spin var(--duration-spin) linear infinite;
|
|
5305
5630
|
vertical-align: text-bottom;
|
|
5306
5631
|
}
|
|
5307
5632
|
|
|
@@ -5322,16 +5647,17 @@ dialog.drawer > form > header {
|
|
|
5322
5647
|
--spinner-width: 3px;
|
|
5323
5648
|
}
|
|
5324
5649
|
|
|
5650
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
5651
|
+
.spinner {
|
|
5652
|
+
animation: actual-spin var(--duration-spin) linear infinite;
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5325
5656
|
@keyframes actual-spin {
|
|
5326
5657
|
to {
|
|
5327
5658
|
transform: rotate(360deg);
|
|
5328
5659
|
}
|
|
5329
5660
|
}
|
|
5330
|
-
@media print {
|
|
5331
|
-
.spinner {
|
|
5332
|
-
display: none !important;
|
|
5333
|
-
}
|
|
5334
|
-
}
|
|
5335
5661
|
|
|
5336
5662
|
/*
|
|
5337
5663
|
* Skeleton placeholders for loading states.
|
|
@@ -5662,11 +5988,6 @@ dialog.drawer > form > header {
|
|
|
5662
5988
|
translate var(--duration-slow) var(--ease-enter),
|
|
5663
5989
|
background-color var(--duration) ease;
|
|
5664
5990
|
}
|
|
5665
|
-
@media print {
|
|
5666
|
-
.status-bar {
|
|
5667
|
-
display: none !important;
|
|
5668
|
-
}
|
|
5669
|
-
}
|
|
5670
5991
|
|
|
5671
5992
|
|
|
5672
5993
|
/* Display */
|
|
@@ -5718,7 +6039,7 @@ dialog.drawer > form > header {
|
|
|
5718
6039
|
color: var(--avatar-fg);
|
|
5719
6040
|
font: inherit;
|
|
5720
6041
|
font-size: var(--avatar-font-size);
|
|
5721
|
-
font-weight: var(--font-weight-
|
|
6042
|
+
font-weight: var(--font-weight-semibold);
|
|
5722
6043
|
line-height: 1;
|
|
5723
6044
|
text-decoration: none;
|
|
5724
6045
|
vertical-align: middle;
|
|
@@ -5832,6 +6153,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
5832
6153
|
transform: translate(var(--avatar-status-translate-x), 50%);
|
|
5833
6154
|
}
|
|
5834
6155
|
|
|
6156
|
+
/* Safari before 16.4 has no :dir(); the ancestor-attribute path is the
|
|
6157
|
+
Degraded-tier fallback and the :dir() rules are dropped on their own there.
|
|
6158
|
+
:dir() follows inherited directionality, so the explicit ltr rule
|
|
6159
|
+
neutralizes an outer RTL fallback for a nested direction change. */
|
|
6160
|
+
[dir="rtl"] .avatar > .badge:empty {
|
|
6161
|
+
--avatar-status-translate-x: -50%;
|
|
6162
|
+
}
|
|
6163
|
+
|
|
6164
|
+
.avatar:dir(ltr) > .badge:empty {
|
|
6165
|
+
--avatar-status-translate-x: 50%;
|
|
6166
|
+
}
|
|
6167
|
+
|
|
5835
6168
|
.avatar:dir(rtl) > .badge:empty {
|
|
5836
6169
|
--avatar-status-translate-x: -50%;
|
|
5837
6170
|
}
|
|
@@ -5920,10 +6253,31 @@ a.avatar[aria-disabled="true"] {
|
|
|
5920
6253
|
inset-block-end: 0;
|
|
5921
6254
|
}
|
|
5922
6255
|
|
|
6256
|
+
/* Safari before 16.4 has no :dir(); the ancestor-attribute path is the
|
|
6257
|
+
Degraded-tier fallback and the :dir() rules are dropped on their own there.
|
|
6258
|
+
:dir() follows inherited directionality, so the explicit ltr rules
|
|
6259
|
+
neutralize an outer RTL fallback for a nested direction change. The rules
|
|
6260
|
+
stay grouped by specificity so the fallback never follows the :dir() rules. */
|
|
6261
|
+
[dir="rtl"] .indicator-item {
|
|
6262
|
+
--indicator-translate-x: -50%;
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
.indicator-item:dir(ltr) {
|
|
6266
|
+
--indicator-translate-x: 50%;
|
|
6267
|
+
}
|
|
6268
|
+
|
|
5923
6269
|
.indicator-item:dir(rtl) {
|
|
5924
6270
|
--indicator-translate-x: -50%;
|
|
5925
6271
|
}
|
|
5926
6272
|
|
|
6273
|
+
[dir="rtl"] .indicator-item.start {
|
|
6274
|
+
--indicator-translate-x: 50%;
|
|
6275
|
+
}
|
|
6276
|
+
|
|
6277
|
+
.indicator-item.start:dir(ltr) {
|
|
6278
|
+
--indicator-translate-x: -50%;
|
|
6279
|
+
}
|
|
6280
|
+
|
|
5927
6281
|
.indicator-item.start:dir(rtl) {
|
|
5928
6282
|
--indicator-translate-x: 50%;
|
|
5929
6283
|
}
|
|
@@ -5958,6 +6312,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
5958
6312
|
}
|
|
5959
6313
|
/* @sync key-recipe */
|
|
5960
6314
|
|
|
6315
|
+
/* .key has a compact typographic default of its own, so its local size roles
|
|
6316
|
+
step around that default instead of inheriting the generic mapping. */
|
|
6317
|
+
.key.sm {
|
|
6318
|
+
font-size: var(--font-size-xs);
|
|
6319
|
+
}
|
|
6320
|
+
|
|
6321
|
+
.key.lg {
|
|
6322
|
+
font-size: var(--font-size-md);
|
|
6323
|
+
}
|
|
6324
|
+
|
|
5961
6325
|
/*
|
|
5962
6326
|
* Message rows composed from an optional avatar, metadata, and bubble.
|
|
5963
6327
|
*
|
|
@@ -6022,6 +6386,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6022
6386
|
:where(.chat-bubble) {
|
|
6023
6387
|
--intent: initial;
|
|
6024
6388
|
--intent-fg: initial;
|
|
6389
|
+
--intent-soft-fg: initial;
|
|
6025
6390
|
--ui-bg: initial;
|
|
6026
6391
|
--ui-fg: initial;
|
|
6027
6392
|
--ui-border: initial;
|
|
@@ -6187,12 +6552,6 @@ a.avatar[aria-disabled="true"] {
|
|
|
6187
6552
|
transform: none;
|
|
6188
6553
|
}
|
|
6189
6554
|
|
|
6190
|
-
@media print {
|
|
6191
|
-
.fab {
|
|
6192
|
-
display: none;
|
|
6193
|
-
}
|
|
6194
|
-
}
|
|
6195
|
-
|
|
6196
6555
|
/*
|
|
6197
6556
|
* Rating — integer star rating drawn on native radio inputs.
|
|
6198
6557
|
*
|
|
@@ -6305,23 +6664,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
6305
6664
|
*
|
|
6306
6665
|
* `.steps` is what a step *is* — the counter, the marker, `.step-label`,
|
|
6307
6666
|
* `.step-complete`, `aria-current="step"`. It carries no orientation, so it is
|
|
6308
|
-
* never written alone.
|
|
6309
|
-
*
|
|
6310
|
-
* has to exclude, so no rule says `:not(...)` and no rule undoes another's
|
|
6311
|
-
* layout.
|
|
6667
|
+
* never written alone. The two orientations are peers: no rule says
|
|
6668
|
+
* `:not(...)` and no rule undoes another's layout.
|
|
6312
6669
|
*
|
|
6313
|
-
* Orientation and labels are independent
|
|
6314
|
-
*
|
|
6315
|
-
* one:
|
|
6670
|
+
* Orientation and labels are independent. `.step-label` is optional in either;
|
|
6671
|
+
* when a sequence carries names, every step carries one:
|
|
6316
6672
|
*
|
|
6317
6673
|
* empty steps .step-label on every step
|
|
6318
6674
|
* horizontal compact progression named sequence, stacked or inline
|
|
6319
6675
|
* vertical compact rail named sequence, labels beside markers
|
|
6320
6676
|
*
|
|
6321
|
-
* An empty step is a marker and nothing else, so it is compact by structure —
|
|
6322
|
-
* `> li:empty` in both orientations, no variant class, and nothing authored
|
|
6323
|
-
* only to be hidden again. The page around it owns the step name.
|
|
6324
|
-
*
|
|
6325
6677
|
* A label may be an `a[href]`, which is the whole interactive surface: the
|
|
6326
6678
|
* marker is never navigable, so navigating a flow means naming its
|
|
6327
6679
|
* destinations. Anything else focusable in a step is out of contract.
|
|
@@ -6392,7 +6744,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6392
6744
|
background: var(--step-marker-bg);
|
|
6393
6745
|
color: var(--step-marker-fg);
|
|
6394
6746
|
font-size: var(--font-size-sm);
|
|
6395
|
-
font-weight: var(--font-weight-
|
|
6747
|
+
font-weight: var(--font-weight-semibold);
|
|
6396
6748
|
line-height: 1;
|
|
6397
6749
|
}
|
|
6398
6750
|
|
|
@@ -6452,11 +6804,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6452
6804
|
border-width: calc(var(--border-width) * 2);
|
|
6453
6805
|
}
|
|
6454
6806
|
|
|
6455
|
-
/*
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
font-weight: var(--font-weight-strong);
|
|
6459
|
-
}
|
|
6807
|
+
/* No label weight change for current: the accented marker with its doubled
|
|
6808
|
+
border already carries the state, and selection never changes text metrics.
|
|
6809
|
+
Complete needs none either: its filled disc already reads as strongly. */
|
|
6460
6810
|
|
|
6461
6811
|
.steps > .step-complete:not([aria-current="step"])::before {
|
|
6462
6812
|
content: var(--step-complete-mark, counter(actual-step));
|
|
@@ -6514,8 +6864,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
6514
6864
|
`:empty` says exactly what the markup says, the way `.badge:empty` does, and
|
|
6515
6865
|
it reads the item's own content: bare text in an `<li>` keeps a reading width
|
|
6516
6866
|
like its wrapped peers, where `> li:has(.step-label)` would give it a marker
|
|
6517
|
-
floor and skew the row.
|
|
6518
|
-
|
|
6867
|
+
floor and skew the row. `:empty` also keeps the compact form reachable on the
|
|
6868
|
+
Degraded tier, where only the inline bonus needs `:has()`. */
|
|
6519
6869
|
.steps-horizontal > li:empty {
|
|
6520
6870
|
flex: 1 1 0;
|
|
6521
6871
|
justify-items: start;
|
|
@@ -6546,48 +6896,43 @@ a.avatar[aria-disabled="true"] {
|
|
|
6546
6896
|
}
|
|
6547
6897
|
|
|
6548
6898
|
/*
|
|
6549
|
-
* The one horizontal container query
|
|
6550
|
-
*
|
|
6551
|
-
*
|
|
6552
|
-
*
|
|
6553
|
-
*
|
|
6554
|
-
*
|
|
6555
|
-
*
|
|
6556
|
-
* Horizontal is a 2-5 step component. Outside that domain the rule simply
|
|
6557
|
-
* applies as written, because a row that is out of contract is not owed a
|
|
6558
|
-
* curated fallback.
|
|
6899
|
+
* The one horizontal container query: past 60rem a labelled row can put marker
|
|
6900
|
+
* and label inline and still leave the connector a real region between groups.
|
|
6901
|
+
* It counts nothing, so it serves every count; below it, stacked. A marker row
|
|
6902
|
+
* is already the shape this query produces and stays out of it.
|
|
6903
|
+
*
|
|
6904
|
+
* Horizontal is a 2-5 step component. Outside that domain the rule applies as
|
|
6905
|
+
* written — a row out of contract is not owed a curated fallback.
|
|
6559
6906
|
*/
|
|
6560
|
-
@
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
}
|
|
6907
|
+
@container actual-container (inline-size >= 60rem) {
|
|
6908
|
+
.steps-horizontal:has(.step-label) > li {
|
|
6909
|
+
display: flex;
|
|
6910
|
+
flex: 1 1 auto;
|
|
6911
|
+
align-items: center;
|
|
6912
|
+
gap: var(--step-inline-gap);
|
|
6913
|
+
min-inline-size: 0;
|
|
6914
|
+
text-align: start;
|
|
6915
|
+
white-space: nowrap;
|
|
6916
|
+
}
|
|
6571
6917
|
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6918
|
+
.steps-horizontal:has(.step-label) > li::after {
|
|
6919
|
+
position: static;
|
|
6920
|
+
flex: 1 1 2rem;
|
|
6921
|
+
inline-size: auto;
|
|
6922
|
+
block-size: var(--step-size);
|
|
6923
|
+
margin-inline-end: var(--step-inline-gap);
|
|
6924
|
+
background: var(
|
|
6925
|
+
--step-inline-connector,
|
|
6926
|
+
var(
|
|
6927
|
+
--step-connector,
|
|
6928
|
+
linear-gradient(var(--step-line), var(--step-line)) center /
|
|
6929
|
+
100% var(--step-line-size) no-repeat
|
|
6930
|
+
)
|
|
6931
|
+
);
|
|
6932
|
+
}
|
|
6587
6933
|
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
}
|
|
6934
|
+
.steps-horizontal:has(.step-label) > li:last-child {
|
|
6935
|
+
flex: 0 0 auto;
|
|
6591
6936
|
}
|
|
6592
6937
|
}
|
|
6593
6938
|
|
|
@@ -6663,8 +7008,12 @@ a.avatar[aria-disabled="true"] {
|
|
|
6663
7008
|
* Public hooks:
|
|
6664
7009
|
* --app-nav-size
|
|
6665
7010
|
* --app-nav-gap
|
|
7011
|
+
* --app-nav-icon-size
|
|
6666
7012
|
*/
|
|
6667
7013
|
:where(.app-nav) {
|
|
7014
|
+
--intent: initial;
|
|
7015
|
+
--intent-fg: initial;
|
|
7016
|
+
--intent-soft-fg: initial;
|
|
6668
7017
|
--ui-bg: initial;
|
|
6669
7018
|
--ui-fg: initial;
|
|
6670
7019
|
--ui-border: initial;
|
|
@@ -6673,6 +7022,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6673
7022
|
.app-nav {
|
|
6674
7023
|
--app-nav-size: 4rem;
|
|
6675
7024
|
--app-nav-gap: var(--space-10);
|
|
7025
|
+
--app-nav-icon-size: 1.5rem;
|
|
6676
7026
|
|
|
6677
7027
|
position: sticky;
|
|
6678
7028
|
inset-block-end: 0;
|
|
@@ -6709,9 +7059,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
6709
7059
|
}
|
|
6710
7060
|
|
|
6711
7061
|
.app-nav > a > :where(svg, img, [aria-hidden="true"]) {
|
|
6712
|
-
inline-size:
|
|
6713
|
-
block-size:
|
|
6714
|
-
font-size:
|
|
7062
|
+
inline-size: var(--app-nav-icon-size);
|
|
7063
|
+
block-size: var(--app-nav-icon-size);
|
|
7064
|
+
font-size: var(--app-nav-icon-size);
|
|
6715
7065
|
line-height: 1;
|
|
6716
7066
|
}
|
|
6717
7067
|
|
|
@@ -6720,15 +7070,20 @@ a.avatar[aria-disabled="true"] {
|
|
|
6720
7070
|
color: var(--text);
|
|
6721
7071
|
}
|
|
6722
7072
|
|
|
6723
|
-
|
|
7073
|
+
/* Current tile is already a full selected surface. Selection never changes
|
|
7074
|
+
text metrics: the shared medium weight is kept, no permanent indicator is
|
|
7075
|
+
added here. */
|
|
7076
|
+
.app-nav > a[aria-current="page"] {
|
|
6724
7077
|
background: var(--state-selected);
|
|
6725
7078
|
color: var(--state-selected-fg);
|
|
6726
|
-
font-weight: var(--font-weight-bold);
|
|
6727
7079
|
}
|
|
6728
7080
|
|
|
6729
|
-
@media
|
|
6730
|
-
|
|
6731
|
-
|
|
7081
|
+
@media (forced-colors: active) {
|
|
7082
|
+
/* --state-selected flattens to a UA color that no longer distinguishes the
|
|
7083
|
+
current tile from the rest of the bar, so paint the system Highlight pair. */
|
|
7084
|
+
.app-nav > a[aria-current="page"] {
|
|
7085
|
+
background: Highlight;
|
|
7086
|
+
color: HighlightText;
|
|
6732
7087
|
}
|
|
6733
7088
|
}
|
|
6734
7089
|
|
|
@@ -6743,8 +7098,15 @@ a.avatar[aria-disabled="true"] {
|
|
|
6743
7098
|
* --gap (local value for the shared gap token)
|
|
6744
7099
|
*/
|
|
6745
7100
|
/* Intent boundary — inherited intents do not tint this component.
|
|
6746
|
-
Local intent classes still win because :where() has zero specificity.
|
|
7101
|
+
Local intent classes still win because :where() has zero specificity.
|
|
7102
|
+
The intent tokens are reset alongside --ui-*: the shared variants
|
|
7103
|
+
(.soft/.solid/.outline/.surface) read --intent / --intent-fg / --intent-soft-fg
|
|
7104
|
+
when they paint this surface, so an ancestor intent would otherwise leak
|
|
7105
|
+
through a variant that has no local intent class. */
|
|
6747
7106
|
:where(.navbar) {
|
|
7107
|
+
--intent: initial;
|
|
7108
|
+
--intent-fg: initial;
|
|
7109
|
+
--intent-soft-fg: initial;
|
|
6748
7110
|
--ui-bg: initial;
|
|
6749
7111
|
--ui-fg: initial;
|
|
6750
7112
|
--ui-border: initial;
|
|
@@ -6800,10 +7162,15 @@ a.avatar[aria-disabled="true"] {
|
|
|
6800
7162
|
text-decoration: none;
|
|
6801
7163
|
}
|
|
6802
7164
|
|
|
6803
|
-
|
|
7165
|
+
/* nav-link serves two contracts: plain page navigation (aria-current="page")
|
|
7166
|
+
and scrollspy, which toggles aria-current="location" on the active link.
|
|
7167
|
+
Presence is the contract: the attribute is set on the current link and
|
|
7168
|
+
removed everywhere else, never serialized as aria-current="false".
|
|
7169
|
+
Selection never changes text metrics: the shared medium weight is kept and
|
|
7170
|
+
the current link is carried by the selected accent. */
|
|
7171
|
+
.nav-link[aria-current] {
|
|
6804
7172
|
background: var(--surface-subtle);
|
|
6805
|
-
color: var(--
|
|
6806
|
-
font-weight: var(--font-weight-bold);
|
|
7173
|
+
color: var(--state-selected);
|
|
6807
7174
|
}
|
|
6808
7175
|
|
|
6809
7176
|
.nav-link:hover {
|
|
@@ -6811,11 +7178,18 @@ a.avatar[aria-disabled="true"] {
|
|
|
6811
7178
|
color: var(--text);
|
|
6812
7179
|
}
|
|
6813
7180
|
|
|
7181
|
+
/* A current link keeps its accent on hover: without this, the hover rule above
|
|
7182
|
+
would drop it back to --text. The surface hover still applies. */
|
|
7183
|
+
.nav-link[aria-current]:hover {
|
|
7184
|
+
color: var(--state-selected);
|
|
7185
|
+
}
|
|
7186
|
+
|
|
6814
7187
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
6815
7188
|
Links fill each row so the whole line is an interactive target; the
|
|
6816
7189
|
horizontal .navbar-nav links keep their intrinsic size. */
|
|
6817
7190
|
.nav-list {
|
|
6818
7191
|
display: grid;
|
|
7192
|
+
grid-template-columns: minmax(0, 1fr);
|
|
6819
7193
|
--gap: var(--space-20);
|
|
6820
7194
|
gap: var(--gap);
|
|
6821
7195
|
margin: 0;
|
|
@@ -6828,9 +7202,23 @@ a.avatar[aria-disabled="true"] {
|
|
|
6828
7202
|
}
|
|
6829
7203
|
|
|
6830
7204
|
.nav-list .nav-link {
|
|
7205
|
+
position: relative;
|
|
6831
7206
|
inline-size: 100%;
|
|
6832
7207
|
}
|
|
6833
7208
|
|
|
7209
|
+
/* Vertical current indicator: inset inline-start trait in currentColor, so it
|
|
7210
|
+
follows the forced palette without a forced-colors block and never touches
|
|
7211
|
+
the row separators. Navbar horizontal links keep color + surface only. The
|
|
7212
|
+
positioning context lives on every row link, so becoming current never moves
|
|
7213
|
+
an app-positioned decoration to a new containing block. */
|
|
7214
|
+
.nav-list .nav-link[aria-current]::before {
|
|
7215
|
+
content: "";
|
|
7216
|
+
position: absolute;
|
|
7217
|
+
inset-block: var(--space-20);
|
|
7218
|
+
inset-inline-start: 0;
|
|
7219
|
+
border-inline-start: 2px solid currentColor;
|
|
7220
|
+
}
|
|
7221
|
+
|
|
6834
7222
|
/*
|
|
6835
7223
|
* Breadcrumb — current-location trail from the site root.
|
|
6836
7224
|
*
|
|
@@ -6866,8 +7254,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6866
7254
|
text-decoration: none;
|
|
6867
7255
|
}
|
|
6868
7256
|
|
|
6869
|
-
.breadcrumb a:not([aria-current]):hover
|
|
6870
|
-
.breadcrumb a[aria-current="false"]:hover {
|
|
7257
|
+
.breadcrumb a:not([aria-current="page"]):hover {
|
|
6871
7258
|
color: var(--text);
|
|
6872
7259
|
text-decoration: underline;
|
|
6873
7260
|
}
|
|
@@ -6876,7 +7263,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6876
7263
|
border-radius: var(--radius-sm);
|
|
6877
7264
|
}
|
|
6878
7265
|
|
|
6879
|
-
.breadcrumb :where(li, a, span)
|
|
7266
|
+
.breadcrumb :where(li, a, span)[aria-current="page"] {
|
|
6880
7267
|
color: var(--text);
|
|
6881
7268
|
font-weight: var(--font-weight-medium);
|
|
6882
7269
|
}
|
|
@@ -6900,6 +7287,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6900
7287
|
margin: 0;
|
|
6901
7288
|
padding: 0;
|
|
6902
7289
|
list-style: none;
|
|
7290
|
+
font-size: var(--control-font-size);
|
|
6903
7291
|
|
|
6904
7292
|
/* Arrows, page numbers, and truncation ellipses share one geometric
|
|
6905
7293
|
system: equal digit widths keep the row from re-flowing as numbers grow
|
|
@@ -6931,6 +7319,11 @@ a.avatar[aria-disabled="true"] {
|
|
|
6931
7319
|
* and arrow-key navigation. CSS owns the tab strip appearance and
|
|
6932
7320
|
* active indicator.
|
|
6933
7321
|
*
|
|
7322
|
+
* aria-orientation is matched inside :where() so it keeps describing which
|
|
7323
|
+
* arrow keys move between tabs without also carrying cascade weight: an
|
|
7324
|
+
* author class can then adjust a vertical strip without restating the
|
|
7325
|
+
* attribute to reach its specificity.
|
|
7326
|
+
*
|
|
6934
7327
|
* Public hooks:
|
|
6935
7328
|
* --tab-gap
|
|
6936
7329
|
*/
|
|
@@ -6951,7 +7344,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6951
7344
|
list-style: none;
|
|
6952
7345
|
}
|
|
6953
7346
|
|
|
6954
|
-
.tabs[aria-orientation="vertical"] {
|
|
7347
|
+
.tabs:where([aria-orientation="vertical"]) {
|
|
6955
7348
|
display: grid;
|
|
6956
7349
|
align-content: start;
|
|
6957
7350
|
justify-items: stretch;
|
|
@@ -6972,12 +7365,15 @@ a.avatar[aria-disabled="true"] {
|
|
|
6972
7365
|
background: transparent;
|
|
6973
7366
|
color: var(--text-muted);
|
|
6974
7367
|
font: inherit;
|
|
6975
|
-
font-
|
|
7368
|
+
font-size: var(--control-font-size);
|
|
7369
|
+
/* Selection must not change text metrics: a heavier active label would shift
|
|
7370
|
+
the strip. The active state is carried by color and the indicator line. */
|
|
7371
|
+
font-weight: var(--font-weight-semibold);
|
|
6976
7372
|
text-decoration: none;
|
|
6977
7373
|
cursor: pointer;
|
|
6978
7374
|
}
|
|
6979
7375
|
|
|
6980
|
-
.tabs[aria-orientation="vertical"] .tab {
|
|
7376
|
+
.tabs:where([aria-orientation="vertical"]) .tab {
|
|
6981
7377
|
justify-content: flex-start;
|
|
6982
7378
|
padding-inline-end: var(--space-40);
|
|
6983
7379
|
}
|
|
@@ -6991,6 +7387,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
6991
7387
|
color: var(--text);
|
|
6992
7388
|
}
|
|
6993
7389
|
|
|
7390
|
+
/* The ring is square on the edge the tab is attached to, so it reads as part
|
|
7391
|
+
of the strip rather than a floating chip. */
|
|
6994
7392
|
.tab:focus-visible {
|
|
6995
7393
|
outline: var(--focus-outline);
|
|
6996
7394
|
outline-color: var(--focus);
|
|
@@ -6998,18 +7396,27 @@ a.avatar[aria-disabled="true"] {
|
|
|
6998
7396
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
6999
7397
|
}
|
|
7000
7398
|
|
|
7001
|
-
|
|
7399
|
+
/* A vertical tab is attached along its inline end, not its block end, so the
|
|
7400
|
+
horizontal geometry above would round the wrong pair of corners. Logical
|
|
7401
|
+
corners keep it correct in RTL. */
|
|
7402
|
+
.tabs:where([aria-orientation="vertical"]) .tab:focus-visible {
|
|
7403
|
+
border-start-start-radius: var(--radius-sm);
|
|
7404
|
+
border-start-end-radius: 0;
|
|
7405
|
+
border-end-start-radius: var(--radius-sm);
|
|
7406
|
+
border-end-end-radius: 0;
|
|
7407
|
+
}
|
|
7408
|
+
|
|
7409
|
+
.tab[aria-selected="true"],
|
|
7410
|
+
.tab[aria-current="page"] {
|
|
7002
7411
|
color: var(--intent, var(--state-selected));
|
|
7003
|
-
/* Second, layout-free selected cue — the underline alone is color-only in
|
|
7004
|
-
forced colors. */
|
|
7005
|
-
font-weight: var(--font-weight-bold);
|
|
7006
7412
|
}
|
|
7007
7413
|
|
|
7008
7414
|
/* The selected indicator lives on a positioned pseudo so that only the active
|
|
7009
7415
|
tab owns an underline: a transparent border on every tab would surface in
|
|
7010
7416
|
forced colors and erase the distinction. currentColor follows the palette the
|
|
7011
7417
|
UA forces, so no forced-colors block is needed. */
|
|
7012
|
-
.tab[aria-selected="true"]::after
|
|
7418
|
+
.tab[aria-selected="true"]::after,
|
|
7419
|
+
.tab[aria-current="page"]::after {
|
|
7013
7420
|
content: "";
|
|
7014
7421
|
position: absolute;
|
|
7015
7422
|
inset-inline: 0;
|
|
@@ -7017,7 +7424,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
7017
7424
|
border-block-end: 2px solid currentColor;
|
|
7018
7425
|
}
|
|
7019
7426
|
|
|
7020
|
-
.tabs[aria-orientation="vertical"] .tab[aria-selected="true"]::after
|
|
7427
|
+
.tabs:where([aria-orientation="vertical"]) .tab[aria-selected="true"]::after,
|
|
7428
|
+
.tabs:where([aria-orientation="vertical"]) .tab[aria-current="page"]::after {
|
|
7021
7429
|
inset-block: 0;
|
|
7022
7430
|
inset-inline: auto 0;
|
|
7023
7431
|
border-block-end: 0;
|
|
@@ -7074,7 +7482,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7074
7482
|
.data-list > dt {
|
|
7075
7483
|
min-inline-size: 0;
|
|
7076
7484
|
margin-block-start: 0;
|
|
7077
|
-
font-weight: var(--font-weight-
|
|
7485
|
+
font-weight: var(--font-weight-semibold);
|
|
7078
7486
|
}
|
|
7079
7487
|
|
|
7080
7488
|
.data-list > dd {
|
|
@@ -7102,6 +7510,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7102
7510
|
--list-divider: var(--border-width) solid var(--border);
|
|
7103
7511
|
|
|
7104
7512
|
display: grid;
|
|
7513
|
+
grid-template-columns: minmax(0, 1fr);
|
|
7105
7514
|
gap: 0;
|
|
7106
7515
|
margin: 0;
|
|
7107
7516
|
padding: 0;
|
|
@@ -7113,10 +7522,17 @@ a.avatar[aria-disabled="true"] {
|
|
|
7113
7522
|
margin: 0;
|
|
7114
7523
|
}
|
|
7115
7524
|
|
|
7525
|
+
/*
|
|
7526
|
+
* Flex, not a three-track grid: the leading and trailing regions are optional,
|
|
7527
|
+
* and a fixed `auto minmax(0, 1fr) auto` template gives an absent region a
|
|
7528
|
+
* track anyway — the content of a row with no leading region lands in the
|
|
7529
|
+
* leading track and the trailing control inherits the flexible one, which is
|
|
7530
|
+
* the opposite of the intent. Flex drops both the track and its gap when a
|
|
7531
|
+
* region is not there, so all four shapes work off one rule.
|
|
7532
|
+
*/
|
|
7116
7533
|
.list > :where(.list-item),
|
|
7117
7534
|
.list > li > .list-item {
|
|
7118
|
-
display:
|
|
7119
|
-
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
7535
|
+
display: flex;
|
|
7120
7536
|
/* Regions align to the first line, so leading, title, and trailing controls
|
|
7121
7537
|
share one horizontal line while supporting text wraps beneath. */
|
|
7122
7538
|
align-items: start;
|
|
@@ -7132,6 +7548,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7132
7548
|
}
|
|
7133
7549
|
|
|
7134
7550
|
a.list-item {
|
|
7551
|
+
position: relative;
|
|
7135
7552
|
color: inherit;
|
|
7136
7553
|
text-decoration: none;
|
|
7137
7554
|
}
|
|
@@ -7140,7 +7557,31 @@ a.list-item:hover {
|
|
|
7140
7557
|
background: var(--surface-subtle);
|
|
7141
7558
|
}
|
|
7142
7559
|
|
|
7560
|
+
/* Current row in a navigational list (master/detail). Generic by design: the
|
|
7561
|
+
visual treatment only cares that the row is current, so presence is the
|
|
7562
|
+
contract — the attribute is set on the current row and removed everywhere
|
|
7563
|
+
else, never serialized as aria-current="false". The value (page, location,
|
|
7564
|
+
…) stays free for assistive technology.
|
|
7565
|
+
Calm by design: the row keeps its normal text metrics and color on a subtle
|
|
7566
|
+
surface, and the selected accent lives on an inset inline-start trait only.
|
|
7567
|
+
The trait is a real border, so it survives forced colors without a repaint
|
|
7568
|
+
block. The positioning context lives on every row link, so becoming current
|
|
7569
|
+
never moves an app-positioned decoration to a new containing block. */
|
|
7570
|
+
a.list-item[aria-current] {
|
|
7571
|
+
background: var(--surface-subtle);
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7574
|
+
a.list-item[aria-current]::before {
|
|
7575
|
+
content: "";
|
|
7576
|
+
position: absolute;
|
|
7577
|
+
inset-block: var(--list-item-pad-block);
|
|
7578
|
+
inset-inline-start: 0;
|
|
7579
|
+
border-inline-start: 2px solid var(--state-selected);
|
|
7580
|
+
}
|
|
7581
|
+
|
|
7582
|
+
/* The content region is the one that flexes, whichever position it holds. */
|
|
7143
7583
|
.list-item-content {
|
|
7584
|
+
flex: 1 1 0;
|
|
7144
7585
|
min-inline-size: 0;
|
|
7145
7586
|
}
|
|
7146
7587
|
|
|
@@ -7148,16 +7589,19 @@ a.list-item:hover {
|
|
|
7148
7589
|
.list-item-text {
|
|
7149
7590
|
display: block;
|
|
7150
7591
|
margin: 0;
|
|
7592
|
+
overflow-wrap: anywhere;
|
|
7151
7593
|
}
|
|
7152
7594
|
|
|
7153
7595
|
.list-item-text {
|
|
7154
7596
|
color: var(--text-muted);
|
|
7155
7597
|
font-size: var(--font-size-sm);
|
|
7156
|
-
overflow-wrap: anywhere;
|
|
7157
7598
|
}
|
|
7158
7599
|
|
|
7600
|
+
/* The auto margin keeps the trailing region at the row's end even in a row
|
|
7601
|
+
with no content region to absorb the free space. */
|
|
7159
7602
|
.list-item-trailing {
|
|
7160
|
-
|
|
7603
|
+
flex: none;
|
|
7604
|
+
margin-inline-start: auto;
|
|
7161
7605
|
min-inline-size: 0;
|
|
7162
7606
|
}
|
|
7163
7607
|
|
|
@@ -7213,13 +7657,13 @@ a.list-item:hover {
|
|
|
7213
7657
|
vertical-align: baseline;
|
|
7214
7658
|
}
|
|
7215
7659
|
|
|
7216
|
-
.table thead th {
|
|
7217
|
-
vertical-align: bottom;
|
|
7218
|
-
}
|
|
7219
|
-
|
|
7220
7660
|
.table th {
|
|
7221
7661
|
color: var(--text-muted);
|
|
7222
|
-
font-weight: var(--font-weight-
|
|
7662
|
+
font-weight: var(--font-weight-semibold);
|
|
7663
|
+
}
|
|
7664
|
+
|
|
7665
|
+
.table thead th {
|
|
7666
|
+
vertical-align: bottom;
|
|
7223
7667
|
}
|
|
7224
7668
|
|
|
7225
7669
|
.table thead {
|
|
@@ -7398,6 +7842,13 @@ a.list-item:hover {
|
|
|
7398
7842
|
border-end-start-radius: var(--join-es);
|
|
7399
7843
|
}
|
|
7400
7844
|
|
|
7845
|
+
/* A square icon button has an explicit block size, which otherwise prevents
|
|
7846
|
+
align-items: stretch from matching a taller labelled sibling. The button's
|
|
7847
|
+
existing min-block-size keeps the standalone square-size invariant intact. */
|
|
7848
|
+
.join > .btn.icon-only {
|
|
7849
|
+
block-size: auto;
|
|
7850
|
+
}
|
|
7851
|
+
|
|
7401
7852
|
.join > :first-child {
|
|
7402
7853
|
--join-ss: var(--join-radius);
|
|
7403
7854
|
--join-es: var(--join-radius);
|
|
@@ -7780,7 +8231,8 @@ a.list-item:hover {
|
|
|
7780
8231
|
text-decoration: none;
|
|
7781
8232
|
}
|
|
7782
8233
|
|
|
7783
|
-
.link-muted:hover
|
|
8234
|
+
.link-muted:hover,
|
|
8235
|
+
.link-muted:focus-visible {
|
|
7784
8236
|
color: var(--text);
|
|
7785
8237
|
}
|
|
7786
8238
|
|
|
@@ -7821,7 +8273,8 @@ a.list-item:hover {
|
|
|
7821
8273
|
* They are escape hatches for layout nudges, not a full atomic scale.
|
|
7822
8274
|
*
|
|
7823
8275
|
* Core principle:
|
|
7824
|
-
* - .
|
|
8276
|
+
* - .compact / .spacious provide inherited rhythm through --gap and
|
|
8277
|
+
* --density-space.
|
|
7825
8278
|
* - utilities consume the current context instead of defining their own -sm/-lg scale.
|
|
7826
8279
|
* - optional utilities extend core utilities, they do not re-express them.
|
|
7827
8280
|
* - follow verbose .{property}-{value} naming.
|
|
@@ -7902,18 +8355,6 @@ a.list-item:hover {
|
|
|
7902
8355
|
flex-wrap: nowrap;
|
|
7903
8356
|
}
|
|
7904
8357
|
|
|
7905
|
-
.gap-sm {
|
|
7906
|
-
gap: var(--space-20);
|
|
7907
|
-
}
|
|
7908
|
-
|
|
7909
|
-
.gap-md {
|
|
7910
|
-
gap: var(--space-40);
|
|
7911
|
-
}
|
|
7912
|
-
|
|
7913
|
-
.gap-lg {
|
|
7914
|
-
gap: var(--space-50);
|
|
7915
|
-
}
|
|
7916
|
-
|
|
7917
8358
|
.justify-content-start {
|
|
7918
8359
|
--cluster-justify: flex-start;
|
|
7919
8360
|
justify-content: flex-start;
|
|
@@ -7971,6 +8412,10 @@ a.list-item:hover {
|
|
|
7971
8412
|
margin-block-end: auto;
|
|
7972
8413
|
}
|
|
7973
8414
|
|
|
8415
|
+
/* Consume the inherited --gap rhythm on an arbitrary layout container.
|
|
8416
|
+
Setting --gap alone only defines the custom property; it has no visual
|
|
8417
|
+
effect unless the element already uses gap: var(--gap) (as .stack,
|
|
8418
|
+
.cluster, .grid, etc. do). */
|
|
7974
8419
|
.gap-context {
|
|
7975
8420
|
gap: var(--gap);
|
|
7976
8421
|
}
|
|
@@ -8022,15 +8467,15 @@ a.list-item:hover {
|
|
|
8022
8467
|
}
|
|
8023
8468
|
|
|
8024
8469
|
.font-weight-normal {
|
|
8025
|
-
font-weight: var(--font-weight);
|
|
8470
|
+
font-weight: var(--font-weight-normal);
|
|
8026
8471
|
}
|
|
8027
8472
|
|
|
8028
8473
|
.font-weight-medium {
|
|
8029
8474
|
font-weight: var(--font-weight-medium);
|
|
8030
8475
|
}
|
|
8031
8476
|
|
|
8032
|
-
.font-weight-
|
|
8033
|
-
font-weight: var(--font-weight-
|
|
8477
|
+
.font-weight-semibold {
|
|
8478
|
+
font-weight: var(--font-weight-semibold);
|
|
8034
8479
|
}
|
|
8035
8480
|
|
|
8036
8481
|
.font-weight-bold {
|
|
@@ -8071,18 +8516,28 @@ a.list-item:hover {
|
|
|
8071
8516
|
border-inline-end: var(--border-width) solid var(--border);
|
|
8072
8517
|
}
|
|
8073
8518
|
|
|
8074
|
-
/* Surface
|
|
8519
|
+
/* Surface. These are semantic surfaces, not arbitrary paint: each owns its
|
|
8520
|
+
matching ink and rebinds --heading to that ink, so it stays readable when it
|
|
8521
|
+
recreates a light surface inside a context that installed a dark one
|
|
8522
|
+
(.inverted). Intent ink is not inherited through a surface utility; use
|
|
8523
|
+
.intent-color for that. */
|
|
8075
8524
|
.background-surface {
|
|
8525
|
+
--heading: currentColor;
|
|
8526
|
+
|
|
8076
8527
|
background: var(--surface);
|
|
8077
8528
|
color: var(--text);
|
|
8078
8529
|
}
|
|
8079
8530
|
|
|
8080
8531
|
.background-raised {
|
|
8532
|
+
--heading: currentColor;
|
|
8533
|
+
|
|
8081
8534
|
background: var(--surface-raised);
|
|
8082
8535
|
color: var(--text);
|
|
8083
8536
|
}
|
|
8084
8537
|
|
|
8085
8538
|
.background-subtle {
|
|
8539
|
+
--heading: currentColor;
|
|
8540
|
+
|
|
8086
8541
|
background: var(--surface-subtle);
|
|
8087
8542
|
color: var(--text);
|
|
8088
8543
|
}
|