actual-css 0.9.2 → 0.11.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 +169 -1
- package/README.md +65 -44
- package/components.json +35 -14
- package/dist/actual.full.css +877 -583
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/docs/design-notes/app-ui.md +1 -1
- package/docs/design-notes/column-layout.md +103 -520
- package/docs/design-notes/enhancement-contract.md +16 -7
- package/docs/design-notes/forced-colors.md +12 -1
- package/docs/design-notes/naming.md +1 -1
- package/docs/design-notes/platform-alignment.md +8 -4
- package/docs/design-notes/rich-text.md +61 -0
- package/docs/design-notes/steps.md +2 -2
- package/docs/design-notes/surface-reparenting.md +8 -9
- package/docs/navigation.json +4 -0
- package/docs/pages/components/accordion.md +55 -1
- package/docs/pages/components/alert.md +9 -15
- package/docs/pages/components/aura.md +12 -13
- package/docs/pages/components/badge.md +16 -7
- package/docs/pages/components/bleed.md +42 -0
- package/docs/pages/components/button.md +30 -4
- package/docs/pages/components/card.md +21 -86
- package/docs/pages/components/chat.md +5 -0
- package/docs/pages/components/close.md +48 -0
- package/docs/pages/components/data-list.md +26 -1
- package/docs/pages/components/dialog.md +21 -18
- package/docs/pages/components/drawer.md +9 -9
- package/docs/pages/components/navbar.md +5 -5
- package/docs/pages/components/pagination.md +15 -15
- package/docs/pages/components/shimmer.md +57 -0
- package/docs/pages/components/skeleton.md +2 -1
- package/docs/pages/components/spinner.md +14 -9
- package/docs/pages/components/table.md +13 -0
- package/docs/pages/enhancements/flyout.md +2 -0
- package/docs/pages/enhancements/overview.md +5 -0
- package/docs/pages/enhancements/tabs.md +4 -0
- package/docs/pages/enhancements/tooltip.md +3 -1
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/choice-cards.md +116 -4
- package/docs/pages/forms/combobox.md +39 -12
- package/docs/pages/forms/joined-controls.md +2 -5
- package/docs/pages/forms/overview.md +10 -1
- package/docs/pages/forms/range.md +110 -5
- package/docs/pages/forms/rich-text.md +110 -0
- package/docs/pages/forms/validation.md +5 -1
- package/docs/pages/foundations/theming.md +110 -18
- package/docs/pages/foundations/tokens.md +41 -13
- package/docs/pages/guides/bootstrap.md +2 -1
- package/docs/pages/guides/introduction.md +15 -23
- package/docs/pages/guides/modular-import.md +54 -7
- package/docs/pages/guides/progressive-enhancement.md +19 -17
- package/docs/pages/guides/tailwind.md +21 -7
- package/docs/pages/layout/column-layout.md +92 -54
- package/docs/pages/layout/media.md +1 -1
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/utilities/overview.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +5 -3
- package/llms.txt +25 -4
- package/package.json +10 -6
- package/reserved-classes.json +6 -30
- package/skills/actual-css/PROJECT.md.example +65 -0
- package/skills/actual-css/README.md +64 -0
- package/skills/actual-css/SKILL.md +234 -0
- package/skills/actual-css/references/accessibility.md +44 -0
- package/skills/actual-css/references/api-and-vocabulary.md +94 -0
- package/skills/actual-css/references/enhancements.md +59 -0
- package/skills/actual-css/references/forms.md +49 -0
- package/skills/actual-css/references/layout-and-theming.md +82 -0
- package/skills/actual-css/references/review-checklist.md +51 -0
- package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
- package/src/css/components/accordion.css +44 -7
- package/src/css/components/alert.css +23 -43
- package/src/css/components/app-nav.css +9 -0
- package/src/css/components/avatar.css +1 -1
- package/src/css/components/badge.css +11 -39
- package/src/css/components/bleed.css +79 -0
- package/src/css/components/button.css +6 -17
- package/src/css/components/card.css +7 -40
- package/src/css/components/close.css +91 -0
- package/src/css/components/data-list.css +18 -0
- package/src/css/components/drawer.css +15 -56
- package/src/css/components/fab.css +10 -0
- package/src/css/components/flyout.css +5 -1
- package/src/css/components/index.css +2 -0
- package/src/css/components/join.css +36 -20
- package/src/css/components/menu.css +12 -0
- package/src/css/components/meter.css +1 -1
- package/src/css/components/modal.css +10 -57
- package/src/css/components/navbar.css +27 -0
- package/src/css/components/pagination.css +3 -2
- package/src/css/components/rating.css +1 -1
- package/src/css/components/status-bar.css +2 -2
- package/src/css/components/steps.css +3 -3
- package/src/css/components/tab.css +3 -3
- package/src/css/components/table.css +6 -0
- package/src/css/components/tooltip.css +1 -1
- package/src/css/core/focus.css +19 -3
- package/src/css/core/theme.css +33 -43
- package/src/css/core/tokens.css +9 -5
- package/src/css/core/variants.css +5 -34
- package/src/css/effects/aura.css +51 -33
- package/src/css/effects/index.css +1 -0
- package/src/css/effects/shimmer.css +57 -0
- package/src/css/forms/choice-card.css +142 -24
- package/src/css/forms/choice.css +2 -3
- package/src/css/forms/control.css +17 -10
- package/src/css/forms/custom-select.css +4 -4
- package/src/css/forms/native.css +4 -4
- package/src/css/forms/otp.css +9 -1
- package/src/css/forms/range.css +72 -14
- package/src/css/forms/switch.css +2 -3
- package/src/css/layout/app-layout.css +1 -1
- package/src/css/layout/column-layout.css +26 -111
- package/src/css/utilities/extra.css +7 -6
- package/src/js/command.js +61 -32
- package/src/js/dialog.js +103 -47
- package/src/js/dismiss.js +1 -2
- package/src/js/enhance.js +91 -126
- package/src/js/escape.js +1 -0
- package/src/js/flyout.js +10 -1
- package/src/js/focus-group.js +2 -1
- package/src/js/full.js +1 -0
- package/src/js/keys.js +14 -0
- package/src/js/menu.js +2 -1
- package/src/js/password.js +1 -8
- package/src/js/range.js +151 -0
- package/src/js/scrollspy.js +4 -5
- package/src/js/status.js +21 -10
- package/src/js/surface.js +12 -8
- package/src/js/tab.js +2 -1
- package/src/js/tooltip.js +29 -2
- package/src/js/validation.js +8 -15
- package/dist/actual.css +0 -876
- package/dist/actual.js +0 -1
- package/dist/actual.min.css +0 -1
package/dist/actual.full.css
CHANGED
|
@@ -130,11 +130,15 @@ select {
|
|
|
130
130
|
--ease-enter: cubic-bezier(0.2, 0, 0, 1);
|
|
131
131
|
--ease-exit: cubic-bezier(0.4, 0, 1, 1);
|
|
132
132
|
|
|
133
|
-
/*
|
|
133
|
+
/* Generic focus fallback — an adaptive currentColor line for elements
|
|
134
|
+
without a component ring (links, summary, plain items). Components draw
|
|
135
|
+
the themed ring instead: --focus-ring-width solid --focus. See focus.css
|
|
136
|
+
for the two contracts. Both share --focus-ring-width: they differ in
|
|
137
|
+
color only. */
|
|
134
138
|
--focus-outline-color: currentColor;
|
|
135
|
-
--focus-outline:
|
|
139
|
+
--focus-outline: var(--focus-ring-width) solid var(--focus-outline-color);
|
|
136
140
|
--focus-outline-offset: 2px;
|
|
137
|
-
--focus-ring-width:
|
|
141
|
+
--focus-ring-width: 2px;
|
|
138
142
|
|
|
139
143
|
/* Status dot positioning — used by .avatar > .badge:empty. The offset is
|
|
140
144
|
the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
|
|
@@ -195,8 +199,8 @@ select {
|
|
|
195
199
|
--state-selected-fg: var(--primary-fg);
|
|
196
200
|
--state-disabled: var(--text-subtle);
|
|
197
201
|
|
|
198
|
-
/* Status dot ring —
|
|
199
|
-
--indicator-ring: var(--surface);
|
|
202
|
+
/* Status dot ring — cuts the dot out of what it overlaps. */
|
|
203
|
+
--indicator-ring: var(--surface-opaque);
|
|
200
204
|
}
|
|
201
205
|
|
|
202
206
|
:root {
|
|
@@ -291,6 +295,8 @@ select {
|
|
|
291
295
|
* Shadow color, Focus ring derivation.
|
|
292
296
|
* Derived: "Theme-derived aliases", Tone subsystem, Backdrop fill — reference
|
|
293
297
|
* other tokens; hard-coded overrides on a custom theme will break the intent.
|
|
298
|
+
* Exception: --surface-opaque and --surface-solid-fg are public; a theme with
|
|
299
|
+
* translucent surfaces overrides them with opaque values.
|
|
294
300
|
* Internal: --hgroup-gap.
|
|
295
301
|
*/
|
|
296
302
|
:root {
|
|
@@ -356,10 +362,10 @@ select {
|
|
|
356
362
|
|
|
357
363
|
/* Lines and focus */
|
|
358
364
|
--border: hsl(260 15% 88%);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
--focus
|
|
365
|
+
/* A primary-hued mid-tone on purpose, one value for both schemes: focus
|
|
366
|
+
lines must hold 3:1 against --surface and --surface-solid alike (page,
|
|
367
|
+
fields, a solid band), which rules out the primary itself. */
|
|
368
|
+
--focus: hsl(268 30% 55%);
|
|
363
369
|
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
364
370
|
|
|
365
371
|
/* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
|
|
@@ -382,7 +388,12 @@ select {
|
|
|
382
388
|
--heading: var(--text);
|
|
383
389
|
--selection-bg: var(--primary);
|
|
384
390
|
--selection-fg: var(--primary-fg);
|
|
385
|
-
|
|
391
|
+
/* Surface roles that must stay opaque when a theme paints translucent
|
|
392
|
+
surfaces; both equal --surface on an opaque palette. --surface-opaque
|
|
393
|
+
hides or separates what is painted beneath a ring, separator or marker —
|
|
394
|
+
never a fill. --surface-solid-fg is the ink on --surface-solid. */
|
|
395
|
+
--surface-opaque: var(--surface);
|
|
396
|
+
--surface-solid-fg: var(--surface);
|
|
386
397
|
}
|
|
387
398
|
|
|
388
399
|
/* Soft foreground hooks — the soft ink is the intent rebated toward the local
|
|
@@ -391,18 +402,17 @@ select {
|
|
|
391
402
|
default theme: a custom property bakes its var() references where it is
|
|
392
403
|
declared, so these resolve against root/light/dark tokens on every boundary.
|
|
393
404
|
Presets and custom islands keep their --soft-fg-mix / raw-intent ink unless
|
|
394
|
-
they declare hooks themselves
|
|
395
|
-
|
|
396
|
-
:where(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
:root,
|
|
405
|
+
they declare hooks themselves.
|
|
406
|
+
|
|
407
|
+
Why :where(:root) and why the reset comes second: both rules match when a
|
|
408
|
+
custom theme sits on <html> itself. A bare :root is (0,1,0) and would
|
|
409
|
+
outrank the zero-specificity reset, so the same theme resolved a calibrated
|
|
410
|
+
Ink & Terra ink on <html data-theme="brand"> and the raw --soft-fg-mix ink
|
|
411
|
+
on <div data-theme="brand"> — one attribute, two soft palettes. Levelling
|
|
412
|
+
both rules to zero specificity makes source order the tiebreaker, so the
|
|
413
|
+
reset must stay below. Zero specificity is also what lets a theme declare
|
|
414
|
+
its own hooks (e.g. brutalist's 45% soft-fg-mix) and win at (0,1,0). */
|
|
415
|
+
:where(:root),
|
|
406
416
|
[data-theme="light"],
|
|
407
417
|
[data-theme="dark"] {
|
|
408
418
|
--secondary-soft-fg: color-mix(in oklab, var(--text) 10%, var(--secondary) 90%);
|
|
@@ -412,6 +422,15 @@ select {
|
|
|
412
422
|
--neutral-soft-fg: color-mix(in oklab, var(--text) 20%, var(--neutral) 80%);
|
|
413
423
|
}
|
|
414
424
|
|
|
425
|
+
:where([data-theme]:not([data-theme="light"]):not([data-theme="dark"])) {
|
|
426
|
+
--primary-soft-fg: initial;
|
|
427
|
+
--secondary-soft-fg: initial;
|
|
428
|
+
--success-soft-fg: initial;
|
|
429
|
+
--warning-soft-fg: initial;
|
|
430
|
+
--danger-soft-fg: initial;
|
|
431
|
+
--neutral-soft-fg: initial;
|
|
432
|
+
}
|
|
433
|
+
|
|
415
434
|
/* Native platform controls (checkbox, radio, range, progress) that are not
|
|
416
435
|
restyled by a component class still follow the theme accent. Declared on
|
|
417
436
|
every island: accent-color inherits as a computed value, so islands must
|
|
@@ -433,20 +452,6 @@ select {
|
|
|
433
452
|
--tone-fg: var(--text);
|
|
434
453
|
}
|
|
435
454
|
|
|
436
|
-
/* Focus ring derivation — color-mix tier. Runs whether or not light-dark()
|
|
437
|
-
is also supported, so it covers the color-mix-without-light-dark browser
|
|
438
|
-
tier on its own; the light-dark block below refines it further with the
|
|
439
|
-
light/dark alpha split once both features are available. Declared on every
|
|
440
|
-
[data-theme] island (not just the built-in light/dark pair) so custom
|
|
441
|
-
islands recolor the ring for free; a preset that re-declares --focus-ring
|
|
442
|
-
later in source order intentionally pins its own ring instead. */
|
|
443
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
444
|
-
:root,
|
|
445
|
-
[data-theme] {
|
|
446
|
-
--focus-ring: color-mix(in oklch, var(--focus) 30%, transparent);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
455
|
/* Auto dark mode via light-dark(). Resolves based on the element's color-scheme:
|
|
451
456
|
:root (light dark) follows the OS, [data-theme="light"] forces light,
|
|
452
457
|
[data-theme="dark"] forces dark by overriding color-scheme.
|
|
@@ -483,22 +488,11 @@ select {
|
|
|
483
488
|
--text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%));
|
|
484
489
|
|
|
485
490
|
--border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%));
|
|
486
|
-
--focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28));
|
|
487
491
|
--hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06));
|
|
488
492
|
--shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%));
|
|
489
493
|
}
|
|
490
494
|
}
|
|
491
495
|
|
|
492
|
-
@supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) {
|
|
493
|
-
:root,
|
|
494
|
-
[data-theme] {
|
|
495
|
-
--focus-ring: light-dark(
|
|
496
|
-
color-mix(in oklch, var(--focus) 30%, transparent),
|
|
497
|
-
color-mix(in oklch, var(--focus) 28%, transparent)
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
496
|
@media (prefers-contrast: more) {
|
|
503
497
|
:root,
|
|
504
498
|
/* The repeated attribute intentionally outranks single-attribute preset
|
|
@@ -506,7 +500,7 @@ select {
|
|
|
506
500
|
[data-theme][data-theme] {
|
|
507
501
|
--border-width: 2px;
|
|
508
502
|
--focus-outline-offset: 3px;
|
|
509
|
-
--focus-ring-width:
|
|
503
|
+
--focus-ring-width: 3px;
|
|
510
504
|
--text-muted: var(--text);
|
|
511
505
|
--text-subtle: var(--text);
|
|
512
506
|
}
|
|
@@ -662,13 +656,9 @@ small {
|
|
|
662
656
|
* Cross-cutting variant token mapping.
|
|
663
657
|
*
|
|
664
658
|
* Variant scope: .solid/.soft/.outline/.surface set --ui-* for components that
|
|
665
|
-
* opt into shared variants.
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
* (.card, .navbar, .alert, .badge) invert through their own rules. Loaded
|
|
669
|
-
* before components, so a component with its own explicit surface (e.g.
|
|
670
|
-
* .accordion) naturally keeps that surface — .inverted never forces a
|
|
671
|
-
* non-participant.
|
|
659
|
+
* opt into shared variants. A subtree that needs another palette or scheme is
|
|
660
|
+
* a data-theme island, never a variant: only an island recomputes every token
|
|
661
|
+
* (fields, states, intents), where a remap of a few --ui-* tokens cannot.
|
|
672
662
|
* Component-specific defaults (.btn solid-by-default, .badge
|
|
673
663
|
* soft-by-default, .alert soft-by-default) live with their components:
|
|
674
664
|
* components/button.css — .btn default solid + .btn.ghost + .btn.link
|
|
@@ -724,28 +714,6 @@ small {
|
|
|
724
714
|
--ui-border: var(--border);
|
|
725
715
|
}
|
|
726
716
|
|
|
727
|
-
/* Inverse surface treatment. Paints the element directly (so it works on any
|
|
728
|
-
bare block) and remaps the shared --ui-* tokens so opt-in components
|
|
729
|
-
(.card, .navbar) invert through their own rules. Because this loads before
|
|
730
|
-
component base surfaces, components with their own explicit surface
|
|
731
|
-
(e.g. .accordion) keep it — participation is opt-in, never forced. --heading
|
|
732
|
-
is set so global headings (var(--heading, inherit)) do not inherit an outside
|
|
733
|
-
heading color onto the dark surface; surface-owning descendants derive
|
|
734
|
-
their contextual aliases from their own local surface. */
|
|
735
|
-
.inverted {
|
|
736
|
-
--ui-bg: var(--surface-solid);
|
|
737
|
-
--ui-fg: var(--surface);
|
|
738
|
-
--ui-border: transparent;
|
|
739
|
-
--ui-hover-bg: var(--surface-solid);
|
|
740
|
-
|
|
741
|
-
--heading: var(--surface);
|
|
742
|
-
--busy-overlay-bg: var(--surface-solid);
|
|
743
|
-
|
|
744
|
-
background: var(--ui-bg);
|
|
745
|
-
color: var(--ui-fg);
|
|
746
|
-
border-color: var(--ui-border);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
717
|
.compact {
|
|
750
718
|
--gap: var(--space-20);
|
|
751
719
|
--density-space: var(--space-20);
|
|
@@ -763,7 +731,8 @@ small {
|
|
|
763
731
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
764
732
|
component (field, choice-card, floating-field, input-icon, join, pagination,
|
|
765
733
|
tabs, flyout). A local size declaration beats inherited density because it
|
|
766
|
-
is declared on the sized component itself.
|
|
734
|
+
is declared on the sized component itself. Guarded by check:sync
|
|
735
|
+
(size-scale-participants): .sm and .lg must list the same components. */
|
|
767
736
|
:where(
|
|
768
737
|
.btn,
|
|
769
738
|
.input,
|
|
@@ -829,10 +798,6 @@ small {
|
|
|
829
798
|
);
|
|
830
799
|
/* @sync soft-recipe */
|
|
831
800
|
}
|
|
832
|
-
|
|
833
|
-
.inverted {
|
|
834
|
-
--ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black);
|
|
835
|
-
}
|
|
836
801
|
}
|
|
837
802
|
|
|
838
803
|
/* Interactive hover recipes live in the components that consume them:
|
|
@@ -843,9 +808,25 @@ small {
|
|
|
843
808
|
pointer/mouse focus and interactive components layer their ring on top.
|
|
844
809
|
|
|
845
810
|
Invariant: an interactive component must never rely on box-shadow alone
|
|
846
|
-
for its keyboard focus indicator
|
|
847
|
-
|
|
848
|
-
|
|
811
|
+
for its keyboard focus indicator; forced colors keep outlines and drop
|
|
812
|
+
shadows.
|
|
813
|
+
|
|
814
|
+
Two contracts share the job, both --focus-ring-width wide. Text-like
|
|
815
|
+
elements, whose ink is the thing being focused (links, summary, .nav-link,
|
|
816
|
+
.menu-item, breadcrumb links, .navbar-brand), declare no outline and take
|
|
817
|
+
the generic fallback below: a currentColor line that follows whatever the
|
|
818
|
+
application painted. Boxed components, with a fill or border of their own,
|
|
819
|
+
draw the themed ring instead — a solid line in --focus: inset on fields
|
|
820
|
+
(inside their border box), offset by --focus-outline-offset on discrete
|
|
821
|
+
actions. The color never derives from an intent or a local
|
|
822
|
+
surface — the theme guarantees --focus at 3:1 against both --surface and
|
|
823
|
+
--surface-solid, so one token reads on the page, on a solid band and on
|
|
824
|
+
any nested surface.
|
|
825
|
+
|
|
826
|
+
Documented exception: an invalid field focuses in --form-invalid-border
|
|
827
|
+
(danger), so the error stays visible at the point of correction. The border
|
|
828
|
+
carries the same hook; report:theme-contrast measures that line at 3:1
|
|
829
|
+
against the field's background alongside --focus. */
|
|
849
830
|
:focus {
|
|
850
831
|
outline: var(--focus-outline);
|
|
851
832
|
outline-offset: var(--focus-outline-offset);
|
|
@@ -1390,7 +1371,7 @@ small {
|
|
|
1390
1371
|
*
|
|
1391
1372
|
* Docs: docs/pages/layout/app-layout.md
|
|
1392
1373
|
*
|
|
1393
|
-
* Public
|
|
1374
|
+
* Public hooks:
|
|
1394
1375
|
* --app-nav-side-size
|
|
1395
1376
|
*/
|
|
1396
1377
|
.app-layout {
|
|
@@ -1719,12 +1700,12 @@ small {
|
|
|
1719
1700
|
* Docs: docs/pages/layout/column-layout.md
|
|
1720
1701
|
*
|
|
1721
1702
|
* Neither space-driven like .grid nor count-driven like .grid-N: this is a
|
|
1722
|
-
* coordinate system.
|
|
1723
|
-
*
|
|
1724
|
-
* deliberately no automatic collapse — no threshold,
|
|
1725
|
-
* .container-query behavior, no breakpoint variants. A single
|
|
1726
|
-
* threshold cannot be right for arbitrary per-region compositions,
|
|
1727
|
-
* hidden default would have to be undone rather than extended.
|
|
1703
|
+
* coordinate system. Each direct child places itself with two numbers,
|
|
1704
|
+
* --column-start and --column-span, and the author owns narrow-container
|
|
1705
|
+
* recomposition. There is deliberately no automatic collapse — no threshold,
|
|
1706
|
+
* no hidden .container-query behavior, no breakpoint variants. A single
|
|
1707
|
+
* framework threshold cannot be right for arbitrary per-region compositions,
|
|
1708
|
+
* and a hidden default would have to be undone rather than extended.
|
|
1728
1709
|
*
|
|
1729
1710
|
* See docs/design-notes/column-layout.md.
|
|
1730
1711
|
*
|
|
@@ -1736,11 +1717,12 @@ small {
|
|
|
1736
1717
|
* media + flexible content -> .media
|
|
1737
1718
|
* exact custom track templates -> --grid-columns on .grid
|
|
1738
1719
|
*
|
|
1739
|
-
* Child contract: each direct child is placed on the canvas via its own
|
|
1740
|
-
* .column-span-N / .column-start-N.
|
|
1741
|
-
*
|
|
1742
1720
|
* Unlike .grid-N, the track template does not read --gap, so the gap
|
|
1743
1721
|
* utilities are exact here at every width.
|
|
1722
|
+
*
|
|
1723
|
+
* Public hooks:
|
|
1724
|
+
* --column-start (the start line, 1-12; unset = auto-placed)
|
|
1725
|
+
* --column-span (the width in canvas units, 1-12; unset = to the canvas end)
|
|
1744
1726
|
*/
|
|
1745
1727
|
.column-layout {
|
|
1746
1728
|
display: grid;
|
|
@@ -1748,116 +1730,30 @@ small {
|
|
|
1748
1730
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
|
1749
1731
|
}
|
|
1750
1732
|
|
|
1751
|
-
/*
|
|
1752
|
-
*
|
|
1733
|
+
/* One rule covers the four placements:
|
|
1734
|
+
* neither auto-placed, spanning the whole canvas
|
|
1735
|
+
* span only auto-placed over N units, so 8 + 4 flows onto one row
|
|
1736
|
+
* start only from line S to the canvas end (span 13 - S)
|
|
1737
|
+
* start and span explicit; keep S + N - 1 <= 12
|
|
1753
1738
|
*
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
* 3. .column-span-* the end only, so it refines either of the above
|
|
1739
|
+
* The start stays `auto` unless set: a definite start on every child would
|
|
1740
|
+
* take all of them out of auto-placement, and an 8 + 4 pair would stack.
|
|
1757
1741
|
*
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1760
|
-
*
|
|
1761
|
-
*
|
|
1762
|
-
*
|
|
1763
|
-
* Zero specificity is the point: an application's own container-query rules
|
|
1764
|
-
* override placement additively, with a single-class selector and without
|
|
1765
|
-
* having to undo framework specificity first.
|
|
1766
|
-
*/
|
|
1767
|
-
|
|
1768
|
-
/* Tier 1. A child with no placement class stays a readable full-width region
|
|
1769
|
-
* instead of collapsing to one twelfth. Expressed as a span, not as
|
|
1770
|
-
* `1 / -1`: a definite start line would give every child a definite column
|
|
1771
|
-
* position and disable auto-placement, so `.column-span-8` next to
|
|
1772
|
-
* `.column-span-4` would stack instead of composing 8 + 4.
|
|
1742
|
+
* The reset makes the two hooks inputs of this grid item rather than an
|
|
1743
|
+
* inherited context: a nested .column-layout keeps the coordinates it was
|
|
1744
|
+
* given for its own placement, and its children start from nothing. Zero
|
|
1745
|
+
* specificity, so any author rule on the child wins.
|
|
1773
1746
|
*
|
|
1774
1747
|
* min-inline-size: 0 keeps the item box inside its track so the canvas never
|
|
1775
1748
|
* grows past its container. It does not stop content from painting outside a
|
|
1776
1749
|
* narrow track — that distinction is the primitive's contract, not a bug. */
|
|
1777
1750
|
:where(.column-layout) > * {
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
/* Tier 2. A definite start line, running to the end of the canvas unless a
|
|
1783
|
-
* .column-span-* refines the end. `column-start-9` alone is therefore the
|
|
1784
|
-
* last four columns rather than an implicit 21-line overflow. */
|
|
1785
|
-
:where(.column-start-1) {
|
|
1786
|
-
grid-column: 1 / -1;
|
|
1787
|
-
}
|
|
1788
|
-
:where(.column-start-2) {
|
|
1789
|
-
grid-column: 2 / -1;
|
|
1790
|
-
}
|
|
1791
|
-
:where(.column-start-3) {
|
|
1792
|
-
grid-column: 3 / -1;
|
|
1793
|
-
}
|
|
1794
|
-
:where(.column-start-4) {
|
|
1795
|
-
grid-column: 4 / -1;
|
|
1796
|
-
}
|
|
1797
|
-
:where(.column-start-5) {
|
|
1798
|
-
grid-column: 5 / -1;
|
|
1799
|
-
}
|
|
1800
|
-
:where(.column-start-6) {
|
|
1801
|
-
grid-column: 6 / -1;
|
|
1802
|
-
}
|
|
1803
|
-
:where(.column-start-7) {
|
|
1804
|
-
grid-column: 7 / -1;
|
|
1805
|
-
}
|
|
1806
|
-
:where(.column-start-8) {
|
|
1807
|
-
grid-column: 8 / -1;
|
|
1808
|
-
}
|
|
1809
|
-
:where(.column-start-9) {
|
|
1810
|
-
grid-column: 9 / -1;
|
|
1811
|
-
}
|
|
1812
|
-
:where(.column-start-10) {
|
|
1813
|
-
grid-column: 10 / -1;
|
|
1814
|
-
}
|
|
1815
|
-
:where(.column-start-11) {
|
|
1816
|
-
grid-column: 11 / -1;
|
|
1817
|
-
}
|
|
1818
|
-
:where(.column-start-12) {
|
|
1819
|
-
grid-column: 12 / -1;
|
|
1820
|
-
}
|
|
1751
|
+
--column-start: initial;
|
|
1752
|
+
--column-span: initial;
|
|
1821
1753
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
* just the offending region. */
|
|
1826
|
-
:where(.column-span-1) {
|
|
1827
|
-
grid-column-end: span 1;
|
|
1828
|
-
}
|
|
1829
|
-
:where(.column-span-2) {
|
|
1830
|
-
grid-column-end: span 2;
|
|
1831
|
-
}
|
|
1832
|
-
:where(.column-span-3) {
|
|
1833
|
-
grid-column-end: span 3;
|
|
1834
|
-
}
|
|
1835
|
-
:where(.column-span-4) {
|
|
1836
|
-
grid-column-end: span 4;
|
|
1837
|
-
}
|
|
1838
|
-
:where(.column-span-5) {
|
|
1839
|
-
grid-column-end: span 5;
|
|
1840
|
-
}
|
|
1841
|
-
:where(.column-span-6) {
|
|
1842
|
-
grid-column-end: span 6;
|
|
1843
|
-
}
|
|
1844
|
-
:where(.column-span-7) {
|
|
1845
|
-
grid-column-end: span 7;
|
|
1846
|
-
}
|
|
1847
|
-
:where(.column-span-8) {
|
|
1848
|
-
grid-column-end: span 8;
|
|
1849
|
-
}
|
|
1850
|
-
:where(.column-span-9) {
|
|
1851
|
-
grid-column-end: span 9;
|
|
1852
|
-
}
|
|
1853
|
-
:where(.column-span-10) {
|
|
1854
|
-
grid-column-end: span 10;
|
|
1855
|
-
}
|
|
1856
|
-
:where(.column-span-11) {
|
|
1857
|
-
grid-column-end: span 11;
|
|
1858
|
-
}
|
|
1859
|
-
:where(.column-span-12) {
|
|
1860
|
-
grid-column-end: span 12;
|
|
1754
|
+
grid-column-start: var(--column-start, auto);
|
|
1755
|
+
grid-column-end: span var(--column-span, calc(13 - var(--column-start, 1)));
|
|
1756
|
+
min-inline-size: 0;
|
|
1861
1757
|
}
|
|
1862
1758
|
|
|
1863
1759
|
/*
|
|
@@ -2138,6 +2034,8 @@ small {
|
|
|
2138
2034
|
* --control-cursor
|
|
2139
2035
|
* --control-border-block-end-color
|
|
2140
2036
|
* --control-border-block-end-width
|
|
2037
|
+
* --control-hover-border (input and textarea)
|
|
2038
|
+
* --control-hover-bg (select)
|
|
2141
2039
|
*/
|
|
2142
2040
|
.input,
|
|
2143
2041
|
.textarea,
|
|
@@ -2165,8 +2063,8 @@ small {
|
|
|
2165
2063
|
cursor: var(--control-cursor);
|
|
2166
2064
|
/* No outline: none here — the core :focus fallback must survive for browsers
|
|
2167
2065
|
without :focus-visible. Pointer focus is hidden by core's
|
|
2168
|
-
:focus:not(:focus-visible); keyboard focus
|
|
2169
|
-
|
|
2066
|
+
:focus:not(:focus-visible); keyboard focus draws the inset outline below
|
|
2067
|
+
(invalid fields take --form-invalid-border, the documented exception). */
|
|
2170
2068
|
transition:
|
|
2171
2069
|
border-color var(--duration),
|
|
2172
2070
|
box-shadow var(--duration),
|
|
@@ -2182,19 +2080,24 @@ small {
|
|
|
2182
2080
|
|
|
2183
2081
|
.input:hover:not(:disabled),
|
|
2184
2082
|
.textarea:hover:not(:disabled) {
|
|
2185
|
-
--control-border: var(--text-subtle);
|
|
2083
|
+
--control-border: var(--control-hover-border, var(--text-subtle));
|
|
2186
2084
|
}
|
|
2187
2085
|
|
|
2188
2086
|
.select:hover:not(:disabled) {
|
|
2189
|
-
--control-bg: var(--surface-subtle);
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
.
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2087
|
+
--control-bg: var(--control-hover-bg, var(--surface-subtle));
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
/* Field focus is an inset outline: offset = -width keeps every pixel inside
|
|
2091
|
+
the control's border box (any smaller offset bleeds outward), so it covers
|
|
2092
|
+
the border, never touches a .join neighbour or a scrollport edge, and needs
|
|
2093
|
+
no group treatment. Width follows --focus-ring-width, including its
|
|
2094
|
+
prefers-contrast step. Border = shape and validation, outline = focus. */
|
|
2095
|
+
.input:focus-visible:not(:disabled),
|
|
2096
|
+
.textarea:focus-visible:not(:disabled),
|
|
2097
|
+
.select:focus-visible:not(:disabled) {
|
|
2098
|
+
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
|
|
2099
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
2100
|
+
box-shadow: none;
|
|
2198
2101
|
}
|
|
2199
2102
|
|
|
2200
2103
|
.input[readonly],
|
|
@@ -2447,9 +2350,8 @@ small {
|
|
|
2447
2350
|
|
|
2448
2351
|
.check:focus-visible,
|
|
2449
2352
|
.radio:focus-visible {
|
|
2450
|
-
--
|
|
2451
|
-
outline:
|
|
2452
|
-
box-shadow: var(--focus-ring-shadow);
|
|
2353
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
2354
|
+
outline-offset: var(--focus-outline-offset);
|
|
2453
2355
|
}
|
|
2454
2356
|
|
|
2455
2357
|
.check:disabled,
|
|
@@ -2571,8 +2473,8 @@ small {
|
|
|
2571
2473
|
}
|
|
2572
2474
|
|
|
2573
2475
|
.file:focus-visible {
|
|
2574
|
-
outline:
|
|
2575
|
-
|
|
2476
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
2477
|
+
outline-offset: var(--focus-outline-offset);
|
|
2576
2478
|
}
|
|
2577
2479
|
|
|
2578
2480
|
/* Native color picker — keep the browser's dialog and selected-color UI. The
|
|
@@ -2608,8 +2510,8 @@ small {
|
|
|
2608
2510
|
}
|
|
2609
2511
|
|
|
2610
2512
|
.color:focus-visible {
|
|
2611
|
-
outline:
|
|
2612
|
-
|
|
2513
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
2514
|
+
outline-offset: var(--focus-outline-offset);
|
|
2613
2515
|
}
|
|
2614
2516
|
|
|
2615
2517
|
@media (forced-colors: active) {
|
|
@@ -2973,9 +2875,8 @@ small {
|
|
|
2973
2875
|
}
|
|
2974
2876
|
|
|
2975
2877
|
.switch:focus-visible {
|
|
2976
|
-
--
|
|
2977
|
-
outline:
|
|
2978
|
-
box-shadow: var(--focus-ring-shadow);
|
|
2878
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
2879
|
+
outline-offset: var(--focus-outline-offset);
|
|
2979
2880
|
}
|
|
2980
2881
|
|
|
2981
2882
|
.switch:disabled {
|
|
@@ -3011,6 +2912,10 @@ small {
|
|
|
3011
2912
|
* --range-track-height
|
|
3012
2913
|
* --range-thumb-bg
|
|
3013
2914
|
* --range-track-bg
|
|
2915
|
+
* --range-fill-bg
|
|
2916
|
+
*
|
|
2917
|
+
* Runtime-written (by actual-css/js/range, never set by hand):
|
|
2918
|
+
* --range-progress
|
|
3014
2919
|
*/
|
|
3015
2920
|
|
|
3016
2921
|
/* Intent boundary — inherited intents do not tint this component.
|
|
@@ -3020,10 +2925,17 @@ small {
|
|
|
3020
2925
|
}
|
|
3021
2926
|
|
|
3022
2927
|
.range {
|
|
3023
|
-
--range-thumb-size: 1.
|
|
3024
|
-
--range-track-height: 0.
|
|
2928
|
+
--range-thumb-size: 1.125rem;
|
|
2929
|
+
--range-track-height: 0.625rem;
|
|
3025
2930
|
--range-thumb-bg: var(--intent, var(--primary));
|
|
3026
2931
|
--range-track-bg: var(--border);
|
|
2932
|
+
--range-fill-bg: var(--intent, var(--primary));
|
|
2933
|
+
/* Fill share, written by actual-css/js/range. 0% with no runtime means a
|
|
2934
|
+
plain track — the control stays fully usable without JavaScript. */
|
|
2935
|
+
--range-progress: 0%;
|
|
2936
|
+
/* Logical fill direction: the gradient below cannot use flow-relative
|
|
2937
|
+
keywords, so RTL flips the angle through this hook instead. */
|
|
2938
|
+
--range-fill-from: to right;
|
|
3027
2939
|
|
|
3028
2940
|
appearance: none;
|
|
3029
2941
|
-webkit-appearance: none;
|
|
@@ -3033,16 +2945,29 @@ small {
|
|
|
3033
2945
|
cursor: pointer;
|
|
3034
2946
|
}
|
|
3035
2947
|
|
|
2948
|
+
:where([dir="rtl"]) .range,
|
|
2949
|
+
.range:dir(rtl) {
|
|
2950
|
+
--range-fill-from: to left;
|
|
2951
|
+
}
|
|
2952
|
+
|
|
3036
2953
|
.range::-webkit-slider-runnable-track {
|
|
3037
2954
|
block-size: var(--range-track-height);
|
|
3038
2955
|
border-radius: var(--radius-full);
|
|
3039
|
-
background:
|
|
2956
|
+
background: linear-gradient(
|
|
2957
|
+
var(--range-fill-from),
|
|
2958
|
+
var(--range-fill-bg) var(--range-progress),
|
|
2959
|
+
var(--range-track-bg) var(--range-progress)
|
|
2960
|
+
);
|
|
3040
2961
|
}
|
|
3041
2962
|
|
|
3042
2963
|
.range::-moz-range-track {
|
|
3043
2964
|
block-size: var(--range-track-height);
|
|
3044
2965
|
border-radius: var(--radius-full);
|
|
3045
|
-
background:
|
|
2966
|
+
background: linear-gradient(
|
|
2967
|
+
var(--range-fill-from),
|
|
2968
|
+
var(--range-fill-bg) var(--range-progress),
|
|
2969
|
+
var(--range-track-bg) var(--range-progress)
|
|
2970
|
+
);
|
|
3046
2971
|
}
|
|
3047
2972
|
|
|
3048
2973
|
.range::-webkit-slider-thumb {
|
|
@@ -3055,6 +2980,10 @@ small {
|
|
|
3055
2980
|
background: var(--range-thumb-bg);
|
|
3056
2981
|
margin-block-start: calc((var(--range-track-height) - var(--range-thumb-size)) / 2);
|
|
3057
2982
|
cursor: inherit;
|
|
2983
|
+
/* Permanent surface separator: the fill defaults to the thumb color, so
|
|
2984
|
+
without this the thumb would melt into the filled part of the track.
|
|
2985
|
+
Focus-visible keeps this ring and adds the --focus ring outside it. */
|
|
2986
|
+
box-shadow: 0 0 0 2px var(--surface-opaque);
|
|
3058
2987
|
transition: box-shadow var(--duration);
|
|
3059
2988
|
}
|
|
3060
2989
|
|
|
@@ -3065,6 +2994,7 @@ small {
|
|
|
3065
2994
|
border: 0;
|
|
3066
2995
|
background: var(--range-thumb-bg);
|
|
3067
2996
|
cursor: inherit;
|
|
2997
|
+
box-shadow: 0 0 0 2px var(--surface-opaque);
|
|
3068
2998
|
transition: box-shadow var(--duration);
|
|
3069
2999
|
}
|
|
3070
3000
|
|
|
@@ -3073,20 +3003,23 @@ small {
|
|
|
3073
3003
|
}
|
|
3074
3004
|
|
|
3075
3005
|
/*
|
|
3076
|
-
*
|
|
3077
|
-
*
|
|
3078
|
-
*
|
|
3006
|
+
* A thumb pseudo-element takes no reliable outline, so its ring is a solid
|
|
3007
|
+
* --focus shadow (the transparent outline above covers forced colors). The
|
|
3008
|
+
* surface-coloured separator keeps the ring visually distinct from the
|
|
3009
|
+
* coloured thumb — unlike a full-size input where the same gap would
|
|
3010
|
+
* read as an unwanted void. It doubles as the resting separator between
|
|
3011
|
+
* the thumb and the same-coloured fill.
|
|
3079
3012
|
*/
|
|
3080
3013
|
.range:focus-visible::-webkit-slider-thumb {
|
|
3081
3014
|
box-shadow:
|
|
3082
|
-
0 0 0
|
|
3083
|
-
0 0 0 calc(
|
|
3015
|
+
0 0 0 2px var(--surface-opaque),
|
|
3016
|
+
0 0 0 calc(2px + var(--focus-ring-width)) var(--focus);
|
|
3084
3017
|
}
|
|
3085
3018
|
|
|
3086
3019
|
.range:focus-visible::-moz-range-thumb {
|
|
3087
3020
|
box-shadow:
|
|
3088
|
-
0 0 0
|
|
3089
|
-
0 0 0 calc(
|
|
3021
|
+
0 0 0 2px var(--surface-opaque),
|
|
3022
|
+
0 0 0 calc(2px + var(--focus-ring-width)) var(--focus);
|
|
3090
3023
|
}
|
|
3091
3024
|
|
|
3092
3025
|
.range:disabled {
|
|
@@ -3094,22 +3027,48 @@ small {
|
|
|
3094
3027
|
cursor: not-allowed;
|
|
3095
3028
|
opacity: var(--disabled-opacity);
|
|
3096
3029
|
}
|
|
3097
|
-
|
|
3098
3030
|
/* Forced colors: the track must stay a visible line against the page (Canvas
|
|
3099
3031
|
would merge with it) and the thumb needs a real boundary to remain
|
|
3100
|
-
distinguishable from the track.
|
|
3101
|
-
|
|
3032
|
+
distinguishable from the track. The fill keeps its two-tone split with
|
|
3033
|
+
system colors so the value stays readable. Disabled keeps GrayText via
|
|
3034
|
+
:disabled. Focus stays on the transparent outline of :focus-visible. Track
|
|
3035
|
+
and thumb fills are written on the properties, not only the hooks: an
|
|
3036
|
+
application hook override at the same specificity loads after this file
|
|
3037
|
+
and would win the variable, leaving a Canvas-remapped fill.
|
|
3038
|
+
The split lives in a background-image, which forced-color-adjust: auto
|
|
3039
|
+
drops — so the tracks opt out locally. The colors stay system keywords,
|
|
3040
|
+
keeping native contrast adaptation; only the host keeps adjusting. */
|
|
3102
3041
|
@media (forced-colors: active) {
|
|
3103
3042
|
.range {
|
|
3104
3043
|
--range-track-bg: CanvasText;
|
|
3105
3044
|
--range-thumb-bg: ButtonFace;
|
|
3045
|
+
--range-fill-bg: Highlight;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
.range::-webkit-slider-runnable-track {
|
|
3049
|
+
forced-color-adjust: none;
|
|
3050
|
+
background: linear-gradient(
|
|
3051
|
+
var(--range-fill-from),
|
|
3052
|
+
Highlight var(--range-progress),
|
|
3053
|
+
CanvasText var(--range-progress)
|
|
3054
|
+
);
|
|
3106
3055
|
}
|
|
3107
3056
|
|
|
3057
|
+
.range::-moz-range-track {
|
|
3058
|
+
forced-color-adjust: none;
|
|
3059
|
+
background: linear-gradient(
|
|
3060
|
+
var(--range-fill-from),
|
|
3061
|
+
Highlight var(--range-progress),
|
|
3062
|
+
CanvasText var(--range-progress)
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3108
3065
|
.range::-webkit-slider-thumb {
|
|
3066
|
+
background: ButtonFace;
|
|
3109
3067
|
border: var(--border-width) solid ButtonText;
|
|
3110
3068
|
}
|
|
3111
3069
|
|
|
3112
3070
|
.range::-moz-range-thumb {
|
|
3071
|
+
background: ButtonFace;
|
|
3113
3072
|
border: var(--border-width) solid ButtonText;
|
|
3114
3073
|
}
|
|
3115
3074
|
}
|
|
@@ -3121,20 +3080,29 @@ small {
|
|
|
3121
3080
|
*
|
|
3122
3081
|
* Each .choice-card is a <label> wrapping a native radio or checkbox. When
|
|
3123
3082
|
* :has() is supported the input is visually hidden and the card mirrors its
|
|
3124
|
-
* state:
|
|
3125
|
-
*
|
|
3126
|
-
* native input stays visible
|
|
3083
|
+
* state: an indicator on the leading edge (a ring for radio, a box for
|
|
3084
|
+
* checkbox) that fills with a check, checked border and tint, focus ring,
|
|
3085
|
+
* disabled dimming. Without :has() the native input stays visible in the
|
|
3086
|
+
* indicator's slot, so selection state never disappears.
|
|
3087
|
+
*
|
|
3088
|
+
* The card is a two-column grid: the indicator in the first column, level
|
|
3089
|
+
* with the first row, and each child element of the label on its own row in
|
|
3090
|
+
* the second. Wrap loose text in an element. A corner badge for tiles is a
|
|
3091
|
+
* documented recipe on these same layers, not a variant.
|
|
3127
3092
|
*
|
|
3128
|
-
*
|
|
3129
|
-
*
|
|
3130
|
-
*
|
|
3093
|
+
* .chip is the compact pill form (filter chips, selectable tags): control
|
|
3094
|
+
* height, and a leading dot that turns into the check in a fixed slot, so
|
|
3095
|
+
* checking never moves the label.
|
|
3131
3096
|
*
|
|
3132
3097
|
* Public hooks:
|
|
3133
3098
|
* --choice-card-pad
|
|
3099
|
+
* --choice-card-gap
|
|
3134
3100
|
* --choice-card-radius
|
|
3135
3101
|
* --choice-card-border
|
|
3136
3102
|
* --choice-card-bg
|
|
3137
3103
|
* --choice-card-check-size
|
|
3104
|
+
* --choice-chip-dot (read-only, on .chip ::before: the check-slot centre as a
|
|
3105
|
+
* circle() position, for a theme that grows the dot into a fill)
|
|
3138
3106
|
*/
|
|
3139
3107
|
:where(.choice-card) {
|
|
3140
3108
|
--intent: initial;
|
|
@@ -3143,19 +3111,25 @@ small {
|
|
|
3143
3111
|
|
|
3144
3112
|
.choice-card {
|
|
3145
3113
|
--choice-card-pad: var(--density-space, var(--space-40));
|
|
3114
|
+
--choice-card-gap: var(--space-30);
|
|
3146
3115
|
--choice-card-radius: var(--radius);
|
|
3147
3116
|
--choice-card-border: var(--border);
|
|
3148
3117
|
--choice-card-bg: var(--surface);
|
|
3149
|
-
--choice-card-check-size: 1.
|
|
3118
|
+
--choice-card-check-size: 1.25em;
|
|
3150
3119
|
|
|
3151
3120
|
position: relative;
|
|
3152
|
-
display:
|
|
3121
|
+
display: grid;
|
|
3122
|
+
grid-template-columns: var(--choice-card-check-size) minmax(0, 1fr);
|
|
3123
|
+
/* why: a card stretched by a taller grid neighbour keeps its rows at the
|
|
3124
|
+
top instead of spreading them. */
|
|
3125
|
+
align-content: start;
|
|
3126
|
+
column-gap: var(--choice-card-gap);
|
|
3153
3127
|
padding: var(--choice-card-pad);
|
|
3154
3128
|
border: var(--border-width) solid var(--form-invalid-border, var(--choice-card-border));
|
|
3155
3129
|
border-radius: var(--choice-card-radius);
|
|
3156
3130
|
background: var(--choice-card-bg);
|
|
3157
3131
|
color: var(--text);
|
|
3158
|
-
/* Consumed so .sm/.lg reach the card; the
|
|
3132
|
+
/* Consumed so .sm/.lg reach the card; the indicator's em sizing follows. */
|
|
3159
3133
|
font-size: var(--control-font-size);
|
|
3160
3134
|
cursor: pointer;
|
|
3161
3135
|
transition:
|
|
@@ -3164,6 +3138,18 @@ small {
|
|
|
3164
3138
|
box-shadow var(--duration);
|
|
3165
3139
|
}
|
|
3166
3140
|
|
|
3141
|
+
.choice-card > * {
|
|
3142
|
+
grid-column: 2;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
/* The input owns the indicator's cell: visible there without :has(), hidden
|
|
3146
|
+
in it with :has(). */
|
|
3147
|
+
.choice-card > :is(input[type="radio"], input[type="checkbox"]) {
|
|
3148
|
+
grid-area: 1 / 1;
|
|
3149
|
+
place-self: center;
|
|
3150
|
+
margin: 0;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3167
3153
|
/* The native input is visually hidden but stays focusable and participates in
|
|
3168
3154
|
form submission and keyboard navigation (sr-only pattern). Guarded: without
|
|
3169
3155
|
:has() the card cannot mirror the input's state, so the visible native
|
|
@@ -3184,8 +3170,8 @@ small {
|
|
|
3184
3170
|
|
|
3185
3171
|
/* Focus ring on the label follows the hidden input's focus. */
|
|
3186
3172
|
.choice-card:has(:focus-visible) {
|
|
3187
|
-
outline:
|
|
3188
|
-
|
|
3173
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
3174
|
+
outline-offset: var(--focus-outline-offset);
|
|
3189
3175
|
}
|
|
3190
3176
|
|
|
3191
3177
|
.choice-card:has(:disabled) {
|
|
@@ -3209,32 +3195,41 @@ small {
|
|
|
3209
3195
|
}
|
|
3210
3196
|
}
|
|
3211
3197
|
|
|
3212
|
-
/*
|
|
3213
|
-
the
|
|
3198
|
+
/* Indicator: ::before is the ring or box, ::after the check. Both share the
|
|
3199
|
+
input's cell, centred on the first line — not the first row, which a long
|
|
3200
|
+
label wraps — and stay hidden until :has() can mirror the input. The em
|
|
3201
|
+
margin is the lh-less fallback at the default 1.5 line-height. */
|
|
3214
3202
|
.choice-card::before,
|
|
3215
3203
|
.choice-card::after {
|
|
3216
3204
|
content: "";
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3205
|
+
grid-area: 1 / 1;
|
|
3206
|
+
align-self: start;
|
|
3207
|
+
margin-block-start: calc((1.5em - var(--choice-card-check-size)) / 2);
|
|
3208
|
+
margin-block-start: calc((1lh - var(--choice-card-check-size)) / 2);
|
|
3220
3209
|
inline-size: var(--choice-card-check-size);
|
|
3221
3210
|
block-size: var(--choice-card-check-size);
|
|
3222
3211
|
transform: scale(0);
|
|
3223
|
-
transition:
|
|
3212
|
+
transition:
|
|
3213
|
+
transform var(--duration),
|
|
3214
|
+
background-color var(--duration),
|
|
3215
|
+
border-color var(--duration);
|
|
3224
3216
|
}
|
|
3225
3217
|
|
|
3226
|
-
/*
|
|
3218
|
+
/* A border, not an inset shadow: forced colors drop shadows, and the resting
|
|
3219
|
+
ring is the only sign of an unchecked option. The invalid border reaches it
|
|
3220
|
+
through the same hook as the card's. */
|
|
3227
3221
|
.choice-card::before {
|
|
3228
|
-
|
|
3229
|
-
box-shadow: 0 0 0 2px var(--surface);
|
|
3222
|
+
border: max(var(--border-width), 1.5px) solid var(--form-invalid-border, var(--text-muted));
|
|
3230
3223
|
z-index: 1;
|
|
3231
3224
|
}
|
|
3232
3225
|
|
|
3233
|
-
|
|
3226
|
+
/* :where() keeps the shape at single-class weight, so .chip can reshape the
|
|
3227
|
+
layer without out-arguing the input-type test. */
|
|
3228
|
+
.choice-card:where(:has(input[type="radio"]))::before {
|
|
3234
3229
|
border-radius: var(--radius-full);
|
|
3235
3230
|
}
|
|
3236
3231
|
|
|
3237
|
-
.choice-card:has(input[type="checkbox"])::before {
|
|
3232
|
+
.choice-card:where(:has(input[type="checkbox"]))::before {
|
|
3238
3233
|
border-radius: var(--radius-sm);
|
|
3239
3234
|
}
|
|
3240
3235
|
|
|
@@ -3246,18 +3241,86 @@ small {
|
|
|
3246
3241
|
z-index: 2;
|
|
3247
3242
|
}
|
|
3248
3243
|
|
|
3249
|
-
|
|
3244
|
+
/* Guarded with the input's hiding above: a resting ring beside a visible
|
|
3245
|
+
native input would draw the indicator twice. */
|
|
3246
|
+
@supports selector(:has(*)) {
|
|
3247
|
+
.choice-card::before {
|
|
3248
|
+
transform: none;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
.choice-card:has(:checked)::before {
|
|
3253
|
+
border-color: var(--intent, var(--state-selected));
|
|
3254
|
+
background: var(--intent, var(--state-selected));
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3250
3257
|
.choice-card:has(:checked)::after {
|
|
3251
3258
|
transform: scale(1);
|
|
3252
3259
|
}
|
|
3253
3260
|
|
|
3261
|
+
/* Chip — the rules above stay in force; this section only relocates the
|
|
3262
|
+
indicator. Must follow them: several ties are won by source order. */
|
|
3263
|
+
.choice-card.chip {
|
|
3264
|
+
--choice-card-pad: 0 var(--control-pad-x);
|
|
3265
|
+
--choice-card-radius: var(--radius-full);
|
|
3266
|
+
--choice-card-check-size: 1em;
|
|
3267
|
+
|
|
3268
|
+
display: inline-flex;
|
|
3269
|
+
align-items: center;
|
|
3270
|
+
min-block-size: var(--control-size);
|
|
3271
|
+
line-height: 1.2;
|
|
3272
|
+
/* The dot layer sits under the label at z-index -1. */
|
|
3273
|
+
isolation: isolate;
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
/* ::before is a full-bleed layer clipped to a dot centred on the check slot,
|
|
3277
|
+
so a theme can grow the same clip into a selected fill. The chip itself
|
|
3278
|
+
must not clip (its focus ring is an outline), hence the inherited radius. */
|
|
3279
|
+
.choice-card.chip::before {
|
|
3280
|
+
--choice-chip-dot: calc(var(--control-pad-x) + var(--choice-card-check-size) / 2) 50%;
|
|
3281
|
+
|
|
3282
|
+
position: absolute;
|
|
3283
|
+
inset: 0;
|
|
3284
|
+
/* why: an absolute box honours self-alignment — the card's start would
|
|
3285
|
+
shrink this layer to 0 height and the dot with it. */
|
|
3286
|
+
align-self: auto;
|
|
3287
|
+
margin: 0;
|
|
3288
|
+
inline-size: auto;
|
|
3289
|
+
block-size: auto;
|
|
3290
|
+
border: 0;
|
|
3291
|
+
border-radius: inherit;
|
|
3292
|
+
background: var(--text-subtle);
|
|
3293
|
+
transform: none;
|
|
3294
|
+
clip-path: circle(0.1875em at var(--choice-chip-dot));
|
|
3295
|
+
z-index: -1;
|
|
3296
|
+
transition: clip-path var(--duration);
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
.choice-card.chip:has(:checked)::before {
|
|
3300
|
+
clip-path: circle(0 at var(--choice-chip-dot));
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
/* The check keeps the card's scale-in, in flow ahead of the label. */
|
|
3304
|
+
.choice-card.chip::after {
|
|
3305
|
+
order: -1;
|
|
3306
|
+
flex: none;
|
|
3307
|
+
align-self: center;
|
|
3308
|
+
margin-block: 0;
|
|
3309
|
+
margin-inline: 0 0.375em;
|
|
3310
|
+
background: var(--intent, var(--state-selected));
|
|
3311
|
+
-webkit-mask-size: contain;
|
|
3312
|
+
mask-size: contain;
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3254
3315
|
/* The native input is hidden and the checked marker is custom-drawn: without
|
|
3255
3316
|
this repair, forced colors pushes disc and check to the same system color
|
|
3256
3317
|
and the state disappears. Highlight/Canvas/HighlightText are preserved as-is,
|
|
3257
3318
|
so no forced-color-adjust is needed. The pseudo is not focusable — the
|
|
3258
|
-
outline
|
|
3319
|
+
outline separates the disc from whatever it overlaps (the card's border,
|
|
3320
|
+
for a corner-badge recipe), not a focus channel. */
|
|
3259
3321
|
@media (forced-colors: active) {
|
|
3260
3322
|
.choice-card:has(:checked)::before {
|
|
3323
|
+
border-color: Highlight;
|
|
3261
3324
|
background: Highlight;
|
|
3262
3325
|
outline: 2px solid Canvas;
|
|
3263
3326
|
}
|
|
@@ -3265,6 +3328,20 @@ small {
|
|
|
3265
3328
|
.choice-card:has(:checked)::after {
|
|
3266
3329
|
background: HighlightText;
|
|
3267
3330
|
}
|
|
3331
|
+
|
|
3332
|
+
/* A chip has no disc behind its check: the check carries the state alone,
|
|
3333
|
+
and dropping the checked layer also drops any theme fill. */
|
|
3334
|
+
.choice-card.chip::before {
|
|
3335
|
+
background: CanvasText;
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
.choice-card.chip:has(:checked)::before {
|
|
3339
|
+
content: none;
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
.choice-card.chip:has(:checked)::after {
|
|
3343
|
+
background: CanvasText;
|
|
3344
|
+
}
|
|
3268
3345
|
}
|
|
3269
3346
|
|
|
3270
3347
|
/*
|
|
@@ -3278,7 +3355,6 @@ small {
|
|
|
3278
3355
|
* Arbitrary menu and popover behavior belongs elsewhere.
|
|
3279
3356
|
*
|
|
3280
3357
|
* Internal:
|
|
3281
|
-
* --control-border
|
|
3282
3358
|
* --control-bg
|
|
3283
3359
|
*/
|
|
3284
3360
|
/* This enhancement is excluded from forced-colors, where the native/shared
|
|
@@ -3303,11 +3379,12 @@ small {
|
|
|
3303
3379
|
box-shadow: var(--shadow-popout);
|
|
3304
3380
|
}
|
|
3305
3381
|
|
|
3382
|
+
/* Same inset outline as a focused field (control.css). */
|
|
3306
3383
|
&:open {
|
|
3307
|
-
--control-border: var(--border);
|
|
3308
3384
|
--control-bg: var(--surface-raised);
|
|
3309
|
-
outline:
|
|
3310
|
-
|
|
3385
|
+
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
|
|
3386
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
3387
|
+
box-shadow: none;
|
|
3311
3388
|
}
|
|
3312
3389
|
|
|
3313
3390
|
&::picker-icon {
|
|
@@ -3539,7 +3616,7 @@ small {
|
|
|
3539
3616
|
padding-block: 0;
|
|
3540
3617
|
padding-inline: calc((var(--otp-cell-size) - 1ch) / 2) 0;
|
|
3541
3618
|
border: 0;
|
|
3542
|
-
outline:
|
|
3619
|
+
outline: none;
|
|
3543
3620
|
box-shadow: none;
|
|
3544
3621
|
background: transparent;
|
|
3545
3622
|
color: var(--text);
|
|
@@ -3564,6 +3641,14 @@ small {
|
|
|
3564
3641
|
character after it: the state rules select the cells as following siblings
|
|
3565
3642
|
(input:focus ~ span, input:disabled ~ span, …), so that order is part of the
|
|
3566
3643
|
contract. */
|
|
3644
|
+
/* The documented markup puts .input on the native input, so the field ring
|
|
3645
|
+
(.input:focus-visible:not(:disabled), control.css) would outrank the reset
|
|
3646
|
+
above and draw that group ring; this selector must stay one step above it.
|
|
3647
|
+
No transparent outline either: forced colors would paint it as a ring. */
|
|
3648
|
+
.otp > input:focus-visible:not(:disabled) {
|
|
3649
|
+
outline: none;
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3567
3652
|
.otp > input:focus ~ span {
|
|
3568
3653
|
--otp-cell-border: var(--focus);
|
|
3569
3654
|
--otp-cell-border-width: calc(var(--border-width) * 2);
|
|
@@ -3636,7 +3721,6 @@ small {
|
|
|
3636
3721
|
* --btn-min-size
|
|
3637
3722
|
* --btn-pad-x
|
|
3638
3723
|
* --btn-radius
|
|
3639
|
-
* --btn-focus-color
|
|
3640
3724
|
* --btn-gap
|
|
3641
3725
|
* --btn-hover-overlay
|
|
3642
3726
|
* --btn-default-bg
|
|
@@ -3649,7 +3733,6 @@ small {
|
|
|
3649
3733
|
* --btn-fg
|
|
3650
3734
|
* --btn-border
|
|
3651
3735
|
* --btn-default-hover-bg
|
|
3652
|
-
* --btn-focus-ring-color
|
|
3653
3736
|
* --btn-border-block-end-color
|
|
3654
3737
|
* --btn-border-block-end-width
|
|
3655
3738
|
* --spinner-width
|
|
@@ -3676,7 +3759,6 @@ small {
|
|
|
3676
3759
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
3677
3760
|
--btn-default-hover-bg: var(--btn-bg);
|
|
3678
3761
|
--btn-radius: var(--radius);
|
|
3679
|
-
--btn-focus-color: var(--intent, var(--focus));
|
|
3680
3762
|
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
3681
3763
|
|
|
3682
3764
|
/* Positioning context for indicators, overlays, and grouped states. */
|
|
@@ -3718,12 +3800,6 @@ small {
|
|
|
3718
3800
|
padding: 0;
|
|
3719
3801
|
}
|
|
3720
3802
|
|
|
3721
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
3722
|
-
.btn {
|
|
3723
|
-
--btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
|
|
3724
|
-
}
|
|
3725
|
-
}
|
|
3726
|
-
|
|
3727
3803
|
/* .solid/.soft/.outline are shared via variants.css. The baseline --btn-*
|
|
3728
3804
|
fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
|
|
3729
3805
|
.btn.ghost {
|
|
@@ -3811,15 +3887,13 @@ small {
|
|
|
3811
3887
|
text-decoration-thickness: 0.12em;
|
|
3812
3888
|
}
|
|
3813
3889
|
|
|
3890
|
+
/* why: the ring must contrast with the surface around the button, not with
|
|
3891
|
+
the button, so it is --focus for every intent and variant — the theme
|
|
3892
|
+
guarantees --focus against --surface and --surface-solid, which covers
|
|
3893
|
+
the page, a solid band and nested surfaces without tracking any context. */
|
|
3814
3894
|
.btn:focus-visible {
|
|
3815
|
-
outline:
|
|
3816
|
-
|
|
3817
|
-
}
|
|
3818
|
-
|
|
3819
|
-
.btn.link:focus-visible {
|
|
3820
|
-
outline: var(--focus-outline);
|
|
3821
|
-
outline-offset: 0.2em;
|
|
3822
|
-
box-shadow: none;
|
|
3895
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
3896
|
+
outline-offset: var(--focus-outline-offset);
|
|
3823
3897
|
}
|
|
3824
3898
|
|
|
3825
3899
|
.btn:disabled {
|
|
@@ -3865,6 +3939,98 @@ a.btn[aria-disabled="true"] {
|
|
|
3865
3939
|
}
|
|
3866
3940
|
}
|
|
3867
3941
|
|
|
3942
|
+
/*
|
|
3943
|
+
* Close button — the shared X that removes or closes the thing it sits in.
|
|
3944
|
+
*
|
|
3945
|
+
* Docs: docs/pages/components/close.md
|
|
3946
|
+
*
|
|
3947
|
+
* .close is always the X, painted from the --icon-close mask: an empty
|
|
3948
|
+
* <button class="close" aria-label="…"> needs no icon font and no text glyph.
|
|
3949
|
+
* The mechanism stays with the container (command="request-close",
|
|
3950
|
+
* method="dialog", command="--dismiss", application logic); the class only
|
|
3951
|
+
* names the visual action.
|
|
3952
|
+
*
|
|
3953
|
+
* The containers that host one (alert, badge) tune it from their own files by
|
|
3954
|
+
* declaring the size hooks and their geometry; this file owns the control
|
|
3955
|
+
* and the dialog-surface corner, shared by .modal and .drawer.
|
|
3956
|
+
*
|
|
3957
|
+
* Public hooks:
|
|
3958
|
+
* --close-size
|
|
3959
|
+
* --close-icon-size
|
|
3960
|
+
*/
|
|
3961
|
+
.close {
|
|
3962
|
+
display: inline-grid;
|
|
3963
|
+
place-items: center;
|
|
3964
|
+
flex: none;
|
|
3965
|
+
inline-size: var(--close-size, var(--control-size));
|
|
3966
|
+
block-size: var(--close-size, var(--control-size));
|
|
3967
|
+
padding: 0;
|
|
3968
|
+
border: 0;
|
|
3969
|
+
border-radius: var(--radius);
|
|
3970
|
+
background: transparent;
|
|
3971
|
+
color: inherit;
|
|
3972
|
+
cursor: pointer;
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
.close::before {
|
|
3976
|
+
content: "";
|
|
3977
|
+
inline-size: var(--close-icon-size, 1rem);
|
|
3978
|
+
block-size: var(--close-icon-size, 1rem);
|
|
3979
|
+
background: currentColor;
|
|
3980
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
3981
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
3982
|
+
}
|
|
3983
|
+
|
|
3984
|
+
/* An inset overlay, not a background: it tints whatever fill the context gave
|
|
3985
|
+
the control (the dialog corner's --surface-subtle) instead of replacing it. */
|
|
3986
|
+
@media (hover: hover) {
|
|
3987
|
+
.close:hover {
|
|
3988
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
.close:active {
|
|
3993
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
.close:focus-visible {
|
|
3997
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
3998
|
+
outline-offset: var(--focus-outline-offset);
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
/* Dialog corner — out of the content flow, anchored to the positioned dialog
|
|
4002
|
+
surface rather than to the header or a scroll container inside it. Matched
|
|
4003
|
+
on the documented positions (the dialog, its form, its header, or a form
|
|
4004
|
+
inside that header) so a .close inside content — an alert in the body —
|
|
4005
|
+
keeps its own place. */
|
|
4006
|
+
:where(dialog):is(.modal, .drawer) > .close,
|
|
4007
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close,
|
|
4008
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close {
|
|
4009
|
+
position: absolute;
|
|
4010
|
+
z-index: 1;
|
|
4011
|
+
inset-block-start: var(--space-20);
|
|
4012
|
+
inset-inline-end: var(--space-20);
|
|
4013
|
+
background: var(--surface-subtle);
|
|
4014
|
+
color: var(--text-muted);
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
@media (hover: hover) {
|
|
4018
|
+
:where(dialog):is(.modal, .drawer) > .close:hover,
|
|
4019
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close:hover,
|
|
4020
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close:hover {
|
|
4021
|
+
color: var(--text);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
@media (forced-colors: active) {
|
|
4026
|
+
/* The X is a currentColor mask fill: without this the system remaps the
|
|
4027
|
+
background to Canvas and the glyph disappears. Kept on the pseudo so the
|
|
4028
|
+
host keeps its native color and focus adaptation. */
|
|
4029
|
+
.close::before {
|
|
4030
|
+
forced-color-adjust: none;
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
|
|
3868
4034
|
/*
|
|
3869
4035
|
* Badges for compact labels, counts, and state hints.
|
|
3870
4036
|
*
|
|
@@ -3881,7 +4047,6 @@ a.btn[aria-disabled="true"] {
|
|
|
3881
4047
|
* --badge-icon-size
|
|
3882
4048
|
* --badge-dot-size
|
|
3883
4049
|
* --badge-radius
|
|
3884
|
-
* --badge-dismiss-icon-size
|
|
3885
4050
|
*
|
|
3886
4051
|
* Internal:
|
|
3887
4052
|
* --badge-default-bg
|
|
@@ -3912,7 +4077,10 @@ a.btn[aria-disabled="true"] {
|
|
|
3912
4077
|
--badge-font-size: var(--font-size-sm);
|
|
3913
4078
|
--badge-icon-size: 1em;
|
|
3914
4079
|
--badge-dot-size: 0.5rem;
|
|
3915
|
-
|
|
4080
|
+
/* Declared on the badge, not the button, so an author can still set them
|
|
4081
|
+
on either. Same element as --badge-size, so .sm/.lg carry through. */
|
|
4082
|
+
--close-size: max(var(--badge-size), 24px);
|
|
4083
|
+
--close-icon-size: 0.875em;
|
|
3916
4084
|
--badge-default-bg: var(--surface-subtle);
|
|
3917
4085
|
--badge-default-fg: var(--intent, var(--text));
|
|
3918
4086
|
--badge-default-border: var(--border);
|
|
@@ -3962,8 +4130,8 @@ a.btn[aria-disabled="true"] {
|
|
|
3962
4130
|
--badge-dot-size: 0.625rem;
|
|
3963
4131
|
}
|
|
3964
4132
|
|
|
3965
|
-
/* Decorative children follow the badge's optical size. Text and
|
|
3966
|
-
|
|
4133
|
+
/* Decorative children follow the badge's optical size. Text and the .close
|
|
4134
|
+
button retain their own boxes. */
|
|
3967
4135
|
.badge > :where(svg, img, [aria-hidden="true"]:not(.dot)) {
|
|
3968
4136
|
flex: none;
|
|
3969
4137
|
/* The shared rule maps --badge-icon-size onto font-size. One em is therefore
|
|
@@ -4016,45 +4184,15 @@ a.btn[aria-disabled="true"] {
|
|
|
4016
4184
|
background-color: currentColor;
|
|
4017
4185
|
}
|
|
4018
4186
|
|
|
4019
|
-
.
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
keep the badge visually compact through the negative margins, but never
|
|
4025
|
-
shrink its action target below that literal floor. */
|
|
4026
|
-
min-inline-size: max(var(--badge-size), 24px);
|
|
4027
|
-
min-block-size: max(var(--badge-size), 24px);
|
|
4187
|
+
/* Removable tag — a .close (components/close.css) inside the pill. WCAG 2.5.8
|
|
4188
|
+
floors pointer targets at 24 CSS px: density and local size may keep the
|
|
4189
|
+
badge visually compact through the negative margins, but never shrink the
|
|
4190
|
+
target below that literal floor. */
|
|
4191
|
+
.badge > .close {
|
|
4028
4192
|
margin-block: -0.25rem;
|
|
4029
4193
|
margin-inline-start: -0.15em;
|
|
4030
4194
|
margin-inline-end: -0.45em;
|
|
4031
|
-
padding: 0.25rem;
|
|
4032
|
-
border: 0;
|
|
4033
4195
|
border-radius: var(--radius-full);
|
|
4034
|
-
background: transparent;
|
|
4035
|
-
color: inherit;
|
|
4036
|
-
font: inherit;
|
|
4037
|
-
line-height: 1;
|
|
4038
|
-
cursor: pointer;
|
|
4039
|
-
}
|
|
4040
|
-
|
|
4041
|
-
/* Dismiss mark — an empty dismiss button paints its own X from the shared
|
|
4042
|
-
--icon-close mask, so no icon font or text glyph is required in the markup
|
|
4043
|
-
(same contract as .alert-dismiss and .dialog-close). A mask box is a block
|
|
4044
|
-
box, so place-items centers the ink itself; a text glyph would only center
|
|
4045
|
-
its line box and land the ink low, because a lowercase x has no descender
|
|
4046
|
-
and only x-height of ascent. A button with its own content keeps it. */
|
|
4047
|
-
.badge > button:empty::before {
|
|
4048
|
-
content: "";
|
|
4049
|
-
inline-size: var(--badge-dismiss-icon-size);
|
|
4050
|
-
block-size: var(--badge-dismiss-icon-size);
|
|
4051
|
-
background: currentColor;
|
|
4052
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
4053
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
4054
|
-
}
|
|
4055
|
-
|
|
4056
|
-
.badge > button:not(:disabled):hover {
|
|
4057
|
-
background: var(--hover-overlay);
|
|
4058
4196
|
}
|
|
4059
4197
|
|
|
4060
4198
|
/* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
|
|
@@ -4145,7 +4283,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4145
4283
|
* --alert-font-size
|
|
4146
4284
|
* --alert-radius
|
|
4147
4285
|
* --alert-icon-size
|
|
4148
|
-
* --alert-dismiss-size
|
|
4149
4286
|
* --alert-border-inline-start-color
|
|
4150
4287
|
* --alert-border-inline-start-width
|
|
4151
4288
|
*
|
|
@@ -4156,7 +4293,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4156
4293
|
* --alert-bg
|
|
4157
4294
|
* --alert-fg
|
|
4158
4295
|
* --alert-border
|
|
4159
|
-
* --alert-dismiss-icon-size
|
|
4160
4296
|
*/
|
|
4161
4297
|
/* Intent boundary — inherited intents do not tint this component.
|
|
4162
4298
|
Local intent classes still win because :where() has zero specificity. */
|
|
@@ -4205,13 +4341,13 @@ a.badge[aria-disabled="true"] {
|
|
|
4205
4341
|
The slots are excluded because they are anatomy, not content: an
|
|
4206
4342
|
admonition's title bar has to stay flush against its body. The list follows
|
|
4207
4343
|
the anatomy, so it grows with it. */
|
|
4208
|
-
.alert > :not(:where(.alert-icon, .
|
|
4344
|
+
.alert > :not(:where(.alert-icon, .close, .alert-title, .alert-body)) {
|
|
4209
4345
|
margin-block: 0;
|
|
4210
4346
|
}
|
|
4211
4347
|
|
|
4212
4348
|
.alert
|
|
4213
|
-
> :not(:where(.alert-icon, .
|
|
4214
|
-
+ :not(:where(.alert-icon, .
|
|
4349
|
+
> :not(:where(.alert-icon, .close, .alert-title, .alert-body))
|
|
4350
|
+
+ :not(:where(.alert-icon, .close, .alert-title, .alert-body)) {
|
|
4215
4351
|
margin-block-start: var(--space-10);
|
|
4216
4352
|
}
|
|
4217
4353
|
|
|
@@ -4249,42 +4385,24 @@ a.badge[aria-disabled="true"] {
|
|
|
4249
4385
|
vertical-align: -0.125em;
|
|
4250
4386
|
}
|
|
4251
4387
|
|
|
4252
|
-
/*
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
--
|
|
4260
|
-
--alert-dismiss-icon-size: 0.875rem;
|
|
4261
|
-
|
|
4262
|
-
display: inline-grid;
|
|
4263
|
-
place-items: center;
|
|
4264
|
-
inline-size: var(--alert-dismiss-size);
|
|
4265
|
-
block-size: var(--alert-dismiss-size);
|
|
4266
|
-
flex: none;
|
|
4267
|
-
padding: 0;
|
|
4268
|
-
border: 0;
|
|
4269
|
-
border-radius: var(--radius);
|
|
4270
|
-
background: transparent;
|
|
4271
|
-
color: currentColor;
|
|
4272
|
-
opacity: 0.65;
|
|
4273
|
-
cursor: pointer;
|
|
4388
|
+
/* Close (components/close.css) — compact, and at reduced opacity so it stays
|
|
4389
|
+
secondary inside an already-colored alert. Standard alerts place it as a
|
|
4390
|
+
direct trailing child; admonitions place it inside .alert-title (see
|
|
4391
|
+
below). The sizes are declared on the alert so an author can still set
|
|
4392
|
+
them on the alert or on the button. */
|
|
4393
|
+
.alert {
|
|
4394
|
+
--close-size: 1.75rem;
|
|
4395
|
+
--close-icon-size: 0.875rem;
|
|
4274
4396
|
}
|
|
4275
4397
|
|
|
4276
|
-
.alert
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
block-size: var(--alert-dismiss-icon-size);
|
|
4280
|
-
background: currentColor;
|
|
4281
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
4282
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
4398
|
+
.alert > .close,
|
|
4399
|
+
.alert > .alert-title > .close {
|
|
4400
|
+
opacity: 0.65;
|
|
4283
4401
|
}
|
|
4284
4402
|
|
|
4285
4403
|
@media (hover: hover) {
|
|
4286
|
-
.alert
|
|
4287
|
-
|
|
4404
|
+
.alert > .close:hover,
|
|
4405
|
+
.alert > .alert-title > .close:hover {
|
|
4288
4406
|
opacity: 1;
|
|
4289
4407
|
}
|
|
4290
4408
|
}
|
|
@@ -4293,7 +4411,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4293
4411
|
both. Icon and dismiss are pinned to the outer columns; anything else (the
|
|
4294
4412
|
message content) flows into the remaining text column. */
|
|
4295
4413
|
@supports selector(.alert:has(> *)) {
|
|
4296
|
-
.alert:has(> .alert-icon, > .
|
|
4414
|
+
.alert:has(> .alert-icon, > .close) {
|
|
4297
4415
|
display: grid;
|
|
4298
4416
|
grid-template-columns: minmax(0, 1fr);
|
|
4299
4417
|
align-items: var(--items-align, start);
|
|
@@ -4312,19 +4430,19 @@ a.badge[aria-disabled="true"] {
|
|
|
4312
4430
|
margin-block-start: 0.125em;
|
|
4313
4431
|
}
|
|
4314
4432
|
|
|
4315
|
-
.alert:has(> .alert-icon) > :not(.alert-icon, .
|
|
4433
|
+
.alert:has(> .alert-icon) > :not(.alert-icon, .close) {
|
|
4316
4434
|
grid-column: 2;
|
|
4317
4435
|
}
|
|
4318
4436
|
|
|
4319
|
-
.alert:has(> .
|
|
4437
|
+
.alert:has(> .close) {
|
|
4320
4438
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
4321
4439
|
}
|
|
4322
4440
|
|
|
4323
|
-
.alert:has(> .alert-icon):has(> .
|
|
4441
|
+
.alert:has(> .alert-icon):has(> .close) {
|
|
4324
4442
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
4325
4443
|
}
|
|
4326
4444
|
|
|
4327
|
-
.alert:has(> .
|
|
4445
|
+
.alert:has(> .close) > .close {
|
|
4328
4446
|
grid-column: -2 / -1;
|
|
4329
4447
|
grid-row: 1;
|
|
4330
4448
|
}
|
|
@@ -4372,8 +4490,8 @@ a.badge[aria-disabled="true"] {
|
|
|
4372
4490
|
border-start-end-radius: calc(var(--alert-radius, var(--radius-lg)) - var(--border-width));
|
|
4373
4491
|
}
|
|
4374
4492
|
|
|
4375
|
-
/*
|
|
4376
|
-
.alert.admonition > .alert-title > .
|
|
4493
|
+
/* Close inside an admonition title bar rides the inline end. */
|
|
4494
|
+
.alert.admonition > .alert-title > .close {
|
|
4377
4495
|
margin-inline-start: auto;
|
|
4378
4496
|
}
|
|
4379
4497
|
|
|
@@ -4466,7 +4584,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4466
4584
|
*
|
|
4467
4585
|
* Child contract:
|
|
4468
4586
|
* Direct .bleed children escape --card-pad (negative inset, wider media);
|
|
4469
|
-
* edge children inherit the card corners. .bleed must be a direct child
|
|
4587
|
+
* edge children inherit the card corners. .bleed must be a direct child, and
|
|
4588
|
+
* its rules live in components/bleed.css — the card relays --card-pad to the
|
|
4589
|
+
* shared --surface-pad that they read.
|
|
4470
4590
|
*
|
|
4471
4591
|
* Internal:
|
|
4472
4592
|
* --card-bg
|
|
@@ -4497,6 +4617,10 @@ a.badge[aria-disabled="true"] {
|
|
|
4497
4617
|
--card-bg: var(--ui-bg, var(--surface-raised));
|
|
4498
4618
|
--card-fg: var(--ui-fg, var(--text));
|
|
4499
4619
|
|
|
4620
|
+
/* The inset a direct .bleed child cancels (components/bleed.css). Relayed
|
|
4621
|
+
from --card-pad so .compact and an author override both reach it. */
|
|
4622
|
+
--surface-pad: var(--card-pad);
|
|
4623
|
+
|
|
4500
4624
|
/* Keep contextual descendants tied to the surface this card actually owns. */
|
|
4501
4625
|
--heading: var(--card-fg);
|
|
4502
4626
|
--busy-overlay-bg: var(--card-bg);
|
|
@@ -4562,45 +4686,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4562
4686
|
--card-pad: var(--space-20);
|
|
4563
4687
|
}
|
|
4564
4688
|
|
|
4565
|
-
/* Full-bleed children */
|
|
4566
|
-
.card > .bleed {
|
|
4567
|
-
margin-inline: calc(var(--card-pad) * -1);
|
|
4568
|
-
max-inline-size: none;
|
|
4569
|
-
}
|
|
4570
|
-
|
|
4571
|
-
.card > .bleed:first-child {
|
|
4572
|
-
margin-block-start: calc(var(--card-pad) * -1);
|
|
4573
|
-
border-start-start-radius: inherit;
|
|
4574
|
-
border-start-end-radius: inherit;
|
|
4575
|
-
}
|
|
4576
|
-
|
|
4577
|
-
.card > .bleed:last-child {
|
|
4578
|
-
margin-block-end: calc(var(--card-pad) * -1);
|
|
4579
|
-
border-end-start-radius: inherit;
|
|
4580
|
-
border-end-end-radius: inherit;
|
|
4581
|
-
}
|
|
4582
|
-
|
|
4583
|
-
/* Media bleed */
|
|
4584
|
-
.card > :where(img, picture, video, canvas, svg).bleed {
|
|
4585
|
-
display: block;
|
|
4586
|
-
inline-size: calc(100% + var(--card-pad) * 2);
|
|
4587
|
-
max-inline-size: none;
|
|
4588
|
-
}
|
|
4589
|
-
|
|
4590
|
-
.card > picture.bleed {
|
|
4591
|
-
overflow: hidden;
|
|
4592
|
-
}
|
|
4593
|
-
|
|
4594
|
-
.card > picture.bleed > img {
|
|
4595
|
-
display: block;
|
|
4596
|
-
inline-size: 100%;
|
|
4597
|
-
block-size: auto;
|
|
4598
|
-
}
|
|
4599
|
-
|
|
4600
|
-
/* Padded structural bleed */
|
|
4601
|
-
.card > :where(header, footer, figure).bleed {
|
|
4602
|
-
padding: var(--card-pad);
|
|
4603
|
-
}
|
|
4604
4689
|
@media print {
|
|
4605
4690
|
.card {
|
|
4606
4691
|
border: var(--border-width) solid CanvasText;
|
|
@@ -4622,8 +4707,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4622
4707
|
* --dialog-icon-size
|
|
4623
4708
|
* --dialog-icon-glyph-size
|
|
4624
4709
|
*
|
|
4710
|
+
* The corner close control is .close (components/close.css).
|
|
4711
|
+
*
|
|
4625
4712
|
* Internal:
|
|
4626
|
-
* --dialog-close-icon-size
|
|
4627
4713
|
* --dialog-focus-bleed
|
|
4628
4714
|
* --dialog-viewport-block
|
|
4629
4715
|
* --dialog-viewport-gap-default
|
|
@@ -4709,11 +4795,16 @@ a.badge[aria-disabled="true"] {
|
|
|
4709
4795
|
|
|
4710
4796
|
:where(dialog).modal.scrollable > form > :not(:is(header, footer)),
|
|
4711
4797
|
:where(dialog).modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
4712
|
-
--dialog-focus-bleed: calc(
|
|
4798
|
+
--dialog-focus-bleed: calc(
|
|
4799
|
+
var(--focus-outline-offset) +
|
|
4800
|
+
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
4801
|
+
);
|
|
4713
4802
|
|
|
4714
4803
|
min-block-size: 0;
|
|
4715
4804
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
4716
|
-
header and footer edges.
|
|
4805
|
+
header and footer edges. The reserve covers the thicker of the two
|
|
4806
|
+
outside indicators: the :focus baseline (2 × border) and the component
|
|
4807
|
+
line (--focus-ring-width, 3px in bootstrap-v6). */
|
|
4717
4808
|
margin-inline: calc(var(--dialog-focus-bleed) * -1);
|
|
4718
4809
|
padding-inline: var(--dialog-focus-bleed);
|
|
4719
4810
|
overflow: auto;
|
|
@@ -4736,7 +4827,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4736
4827
|
The form/.stack anatomy is structural, so it stays in :where() and never
|
|
4737
4828
|
lends .stack's specificity to a bare <form>. */
|
|
4738
4829
|
:where(dialog).modal > :where(form, .stack) > header {
|
|
4739
|
-
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
4830
|
+
padding-inline-end: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
4740
4831
|
}
|
|
4741
4832
|
|
|
4742
4833
|
:where(dialog).modal > :where(form, .stack) > footer {
|
|
@@ -4900,59 +4991,6 @@ html.has-modal-open.had-scrollbar {
|
|
|
4900
4991
|
}
|
|
4901
4992
|
}
|
|
4902
4993
|
|
|
4903
|
-
/* Close control. A close button is an action attached to a surface, so it
|
|
4904
|
-
never participates in the content flow — absolute at the panel's top
|
|
4905
|
-
inline-end, inside the surface. The glyph comes from --icon-close, masked to
|
|
4906
|
-
currentColor, so no icon font or text glyph is required in the markup. */
|
|
4907
|
-
.dialog-close {
|
|
4908
|
-
--dialog-close-icon-size: 1rem;
|
|
4909
|
-
|
|
4910
|
-
display: inline-grid;
|
|
4911
|
-
place-items: center;
|
|
4912
|
-
padding: 0;
|
|
4913
|
-
border: 0;
|
|
4914
|
-
border-radius: var(--radius);
|
|
4915
|
-
color: var(--text-muted);
|
|
4916
|
-
cursor: pointer;
|
|
4917
|
-
|
|
4918
|
-
/* Out of flow, anchored to the positioned dialog surface, not the header or
|
|
4919
|
-
any scroll container inside it. */
|
|
4920
|
-
position: absolute;
|
|
4921
|
-
z-index: 1;
|
|
4922
|
-
inset-block-start: var(--space-20);
|
|
4923
|
-
inset-inline-end: var(--space-20);
|
|
4924
|
-
|
|
4925
|
-
inline-size: var(--control-size);
|
|
4926
|
-
block-size: var(--control-size);
|
|
4927
|
-
|
|
4928
|
-
background: var(--surface-subtle);
|
|
4929
|
-
}
|
|
4930
|
-
|
|
4931
|
-
.dialog-close::before {
|
|
4932
|
-
content: "";
|
|
4933
|
-
inline-size: var(--dialog-close-icon-size);
|
|
4934
|
-
block-size: var(--dialog-close-icon-size);
|
|
4935
|
-
background: currentColor;
|
|
4936
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
4937
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
4938
|
-
}
|
|
4939
|
-
|
|
4940
|
-
@media (hover: hover) {
|
|
4941
|
-
.dialog-close:hover {
|
|
4942
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4943
|
-
color: var(--text);
|
|
4944
|
-
}
|
|
4945
|
-
}
|
|
4946
|
-
|
|
4947
|
-
.dialog-close:active {
|
|
4948
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4949
|
-
}
|
|
4950
|
-
|
|
4951
|
-
.dialog-close:focus-visible {
|
|
4952
|
-
outline: 2px solid transparent;
|
|
4953
|
-
box-shadow: var(--focus-ring-shadow);
|
|
4954
|
-
}
|
|
4955
|
-
|
|
4956
4994
|
/*
|
|
4957
4995
|
* Drawer — modal side-sheet built on the native dialog element.
|
|
4958
4996
|
*
|
|
@@ -4972,6 +5010,8 @@ html.has-modal-open.had-scrollbar {
|
|
|
4972
5010
|
* --drawer-size
|
|
4973
5011
|
* --drawer-pad
|
|
4974
5012
|
*
|
|
5013
|
+
* The corner close control is .close (components/close.css).
|
|
5014
|
+
*
|
|
4975
5015
|
* Internal:
|
|
4976
5016
|
* --drawer-closed-translate (side/RTL positioning state)
|
|
4977
5017
|
* --drawer-focus-bleed
|
|
@@ -4981,6 +5021,8 @@ html.has-modal-open.had-scrollbar {
|
|
|
4981
5021
|
:where(dialog).drawer {
|
|
4982
5022
|
--drawer-size: 20rem;
|
|
4983
5023
|
--drawer-pad: var(--space-40);
|
|
5024
|
+
/* The inset a direct .bleed child cancels (components/bleed.css). */
|
|
5025
|
+
--surface-pad: var(--drawer-pad);
|
|
4984
5026
|
--drawer-closed-translate: -100%;
|
|
4985
5027
|
/* Exit is shorter than entry so the panel clears quickly; the scrim reads
|
|
4986
5028
|
the same variable through inheritance and never lags the panel. */
|
|
@@ -5080,15 +5122,20 @@ html.has-modal-open.had-scrollbar {
|
|
|
5080
5122
|
/* The body region absorbs the panel's height and scrolls. A drawer is a
|
|
5081
5123
|
full-height shell by definition, so it always scrolls its body — unlike the
|
|
5082
5124
|
modal, which sizes to its content and opts in with .scrollable. Header and
|
|
5083
|
-
footer keep their own tracks; .
|
|
5125
|
+
footer keep their own tracks; .close is named because a generic
|
|
5084
5126
|
exclusion list is easier to trust than its `position: absolute`. */
|
|
5085
|
-
:where(dialog).drawer > :not(:is(header, footer, form, .
|
|
5086
|
-
:where(dialog).drawer > form > :not(:is(header, footer, .
|
|
5087
|
-
--drawer-focus-bleed: calc(
|
|
5127
|
+
:where(dialog).drawer > :not(:is(header, footer, form, .close)),
|
|
5128
|
+
:where(dialog).drawer > form > :not(:is(header, footer, .close)) {
|
|
5129
|
+
--drawer-focus-bleed: calc(
|
|
5130
|
+
var(--focus-outline-offset) +
|
|
5131
|
+
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
5132
|
+
);
|
|
5088
5133
|
|
|
5089
5134
|
min-block-size: 0;
|
|
5090
5135
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
5091
|
-
header and footer edges.
|
|
5136
|
+
header and footer edges. The reserve covers the thicker of the two
|
|
5137
|
+
outside indicators: the :focus baseline (2 × border) and the component
|
|
5138
|
+
line (--focus-ring-width, 3px in bootstrap-v6). */
|
|
5092
5139
|
margin-inline: calc(var(--drawer-focus-bleed) * -1);
|
|
5093
5140
|
padding-inline: var(--drawer-focus-bleed);
|
|
5094
5141
|
overflow: auto;
|
|
@@ -5160,57 +5207,87 @@ html.has-modal-open.had-scrollbar {
|
|
|
5160
5207
|
control. */
|
|
5161
5208
|
:where(dialog).drawer > header,
|
|
5162
5209
|
:where(dialog).drawer > form > header {
|
|
5163
|
-
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
5210
|
+
padding-inline-end: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
5164
5211
|
}
|
|
5165
5212
|
|
|
5166
|
-
/*
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
.
|
|
5170
|
-
|
|
5213
|
+
/*
|
|
5214
|
+
* Full-bleed children — the shared escape hatch for a padded surface.
|
|
5215
|
+
*
|
|
5216
|
+
* Docs: docs/pages/components/bleed.md
|
|
5217
|
+
*
|
|
5218
|
+
* A surface that insets its direct children relays that inset to them as
|
|
5219
|
+
* --surface-pad; a direct .bleed child cancels it and reaches the surface
|
|
5220
|
+
* edge. Card and drawer pad at the container level, so they own the token
|
|
5221
|
+
* there; the accordion pads its summary and its panel instead, so the panel
|
|
5222
|
+
* owns it and the item does not.
|
|
5223
|
+
*
|
|
5224
|
+
* Why the token is handed to the children rather than declared on the surface:
|
|
5225
|
+
* custom properties inherit, so a surface-level declaration would also reach a
|
|
5226
|
+
* grandchild, which sits at its wrapper's edge rather than the surface's and
|
|
5227
|
+
* would pull a negative margin out of the wrong box. Relaying it one level and
|
|
5228
|
+
* clearing it below keeps the child combinator's guarantee while leaving the
|
|
5229
|
+
* .bleed rules themselves free of the surface list.
|
|
5230
|
+
*
|
|
5231
|
+
* Why the clearing rule comes first: both rules are zero-specificity, so
|
|
5232
|
+
* source order decides where they overlap, and they overlap on exactly one
|
|
5233
|
+
* element — a direct child of a surface nested inside another surface. That
|
|
5234
|
+
* child must keep the inner surface's inset, so the relay has to win.
|
|
5235
|
+
*
|
|
5236
|
+
* Public hooks:
|
|
5237
|
+
* --bleed-pad (read-only: the inset relayed to a surface's direct children;
|
|
5238
|
+
* pad a band that is not a header/footer/figure with it)
|
|
5239
|
+
*
|
|
5240
|
+
* Framework plumbing:
|
|
5241
|
+
* --surface-pad (declared by the surface)
|
|
5242
|
+
*/
|
|
5243
|
+
:where(.card, .drawer, .accordion details > summary + *) > * * {
|
|
5244
|
+
--bleed-pad: initial;
|
|
5245
|
+
}
|
|
5171
5246
|
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
border: 0;
|
|
5176
|
-
border-radius: var(--radius);
|
|
5177
|
-
color: var(--text-muted);
|
|
5178
|
-
cursor: pointer;
|
|
5247
|
+
:where(.card, .drawer, .accordion details > summary + *) > * {
|
|
5248
|
+
--bleed-pad: var(--surface-pad);
|
|
5249
|
+
}
|
|
5179
5250
|
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5251
|
+
/* Inert without a surface: the fallback resolves the negative margin to zero
|
|
5252
|
+
rather than dropping the declaration and inheriting a stray inset. */
|
|
5253
|
+
.bleed {
|
|
5254
|
+
margin-inline: calc(var(--bleed-pad, 0px) * -1);
|
|
5255
|
+
max-inline-size: none;
|
|
5256
|
+
}
|
|
5184
5257
|
|
|
5185
|
-
|
|
5186
|
-
block-
|
|
5258
|
+
.bleed:first-child {
|
|
5259
|
+
margin-block-start: calc(var(--bleed-pad, 0px) * -1);
|
|
5260
|
+
border-start-start-radius: inherit;
|
|
5261
|
+
border-start-end-radius: inherit;
|
|
5262
|
+
}
|
|
5187
5263
|
|
|
5188
|
-
|
|
5264
|
+
.bleed:last-child {
|
|
5265
|
+
margin-block-end: calc(var(--bleed-pad, 0px) * -1);
|
|
5266
|
+
border-end-start-radius: inherit;
|
|
5267
|
+
border-end-end-radius: inherit;
|
|
5189
5268
|
}
|
|
5190
5269
|
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
5197
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
5270
|
+
/* Media bleed */
|
|
5271
|
+
:where(img, picture, video, canvas, svg).bleed {
|
|
5272
|
+
display: block;
|
|
5273
|
+
inline-size: calc(100% + var(--bleed-pad, 0px) * 2);
|
|
5274
|
+
max-inline-size: none;
|
|
5198
5275
|
}
|
|
5199
5276
|
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
5203
|
-
color: var(--text);
|
|
5204
|
-
}
|
|
5277
|
+
picture.bleed {
|
|
5278
|
+
overflow: hidden;
|
|
5205
5279
|
}
|
|
5206
5280
|
|
|
5207
|
-
.
|
|
5208
|
-
|
|
5281
|
+
picture.bleed > img {
|
|
5282
|
+
display: block;
|
|
5283
|
+
inline-size: 100%;
|
|
5284
|
+
block-size: auto;
|
|
5209
5285
|
}
|
|
5210
5286
|
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5287
|
+
/* Padded structural bleed — the band keeps the surface's own rhythm while its
|
|
5288
|
+
background reaches the edge. */
|
|
5289
|
+
:where(header, footer, figure).bleed {
|
|
5290
|
+
padding: var(--bleed-pad, 0px);
|
|
5214
5291
|
}
|
|
5215
5292
|
|
|
5216
5293
|
|
|
@@ -5231,6 +5308,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
5231
5308
|
* Public hooks:
|
|
5232
5309
|
* --flyout-inline-size
|
|
5233
5310
|
* --flyout-max-inline-size
|
|
5311
|
+
* --flyout-pad
|
|
5234
5312
|
*
|
|
5235
5313
|
* Internal:
|
|
5236
5314
|
* --menu-item-size (derived from --control-size for menu rows)
|
|
@@ -5283,10 +5361,13 @@ html.has-modal-open.had-scrollbar {
|
|
|
5283
5361
|
);
|
|
5284
5362
|
overflow: auto;
|
|
5285
5363
|
margin: 0;
|
|
5286
|
-
padding
|
|
5364
|
+
/* Tight by default for action menus, whose items carry their own padding.
|
|
5365
|
+
Rich panels (forms, pickers) opt into dialog air per instance. */
|
|
5366
|
+
padding: var(--flyout-pad, var(--space-10));
|
|
5287
5367
|
border: var(--border-width) solid var(--border);
|
|
5288
5368
|
border-radius: var(--radius);
|
|
5289
5369
|
background: var(--surface-raised);
|
|
5370
|
+
color: var(--text);
|
|
5290
5371
|
box-shadow: var(--shadow-popout);
|
|
5291
5372
|
list-style: none;
|
|
5292
5373
|
opacity: 1;
|
|
@@ -5519,6 +5600,18 @@ html.has-modal-open.had-scrollbar {
|
|
|
5519
5600
|
border-block-start: var(--border-width) solid var(--border);
|
|
5520
5601
|
}
|
|
5521
5602
|
|
|
5603
|
+
@media (forced-colors: active) {
|
|
5604
|
+
/* The check mark is a currentColor mask fill and the mixed/radio marks are
|
|
5605
|
+
currentColor gradients: without this the system remaps them to Canvas or
|
|
5606
|
+
drops the image and the state disappears. Kept on the pseudo so the host
|
|
5607
|
+
keeps its native color and focus adaptation. */
|
|
5608
|
+
.menu-item[role="menuitemcheckbox"][aria-checked="true"]::before,
|
|
5609
|
+
.menu-item[role="menuitemcheckbox"][aria-checked="mixed"]::before,
|
|
5610
|
+
.menu-item[role="menuitemradio"]::before {
|
|
5611
|
+
forced-color-adjust: none;
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
|
|
5522
5615
|
/*
|
|
5523
5616
|
* Tooltip — supplemental label overlaid near a trigger.
|
|
5524
5617
|
*
|
|
@@ -5543,7 +5636,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
5543
5636
|
*/
|
|
5544
5637
|
.tooltip {
|
|
5545
5638
|
--tooltip-bg: var(--surface-solid);
|
|
5546
|
-
--tooltip-fg: var(--surface);
|
|
5639
|
+
--tooltip-fg: var(--surface-solid-fg);
|
|
5547
5640
|
--tooltip-arrow-size: 5px;
|
|
5548
5641
|
|
|
5549
5642
|
position: fixed;
|
|
@@ -5680,11 +5773,15 @@ html.has-modal-open.had-scrollbar {
|
|
|
5680
5773
|
*
|
|
5681
5774
|
* Public hooks:
|
|
5682
5775
|
* --accordion-radius
|
|
5776
|
+
* --accordion-pad
|
|
5683
5777
|
* --accordion-marker-color
|
|
5778
|
+
* --accordion-marker-size
|
|
5684
5779
|
*/
|
|
5685
5780
|
.accordion {
|
|
5686
5781
|
--accordion-radius: var(--radius);
|
|
5782
|
+
--accordion-pad: var(--space-40);
|
|
5687
5783
|
--accordion-marker-color: var(--text-muted);
|
|
5784
|
+
--accordion-marker-size: 1rem;
|
|
5688
5785
|
|
|
5689
5786
|
border: var(--border-width) solid var(--border);
|
|
5690
5787
|
border-radius: var(--accordion-radius);
|
|
@@ -5701,7 +5798,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
5701
5798
|
gap: var(--space-30);
|
|
5702
5799
|
cursor: pointer;
|
|
5703
5800
|
list-style: none;
|
|
5704
|
-
padding: var(--
|
|
5801
|
+
padding: var(--accordion-pad);
|
|
5705
5802
|
color: var(--text);
|
|
5706
5803
|
font-weight: var(--font-weight-medium);
|
|
5707
5804
|
}
|
|
@@ -5717,12 +5814,17 @@ html.has-modal-open.had-scrollbar {
|
|
|
5717
5814
|
display: none;
|
|
5718
5815
|
}
|
|
5719
5816
|
|
|
5817
|
+
/* The marker is the chevron itself: the box is filled with
|
|
5818
|
+
--accordion-marker-color and then clipped to the glyph by the mask. Anything
|
|
5819
|
+
painted on this pseudo-element — a background, a ring, a shadow — is clipped
|
|
5820
|
+
with it, so a decorated marker (a chevron in a pill) needs a real element;
|
|
5821
|
+
see the accordion docs for the opt-out. */
|
|
5720
5822
|
.accordion summary::after {
|
|
5721
5823
|
content: "";
|
|
5722
5824
|
flex: 0 0 auto;
|
|
5723
5825
|
margin-inline-start: auto;
|
|
5724
|
-
inline-size:
|
|
5725
|
-
block-size:
|
|
5826
|
+
inline-size: var(--accordion-marker-size);
|
|
5827
|
+
block-size: var(--accordion-marker-size);
|
|
5726
5828
|
background-color: var(--accordion-marker-color);
|
|
5727
5829
|
-webkit-mask: var(--icon-chevron) center / contain no-repeat;
|
|
5728
5830
|
mask: var(--icon-chevron) center / contain no-repeat;
|
|
@@ -5733,9 +5835,14 @@ html.has-modal-open.had-scrollbar {
|
|
|
5733
5835
|
transform: rotate(180deg);
|
|
5734
5836
|
}
|
|
5735
5837
|
|
|
5838
|
+
/* The panel, not the item, owns the inset here — .separated paints a card on
|
|
5839
|
+
<details> but leaves it unpadded — so the panel is what a .bleed child
|
|
5840
|
+
escapes (components/bleed.css). */
|
|
5736
5841
|
.accordion details > summary + * {
|
|
5737
|
-
|
|
5738
|
-
|
|
5842
|
+
--surface-pad: var(--accordion-pad);
|
|
5843
|
+
|
|
5844
|
+
padding-block: 0 var(--accordion-pad);
|
|
5845
|
+
padding-inline: var(--accordion-pad);
|
|
5739
5846
|
color: var(--text-muted);
|
|
5740
5847
|
margin-block: 0;
|
|
5741
5848
|
}
|
|
@@ -5744,6 +5851,9 @@ html.has-modal-open.had-scrollbar {
|
|
|
5744
5851
|
padding-block-start: var(--space-10);
|
|
5745
5852
|
}
|
|
5746
5853
|
|
|
5854
|
+
/* Also cancels the lift of a first .bleed child (bleed.css, lower
|
|
5855
|
+
specificity): the panel's top edge is the summary, not the item edge, so a
|
|
5856
|
+
bleed reaches only the sides and the bottom. */
|
|
5747
5857
|
.accordion details > summary + * > :first-child {
|
|
5748
5858
|
margin-block-start: 0;
|
|
5749
5859
|
}
|
|
@@ -5768,6 +5878,12 @@ html.has-modal-open.had-scrollbar {
|
|
|
5768
5878
|
padding-inline: 0;
|
|
5769
5879
|
}
|
|
5770
5880
|
|
|
5881
|
+
/* Flush already sits at the embedding surface's edge, so there is no inline
|
|
5882
|
+
inset left for a .bleed child to cancel. */
|
|
5883
|
+
.accordion.flush details > summary + * {
|
|
5884
|
+
--surface-pad: 0px;
|
|
5885
|
+
}
|
|
5886
|
+
|
|
5771
5887
|
/* Separated — each item owns a full card. The item's own border beats the
|
|
5772
5888
|
shared details + details separator on specificity alone, so nothing needs
|
|
5773
5889
|
undoing for the second item onwards. */
|
|
@@ -5784,9 +5900,23 @@ html.has-modal-open.had-scrollbar {
|
|
|
5784
5900
|
background: var(--surface-raised);
|
|
5785
5901
|
}
|
|
5786
5902
|
|
|
5903
|
+
/* A last .bleed child inherits its bottom corners from the panel, but the
|
|
5904
|
+
rounded box is the item (.separated) or the group (last item) — relay the
|
|
5905
|
+
radius to the panel. Explicit rather than inherit: in browsers with
|
|
5906
|
+
::details-content the panel's flat-tree parent is that slot, not
|
|
5907
|
+
<details>. */
|
|
5908
|
+
.accordion.separated > details > summary + *,
|
|
5909
|
+
.accordion:not(.flush) > details:last-child > summary + * {
|
|
5910
|
+
border-end-start-radius: var(--accordion-radius);
|
|
5911
|
+
border-end-end-radius: var(--accordion-radius);
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5787
5914
|
@media (forced-colors: active) {
|
|
5788
|
-
|
|
5789
|
-
|
|
5915
|
+
/* Written on the property, not the hook: an application hook override at
|
|
5916
|
+
the same specificity loads after this file and would win the variable,
|
|
5917
|
+
leaving a Canvas-remapped fill. The property keeps CanvasText. */
|
|
5918
|
+
.accordion summary::after {
|
|
5919
|
+
background-color: CanvasText;
|
|
5790
5920
|
}
|
|
5791
5921
|
}
|
|
5792
5922
|
|
|
@@ -6152,8 +6282,8 @@ html.has-modal-open.had-scrollbar {
|
|
|
6152
6282
|
}
|
|
6153
6283
|
|
|
6154
6284
|
.status-bar {
|
|
6155
|
-
--status-bg: var(--intent, var(--
|
|
6156
|
-
--status-fg: var(--intent-fg, var(--surface));
|
|
6285
|
+
--status-bg: var(--intent, var(--surface-solid));
|
|
6286
|
+
--status-fg: var(--intent-fg, var(--surface-solid-fg));
|
|
6157
6287
|
|
|
6158
6288
|
position: fixed;
|
|
6159
6289
|
/* Fixed positioning tracks the layout viewport, which the mobile software
|
|
@@ -6423,7 +6553,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6423
6553
|
}
|
|
6424
6554
|
|
|
6425
6555
|
.avatar-stack > .avatar {
|
|
6426
|
-
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface);
|
|
6556
|
+
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface-opaque);
|
|
6427
6557
|
}
|
|
6428
6558
|
|
|
6429
6559
|
.avatar-stack > .avatar + .avatar {
|
|
@@ -6781,6 +6911,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
6781
6911
|
transform: none;
|
|
6782
6912
|
}
|
|
6783
6913
|
|
|
6914
|
+
@media (forced-colors: active) {
|
|
6915
|
+
/* Both toggle markers are currentColor mask fills: without this the system
|
|
6916
|
+
remaps the backgrounds to Canvas and the glyphs disappear. Kept on the
|
|
6917
|
+
pseudos so the host keeps its native color and focus adaptation. */
|
|
6918
|
+
.fab > summary::before,
|
|
6919
|
+
.fab > summary::after {
|
|
6920
|
+
forced-color-adjust: none;
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
|
|
6784
6924
|
/*
|
|
6785
6925
|
* Rating — integer star rating drawn on native radio inputs.
|
|
6786
6926
|
*
|
|
@@ -6839,7 +6979,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6839
6979
|
}
|
|
6840
6980
|
|
|
6841
6981
|
.rating > input[type="radio"]:focus-visible {
|
|
6842
|
-
outline: var(--focus-
|
|
6982
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
6843
6983
|
outline-offset: var(--focus-outline-offset);
|
|
6844
6984
|
background: var(--rating-color);
|
|
6845
6985
|
}
|
|
@@ -6945,7 +7085,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
6945
7085
|
--step-size: 1.75rem;
|
|
6946
7086
|
--step-inline-gap: var(--space-20);
|
|
6947
7087
|
--step-line-size: var(--border-width);
|
|
6948
|
-
--step-marker-bg: var(--surface);
|
|
7088
|
+
--step-marker-bg: var(--surface-opaque);
|
|
6949
7089
|
--step-marker-border: var(--border);
|
|
6950
7090
|
--step-marker-fg: var(--text-muted);
|
|
6951
7091
|
--step-marker-radius: var(--radius-full);
|
|
@@ -7025,7 +7165,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7025
7165
|
connector leaving current goes back to neutral. Declared after
|
|
7026
7166
|
.step-complete at equal specificity: current wins on every channel. */
|
|
7027
7167
|
.steps > [aria-current="step"] {
|
|
7028
|
-
--step-marker-bg: var(--surface);
|
|
7168
|
+
--step-marker-bg: var(--surface-opaque);
|
|
7029
7169
|
--step-marker-border: var(--state-selected);
|
|
7030
7170
|
--step-marker-fg: var(--state-selected);
|
|
7031
7171
|
--step-line: var(--border);
|
|
@@ -7064,7 +7204,7 @@ a.avatar[aria-disabled="true"] {
|
|
|
7064
7204
|
focus ring's own width and offset. Only this composition scrolls, and only a
|
|
7065
7205
|
link has a ring to protect. */
|
|
7066
7206
|
.steps-horizontal:has(a[href]) {
|
|
7067
|
-
padding-block: calc(var(--focus-outline-offset) + var(--
|
|
7207
|
+
padding-block: calc(var(--focus-outline-offset) + var(--focus-ring-width));
|
|
7068
7208
|
}
|
|
7069
7209
|
|
|
7070
7210
|
/* One explicit row, the marker's: a `.step-label` is auto-placed into an
|
|
@@ -7305,6 +7445,15 @@ a.avatar[aria-disabled="true"] {
|
|
|
7305
7445
|
color: var(--text);
|
|
7306
7446
|
}
|
|
7307
7447
|
|
|
7448
|
+
/* Same contextual hover as .nav-link (navbar.css): a tint of the tile's ink
|
|
7449
|
+
reads on any --ui-* bar; the baseline keeps a legible light chip. */
|
|
7450
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
7451
|
+
.app-nav > a:hover {
|
|
7452
|
+
background: color-mix(in oklch, currentColor 10%, transparent);
|
|
7453
|
+
color: var(--ui-fg, var(--text));
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7456
|
+
|
|
7308
7457
|
/* Current tile is already a full selected surface. Selection never changes
|
|
7309
7458
|
text metrics: the shared medium weight is kept, no permanent indicator is
|
|
7310
7459
|
added here. */
|
|
@@ -7392,11 +7541,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
7392
7541
|
display: inline-flex;
|
|
7393
7542
|
align-items: center;
|
|
7394
7543
|
min-block-size: var(--control-size-sm);
|
|
7544
|
+
padding-block: 0;
|
|
7395
7545
|
padding-inline: var(--space-20);
|
|
7546
|
+
border: 0;
|
|
7396
7547
|
border-radius: var(--radius);
|
|
7548
|
+
background: transparent;
|
|
7397
7549
|
color: var(--ui-fg, var(--text-muted));
|
|
7550
|
+
font: inherit;
|
|
7398
7551
|
font-weight: var(--font-weight-medium);
|
|
7399
7552
|
text-decoration: none;
|
|
7553
|
+
cursor: pointer;
|
|
7400
7554
|
}
|
|
7401
7555
|
|
|
7402
7556
|
/* nav-link serves two contracts: plain page navigation (aria-current="page")
|
|
@@ -7421,6 +7575,28 @@ a.avatar[aria-disabled="true"] {
|
|
|
7421
7575
|
color: var(--state-selected);
|
|
7422
7576
|
}
|
|
7423
7577
|
|
|
7578
|
+
/* On a bar with its own --ui-bg/--ui-fg the fill is a tint of the link's own
|
|
7579
|
+
ink, the .btn.ghost hover recipe, so it reads on light and dark bars alike
|
|
7580
|
+
instead of snapping back to --surface-subtle. Not --ui-hover-bg: that is the
|
|
7581
|
+
bar's own hover and can equal its fill. The ink follows --ui-fg too — the
|
|
7582
|
+
selected accent has no contrast pair with an arbitrary bar. The baseline
|
|
7583
|
+
above keeps the light chip where color-mix is missing, with ink that is
|
|
7584
|
+
legible on it. */
|
|
7585
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
7586
|
+
.nav-link:is(:hover, [aria-current]) {
|
|
7587
|
+
background: color-mix(in oklch, currentColor 10%, transparent);
|
|
7588
|
+
}
|
|
7589
|
+
|
|
7590
|
+
.nav-link:hover {
|
|
7591
|
+
color: var(--ui-fg, var(--text));
|
|
7592
|
+
}
|
|
7593
|
+
|
|
7594
|
+
.nav-link[aria-current],
|
|
7595
|
+
.nav-link[aria-current]:hover {
|
|
7596
|
+
color: var(--ui-fg, var(--state-selected));
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
|
|
7424
7600
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
7425
7601
|
Links fill each row so the whole line is an interactive target; the
|
|
7426
7602
|
horizontal .navbar-nav links keep their intrinsic size. */
|
|
@@ -7512,8 +7688,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
7512
7688
|
*
|
|
7513
7689
|
* Docs: docs/pages/components/pagination.md
|
|
7514
7690
|
*
|
|
7515
|
-
* Render .btn elements inside a .pagination list. The active page is
|
|
7516
|
-
* with aria-current="page"; .btn owns the active state styling
|
|
7691
|
+
* Render .btn.ghost elements inside a .pagination list. The active page is
|
|
7692
|
+
* marked with aria-current="page"; .btn owns the active state styling, so the
|
|
7693
|
+
* current page is the only filled item.
|
|
7517
7694
|
*
|
|
7518
7695
|
* Truncated ranges are plain text (`.muted`, `aria-hidden="true"`), not .btn
|
|
7519
7696
|
* — they are decorative, not actionable. They still get the same box sizing
|
|
@@ -7631,10 +7808,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
7631
7808
|
}
|
|
7632
7809
|
|
|
7633
7810
|
/* The ring is square on the edge the tab is attached to, so it reads as part
|
|
7634
|
-
of the strip rather than a floating chip.
|
|
7811
|
+
of the strip rather than a floating chip. Color and width follow the
|
|
7812
|
+
component ring contract (focus.css); only the geometry is tab-specific. */
|
|
7635
7813
|
.tab:focus-visible {
|
|
7636
|
-
outline: var(--focus-
|
|
7637
|
-
outline-color: var(--focus);
|
|
7814
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
7638
7815
|
outline-offset: -2px;
|
|
7639
7816
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
7640
7817
|
}
|
|
@@ -7716,6 +7893,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
7716
7893
|
* A plain <dl> handles richer relationships (several terms or descriptions)
|
|
7717
7894
|
* and stays untouched by this component.
|
|
7718
7895
|
*
|
|
7896
|
+
* Variants:
|
|
7897
|
+
* .stacked — value under its term rather than beside it.
|
|
7898
|
+
*
|
|
7719
7899
|
* Why margin resets: neutralizes .prose list margins if .data-list appears
|
|
7720
7900
|
* inside an authored rich-text scope; the grid owns row/column rhythm.
|
|
7721
7901
|
*/
|
|
@@ -7737,6 +7917,21 @@ a.avatar[aria-disabled="true"] {
|
|
|
7737
7917
|
margin: 0;
|
|
7738
7918
|
}
|
|
7739
7919
|
|
|
7920
|
+
/* Stacked — the value sits under its term instead of beside it, for narrow
|
|
7921
|
+
columns and for values too long to sit in a second column.
|
|
7922
|
+
Why the pair separation rides on the term's margin: the children alternate
|
|
7923
|
+
dt/dd with no element around a pair, so the grid has no row boundary to give
|
|
7924
|
+
a larger gap to. The tight gap is the row gap; the term reopens the space. */
|
|
7925
|
+
.data-list.stacked {
|
|
7926
|
+
grid-template-columns: minmax(0, 1fr);
|
|
7927
|
+
gap: var(--space-10);
|
|
7928
|
+
}
|
|
7929
|
+
|
|
7930
|
+
.data-list.stacked > dt + dt,
|
|
7931
|
+
.data-list.stacked > dd + dt {
|
|
7932
|
+
margin-block-start: var(--space-30);
|
|
7933
|
+
}
|
|
7934
|
+
|
|
7740
7935
|
/*
|
|
7741
7936
|
* Application list — repeated rows with optional leading and trailing regions.
|
|
7742
7937
|
* Content determines the number of lines; the component only owns the row
|
|
@@ -7869,6 +8064,12 @@ a.list-item[aria-current]::before {
|
|
|
7869
8064
|
* .table-wrap provides the scroll container and outer chrome; .table owns row
|
|
7870
8065
|
* and cell rules. Tokens are sourced from the shared scale.
|
|
7871
8066
|
*
|
|
8067
|
+
* Responsive strategy is horizontal overflow: the wrap scrolls, the table
|
|
8068
|
+
* keeps its columns. Restructuring rows into cards would destroy the
|
|
8069
|
+
* column scanability tables exist for, and needs data-label duplication
|
|
8070
|
+
* because CSS cannot join a cell to its header — that representation choice
|
|
8071
|
+
* belongs to the application, not the framework.
|
|
8072
|
+
*
|
|
7872
8073
|
* Public hooks:
|
|
7873
8074
|
* --table-min
|
|
7874
8075
|
* --table-cell-pad
|
|
@@ -7989,7 +8190,7 @@ a.list-item[aria-current]::before {
|
|
|
7989
8190
|
}
|
|
7990
8191
|
|
|
7991
8192
|
.meter:not(meter) > * + * {
|
|
7992
|
-
border-inline-start: var(--border-width) solid var(--surface);
|
|
8193
|
+
border-inline-start: var(--border-width) solid var(--surface-opaque);
|
|
7993
8194
|
}
|
|
7994
8195
|
|
|
7995
8196
|
.meter::-webkit-meter-bar {
|
|
@@ -8090,6 +8291,11 @@ a.list-item[aria-current]::before {
|
|
|
8090
8291
|
min-block-size: var(--control-size);
|
|
8091
8292
|
padding-inline: var(--space-30);
|
|
8092
8293
|
border: var(--border-width) solid var(--control-border);
|
|
8294
|
+
/* An addon is a field segment: it honours the same lower-edge extension
|
|
8295
|
+
points as text controls (control.css), so a theme's underline or accent
|
|
8296
|
+
edge reaches it instead of stopping at the field. */
|
|
8297
|
+
border-block-end-color: var(--control-border-block-end-color, var(--control-border));
|
|
8298
|
+
border-block-end-width: var(--control-border-block-end-width, var(--border-width));
|
|
8093
8299
|
border-radius: var(--radius);
|
|
8094
8300
|
background: var(--control-bg);
|
|
8095
8301
|
color: var(--control-fg);
|
|
@@ -8137,38 +8343,49 @@ a.list-item[aria-current]::before {
|
|
|
8137
8343
|
z-index: 1;
|
|
8138
8344
|
}
|
|
8139
8345
|
|
|
8140
|
-
/* Keep an active focus ring above an adjacent hovered control. This matters
|
|
8141
|
-
on platforms where clicking a button does not move focus from the input. */
|
|
8142
|
-
.join > :is(:focus, :focus-within) {
|
|
8143
|
-
z-index: 4;
|
|
8144
|
-
}
|
|
8145
|
-
|
|
8146
8346
|
/* An input's border must read as the "primary" edge against an attached
|
|
8147
8347
|
action (e.g. a password-reveal button) at all times, not only while
|
|
8148
8348
|
hovered or focused — otherwise the shared edge flips ownership as the
|
|
8149
|
-
user's pointer moves, which reads as a rendering glitch.
|
|
8150
|
-
|
|
8349
|
+
user's pointer moves, which reads as a rendering glitch. The :has()
|
|
8350
|
+
variant covers a control nested one wrapper level deep (e.g. a combobox
|
|
8351
|
+
element): the wrapper is the flex item, so it carries the z-index — a
|
|
8352
|
+
z-index on the inner control would be a no-op on a static grandchild. */
|
|
8353
|
+
.join > :is(.input, .textarea, .select),
|
|
8354
|
+
.join > :has(> :is(.input, .textarea, .select)) {
|
|
8151
8355
|
z-index: 3;
|
|
8152
8356
|
}
|
|
8153
8357
|
|
|
8154
|
-
/*
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8358
|
+
/* Keep an active focus line above every neighbour, including an adjacent
|
|
8359
|
+
field and a hovered control (some platforms keep focus on the input while
|
|
8360
|
+
a button is clicked). It must follow the primary-edge rule above: both are
|
|
8361
|
+
(0,2,0), and when it came first a focused field stayed at 3 and the next
|
|
8362
|
+
field, painted later, covered the 1px shared edge of its line. */
|
|
8363
|
+
.join > :is(:focus, :focus-within) {
|
|
8364
|
+
z-index: 4;
|
|
8161
8365
|
}
|
|
8162
8366
|
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8367
|
+
/* One wrapper level (e.g. a combobox element around the native control) is
|
|
8368
|
+
supported generically — no widget is named here. The wrapper is the flex
|
|
8369
|
+
item that receives the corner longhands above; the inner control inherits
|
|
8370
|
+
them. The wrapper is also what align-items stretches: block-size: 100%
|
|
8371
|
+
resolves against that stretched size, so the field fills it. :hover and
|
|
8372
|
+
:focus-within already propagate to the wrapper, so the z-index rules above
|
|
8373
|
+
need no duplication. */
|
|
8374
|
+
.join > * > :is(.input, .textarea, .select) {
|
|
8375
|
+
block-size: 100%;
|
|
8376
|
+
border-radius: inherit;
|
|
8166
8377
|
}
|
|
8167
8378
|
|
|
8379
|
+
/* Text controls paint their own inset focus outline (control.css), so a
|
|
8380
|
+
.join never moves the ring to the group: button-only groups and wrapped
|
|
8381
|
+
inputs alike keep per-control focus, with no :has() group treatment. */
|
|
8382
|
+
|
|
8168
8383
|
/* A default outline action belongs to the composite field border. An explicit
|
|
8169
8384
|
intent still provides its own outline color — --intent is guaranteed-invalid
|
|
8170
|
-
for a neutral button (the :where(.btn) boundary), so the fallback applies.
|
|
8171
|
-
|
|
8385
|
+
for a neutral button (the :where(.btn) boundary), so the fallback applies.
|
|
8386
|
+
The second :has() covers a control nested one wrapper level deep. */
|
|
8387
|
+
.join:has(> :is(.input, .textarea, .select)) > .btn.outline,
|
|
8388
|
+
.join:has(> * > :is(.input, .textarea, .select)) > .btn.outline {
|
|
8172
8389
|
--btn-border-color: var(--intent, var(--border));
|
|
8173
8390
|
}
|
|
8174
8391
|
|
|
@@ -8210,36 +8427,55 @@ a.list-item[aria-current]::before {
|
|
|
8210
8427
|
|
|
8211
8428
|
.aura {
|
|
8212
8429
|
--aura-color: var(--intent, var(--primary));
|
|
8213
|
-
--aura-width:
|
|
8430
|
+
--aura-width: 1px;
|
|
8214
8431
|
--aura-blur: 0.25rem;
|
|
8215
|
-
--aura-intensity:
|
|
8432
|
+
--aura-intensity: 0.4;
|
|
8216
8433
|
--aura-radius: var(--radius-lg);
|
|
8217
8434
|
--aura-duration: 6s;
|
|
8218
8435
|
|
|
8219
8436
|
position: relative;
|
|
8220
8437
|
isolation: isolate;
|
|
8221
8438
|
display: inline-grid;
|
|
8222
|
-
padding: var(--aura-width);
|
|
8223
8439
|
border-radius: calc(var(--aura-radius) + var(--aura-width));
|
|
8224
|
-
|
|
8440
|
+
|
|
8441
|
+
/* The frame is the border itself, so --aura-width is the painted thickness.
|
|
8442
|
+
A transparent border stays for forced colors, where it becomes the visible
|
|
8443
|
+
boundary; a padding frame inside it would paint both, doubling the line. */
|
|
8444
|
+
border: var(--aura-width) solid transparent;
|
|
8225
8445
|
background: var(--aura-color);
|
|
8226
8446
|
|
|
8227
8447
|
/* A short feathered accent rather than a long wedge with a hard edge, which
|
|
8228
8448
|
reads as one lit side. Nothing tints the frame outside it: a constant tint
|
|
8229
|
-
across
|
|
8449
|
+
across the frame draws an outline, the opposite of the effect. `border-box`
|
|
8450
|
+
sizes the gradient to the border box: the default padding-box origin would
|
|
8451
|
+
tile a neighbouring copy of the gradient into the border, which is now the
|
|
8452
|
+
whole frame. */
|
|
8230
8453
|
background: conic-gradient(
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
/*
|
|
8454
|
+
from var(--aura-angle),
|
|
8455
|
+
transparent 0 78%,
|
|
8456
|
+
var(--aura-color) 93%,
|
|
8457
|
+
transparent
|
|
8458
|
+
)
|
|
8459
|
+
border-box;
|
|
8460
|
+
|
|
8461
|
+
/* Far halo — flat and static. At this blur the rotating detail is no longer
|
|
8462
|
+
legible, so there is nothing to gain from animating a second wide blurred
|
|
8463
|
+
layer. This is the layer that stays lit at all times, so it — not the
|
|
8464
|
+
frame — is what keeps the object present between two passes of the
|
|
8465
|
+
accent. An outer shadow never paints under the box, so it stays out of a
|
|
8466
|
+
translucent child. min() because a color-mix() share above 100% is
|
|
8467
|
+
invalid, where the opacity it replaces clamped. */
|
|
8468
|
+
box-shadow: 0 0 calc(var(--aura-blur) * 8) var(--aura-blur)
|
|
8469
|
+
color-mix(in oklab, var(--aura-color) min(100%, calc(15% * var(--aura-intensity))), transparent);
|
|
8470
|
+
}
|
|
8471
|
+
|
|
8472
|
+
/* Near halo — inherits the frame gradient, so it tracks the lit accent. An
|
|
8473
|
+
absolute inset starts at the padding edge, inside the border that is the
|
|
8474
|
+
frame, so the negative inset puts the halo back on the frame. */
|
|
8239
8475
|
.aura::before {
|
|
8240
8476
|
position: absolute;
|
|
8241
8477
|
z-index: -1;
|
|
8242
|
-
inset:
|
|
8478
|
+
inset: calc(-1 * var(--aura-width));
|
|
8243
8479
|
border-radius: inherit;
|
|
8244
8480
|
background: inherit;
|
|
8245
8481
|
filter: blur(var(--aura-blur));
|
|
@@ -8248,29 +8484,22 @@ a.list-item[aria-current]::before {
|
|
|
8248
8484
|
content: "";
|
|
8249
8485
|
}
|
|
8250
8486
|
|
|
8251
|
-
/*
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
passes of the accent. */
|
|
8487
|
+
/* Occlusion — the frame and the near halo paint across the whole box, and only
|
|
8488
|
+
their edge is meant to show. This mask sits above both, under the child, so
|
|
8489
|
+
a transparent or translucent child (an .outline alert, a glass surface) is
|
|
8490
|
+
framed rather than filled with the frame color. It follows the child's
|
|
8491
|
+
shape through --aura-radius. */
|
|
8257
8492
|
.aura::after {
|
|
8258
8493
|
position: absolute;
|
|
8259
8494
|
z-index: -1;
|
|
8260
|
-
inset:
|
|
8261
|
-
border-radius:
|
|
8262
|
-
background: var(--
|
|
8263
|
-
filter: blur(calc(var(--aura-blur) * 4));
|
|
8264
|
-
opacity: calc(0.15 * var(--aura-intensity));
|
|
8495
|
+
inset: 0;
|
|
8496
|
+
border-radius: var(--aura-radius);
|
|
8497
|
+
background: var(--surface-opaque);
|
|
8265
8498
|
pointer-events: none;
|
|
8266
8499
|
content: "";
|
|
8267
8500
|
}
|
|
8268
8501
|
|
|
8269
|
-
/* Lifts the child above
|
|
8270
|
-
across the whole box and only its edge is meant to show, so a transparent
|
|
8271
|
-
child (an .outline alert, a .ghost button) is filled with the frame color
|
|
8272
|
-
rather than framed by it. Documented as a constraint, not worked around —
|
|
8273
|
-
masking a ring would cost more than the case is worth. */
|
|
8502
|
+
/* Lifts the child above the halo and the mask. */
|
|
8274
8503
|
.aura > * {
|
|
8275
8504
|
position: relative;
|
|
8276
8505
|
}
|
|
@@ -8293,7 +8522,7 @@ a.list-item[aria-current]::before {
|
|
|
8293
8522
|
animation: aura-breathe-near var(--aura-duration) ease-in-out infinite;
|
|
8294
8523
|
}
|
|
8295
8524
|
|
|
8296
|
-
.aura-glow
|
|
8525
|
+
.aura-glow {
|
|
8297
8526
|
animation: aura-breathe-far var(--aura-duration) ease-in-out infinite;
|
|
8298
8527
|
}
|
|
8299
8528
|
}
|
|
@@ -8305,8 +8534,8 @@ a.list-item[aria-current]::before {
|
|
|
8305
8534
|
@media (prefers-reduced-motion: reduce) {
|
|
8306
8535
|
.aura:not(.aura-glow) {
|
|
8307
8536
|
background:
|
|
8308
|
-
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%),
|
|
8309
|
-
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%);
|
|
8537
|
+
radial-gradient(4rem 4rem at 0% 0%, var(--aura-color), transparent 70%) border-box,
|
|
8538
|
+
radial-gradient(4rem 4rem at 100% 100%, var(--aura-color), transparent 70%) border-box;
|
|
8310
8539
|
}
|
|
8311
8540
|
}
|
|
8312
8541
|
|
|
@@ -8327,10 +8556,16 @@ a.list-item[aria-current]::before {
|
|
|
8327
8556
|
}
|
|
8328
8557
|
}
|
|
8329
8558
|
|
|
8559
|
+
/* The far halo is the element's own shadow: opening its spread and share
|
|
8560
|
+
breathes it without scaling the child. */
|
|
8330
8561
|
@keyframes aura-breathe-far {
|
|
8331
8562
|
50% {
|
|
8332
|
-
|
|
8333
|
-
|
|
8563
|
+
box-shadow: 0 0 calc(var(--aura-blur) * 10) calc(var(--aura-blur) * 2)
|
|
8564
|
+
color-mix(
|
|
8565
|
+
in oklab,
|
|
8566
|
+
var(--aura-color) min(100%, calc(28% * var(--aura-intensity))),
|
|
8567
|
+
transparent
|
|
8568
|
+
);
|
|
8334
8569
|
}
|
|
8335
8570
|
}
|
|
8336
8571
|
|
|
@@ -8350,6 +8585,64 @@ a.list-item[aria-current]::before {
|
|
|
8350
8585
|
--aura-radius: var(--radius-full);
|
|
8351
8586
|
}
|
|
8352
8587
|
|
|
8588
|
+
/*
|
|
8589
|
+
* Highlight sweeping across a text label — an activity in progress.
|
|
8590
|
+
*
|
|
8591
|
+
* Docs: docs/pages/components/shimmer.md
|
|
8592
|
+
*
|
|
8593
|
+
* Presentation only: it carries no state and selects no ARIA attribute, so the
|
|
8594
|
+
* label stays whatever the author makes it (a role="status" message, a list
|
|
8595
|
+
* item, a heading).
|
|
8596
|
+
*
|
|
8597
|
+
* Public hooks:
|
|
8598
|
+
* --shimmer-color
|
|
8599
|
+
* --shimmer-highlight
|
|
8600
|
+
*/
|
|
8601
|
+
.shimmer {
|
|
8602
|
+
--shimmer-color: var(--text-muted);
|
|
8603
|
+
--shimmer-highlight: var(--text);
|
|
8604
|
+
|
|
8605
|
+
color: var(--shimmer-color);
|
|
8606
|
+
}
|
|
8607
|
+
|
|
8608
|
+
/* The sweep paints the glyphs through a clipped background, which needs the
|
|
8609
|
+
glyph fill transparent. Without background-clip: text that would erase the
|
|
8610
|
+
label, so the transparency lives under the same query as the clip. It is
|
|
8611
|
+
the fill, not `color`: a label class that sets its own color (.muted, a
|
|
8612
|
+
caption style) would otherwise win the cascade and paint opaque glyphs over
|
|
8613
|
+
the sweep. Reduced motion keeps the plain label — a frozen band would read
|
|
8614
|
+
as a random tint. */
|
|
8615
|
+
@supports (background-clip: text) {
|
|
8616
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
8617
|
+
.shimmer {
|
|
8618
|
+
-webkit-text-fill-color: transparent;
|
|
8619
|
+
background:
|
|
8620
|
+
linear-gradient(
|
|
8621
|
+
90deg,
|
|
8622
|
+
var(--shimmer-color) 40%,
|
|
8623
|
+
var(--shimmer-highlight) 50%,
|
|
8624
|
+
var(--shimmer-color) 60%
|
|
8625
|
+
)
|
|
8626
|
+
0 0 / 300% 100%;
|
|
8627
|
+
background-clip: text;
|
|
8628
|
+
animation: actual-shimmer var(--duration-shimmer) linear infinite;
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8633
|
+
/* The band occupies the middle third of a background three times the label's
|
|
8634
|
+
width, so each pass leaves the end of the label before the next one enters
|
|
8635
|
+
at the start. */
|
|
8636
|
+
@keyframes actual-shimmer {
|
|
8637
|
+
from {
|
|
8638
|
+
background-position: 100% 0;
|
|
8639
|
+
}
|
|
8640
|
+
|
|
8641
|
+
to {
|
|
8642
|
+
background-position: 0% 0;
|
|
8643
|
+
}
|
|
8644
|
+
}
|
|
8645
|
+
|
|
8353
8646
|
/* Utilities — small, single-purpose escape hatches. */
|
|
8354
8647
|
/*
|
|
8355
8648
|
* Small, single-purpose utilities for common escape hatches.
|
|
@@ -8537,9 +8830,9 @@ a.list-item[aria-current]::before {
|
|
|
8537
8830
|
}
|
|
8538
8831
|
|
|
8539
8832
|
/*
|
|
8540
|
-
*
|
|
8541
|
-
*
|
|
8542
|
-
*
|
|
8833
|
+
* Extra utility helpers — explicit property/value names beside the curated
|
|
8834
|
+
* shortcuts of base.css. Both are imported by utilities/index.css and are
|
|
8835
|
+
* equally public (docs/design-notes/naming.md § Utilities).
|
|
8543
8836
|
* They are escape hatches for layout nudges, not a full atomic scale.
|
|
8544
8837
|
*
|
|
8545
8838
|
* Core principle:
|
|
@@ -8715,8 +9008,9 @@ a.list-item[aria-current]::before {
|
|
|
8715
9008
|
overflow-y: auto;
|
|
8716
9009
|
}
|
|
8717
9010
|
|
|
8718
|
-
/* Text
|
|
8719
|
-
.
|
|
9011
|
+
/* Text. .text-wrap is the counterpart of base's .text-nowrap: it restores
|
|
9012
|
+
wrapping inside a component that sets nowrap (.badge). */
|
|
9013
|
+
.text-wrap {
|
|
8720
9014
|
white-space: normal;
|
|
8721
9015
|
}
|
|
8722
9016
|
|
|
@@ -8789,7 +9083,7 @@ a.list-item[aria-current]::before {
|
|
|
8789
9083
|
/* Surface. These are semantic surfaces, not arbitrary paint: each owns its
|
|
8790
9084
|
matching ink and rebinds --heading to that ink, so it stays readable when it
|
|
8791
9085
|
recreates a light surface inside a context that installed a dark one
|
|
8792
|
-
(.
|
|
9086
|
+
(e.g. a --surface-solid band). Intent ink is not inherited through a surface utility; use
|
|
8793
9087
|
.intent-color for that. */
|
|
8794
9088
|
.background-surface {
|
|
8795
9089
|
--heading: currentColor;
|