actual-css 0.9.1 → 0.10.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 +116 -0
- package/README.md +67 -45
- package/components.json +33 -13
- package/dist/actual.full.css +607 -502
- 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/forced-colors.md +12 -1
- package/docs/design-notes/naming.md +1 -1
- package/docs/design-notes/surface-reparenting.md +8 -9
- package/docs/navigation.json +2 -0
- package/docs/pages/components/accordion.md +55 -1
- package/docs/pages/components/alert.md +9 -15
- package/docs/pages/components/badge.md +6 -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/close.md +48 -0
- package/docs/pages/components/data-list.md +26 -1
- package/docs/pages/components/dialog.md +16 -16
- package/docs/pages/components/drawer.md +9 -9
- package/docs/pages/components/navbar.md +5 -5
- package/docs/pages/components/spinner.md +3 -3
- package/docs/pages/components/table.md +3 -2
- package/docs/pages/enhancements/overview.md +5 -0
- package/docs/pages/enhancements/tooltip.md +3 -1
- package/docs/pages/examples/overview.md +3 -1
- package/docs/pages/forms/choice-cards.md +39 -0
- package/docs/pages/forms/combobox.md +23 -11
- package/docs/pages/forms/input-policy.md +59 -0
- package/docs/pages/forms/joined-controls.md +2 -5
- package/docs/pages/forms/overview.md +6 -0
- package/docs/pages/forms/validation.md +37 -1
- package/docs/pages/foundations/theming.md +64 -18
- package/docs/pages/foundations/tokens.md +50 -13
- package/docs/pages/foundations/typography.md +10 -0
- package/docs/pages/guides/introduction.md +17 -24
- package/docs/pages/guides/modular-import.md +53 -6
- package/docs/pages/guides/progressive-enhancement.md +9 -5
- package/docs/pages/guides/tailwind.md +17 -5
- 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 +24 -4
- package/package.json +7 -6
- package/reserved-classes.json +5 -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/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 +1 -0
- 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/modal.css +10 -57
- package/src/css/components/navbar.css +27 -0
- package/src/css/core/base.css +2 -0
- package/src/css/core/focus.css +9 -3
- package/src/css/core/theme.css +25 -43
- package/src/css/core/tokens.css +3 -1
- package/src/css/core/variants.css +15 -36
- package/src/css/forms/choice-card.css +74 -4
- package/src/css/forms/choice.css +2 -3
- package/src/css/forms/control.css +15 -8
- package/src/css/forms/custom-select.css +4 -4
- package/src/css/forms/form.css +6 -0
- package/src/css/forms/native.css +4 -4
- package/src/css/forms/range.css +23 -8
- 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/dialog.js +95 -24
- package/src/js/enhance.js +7 -3
- 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/keys.js +14 -0
- package/src/js/menu.js +2 -1
- package/src/js/status.js +19 -0
- package/src/js/surface.js +12 -8
- package/src/js/tab.js +2 -1
- package/src/js/tooltip.js +18 -1
- package/src/js/validation.js +36 -6
- package/dist/actual.css +0 -863
- package/dist/actual.js +0 -1
- package/dist/actual.min.css +0 -1
package/dist/actual.full.css
CHANGED
|
@@ -71,6 +71,8 @@ select {
|
|
|
71
71
|
/* Typography */
|
|
72
72
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
73
73
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
74
|
+
--font-width: 100%;
|
|
75
|
+
--font-width-dense: 95%;
|
|
74
76
|
--line-height: 1.5;
|
|
75
77
|
--line-height-tight: 1.25;
|
|
76
78
|
--line-height-relaxed: 1.75;
|
|
@@ -132,7 +134,7 @@ select {
|
|
|
132
134
|
--focus-outline-color: currentColor;
|
|
133
135
|
--focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color);
|
|
134
136
|
--focus-outline-offset: 2px;
|
|
135
|
-
--focus-ring-width:
|
|
137
|
+
--focus-ring-width: 2px;
|
|
136
138
|
|
|
137
139
|
/* Status dot positioning — used by .avatar > .badge:empty. The offset is
|
|
138
140
|
the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
|
|
@@ -354,10 +356,10 @@ select {
|
|
|
354
356
|
|
|
355
357
|
/* Lines and focus */
|
|
356
358
|
--border: hsl(260 15% 88%);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
--focus
|
|
359
|
+
/* A primary-hued mid-tone on purpose, one value for both schemes: focus
|
|
360
|
+
lines must hold 3:1 against --surface and --surface-solid alike (page,
|
|
361
|
+
fields, a solid band), which rules out the primary itself. */
|
|
362
|
+
--focus: hsl(268 30% 55%);
|
|
361
363
|
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
362
364
|
|
|
363
365
|
/* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
|
|
@@ -380,7 +382,6 @@ select {
|
|
|
380
382
|
--heading: var(--text);
|
|
381
383
|
--selection-bg: var(--primary);
|
|
382
384
|
--selection-fg: var(--primary-fg);
|
|
383
|
-
--focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
384
385
|
}
|
|
385
386
|
|
|
386
387
|
/* Soft foreground hooks — the soft ink is the intent rebated toward the local
|
|
@@ -389,18 +390,17 @@ select {
|
|
|
389
390
|
default theme: a custom property bakes its var() references where it is
|
|
390
391
|
declared, so these resolve against root/light/dark tokens on every boundary.
|
|
391
392
|
Presets and custom islands keep their --soft-fg-mix / raw-intent ink unless
|
|
392
|
-
they declare hooks themselves
|
|
393
|
-
|
|
394
|
-
:where(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
:root,
|
|
393
|
+
they declare hooks themselves.
|
|
394
|
+
|
|
395
|
+
Why :where(:root) and why the reset comes second: both rules match when a
|
|
396
|
+
custom theme sits on <html> itself. A bare :root is (0,1,0) and would
|
|
397
|
+
outrank the zero-specificity reset, so the same theme resolved a calibrated
|
|
398
|
+
Ink & Terra ink on <html data-theme="brand"> and the raw --soft-fg-mix ink
|
|
399
|
+
on <div data-theme="brand"> — one attribute, two soft palettes. Levelling
|
|
400
|
+
both rules to zero specificity makes source order the tiebreaker, so the
|
|
401
|
+
reset must stay below. Zero specificity is also what lets a theme declare
|
|
402
|
+
its own hooks (e.g. brutalist's 45% soft-fg-mix) and win at (0,1,0). */
|
|
403
|
+
:where(:root),
|
|
404
404
|
[data-theme="light"],
|
|
405
405
|
[data-theme="dark"] {
|
|
406
406
|
--secondary-soft-fg: color-mix(in oklab, var(--text) 10%, var(--secondary) 90%);
|
|
@@ -410,6 +410,15 @@ select {
|
|
|
410
410
|
--neutral-soft-fg: color-mix(in oklab, var(--text) 20%, var(--neutral) 80%);
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
:where([data-theme]:not([data-theme="light"]):not([data-theme="dark"])) {
|
|
414
|
+
--primary-soft-fg: initial;
|
|
415
|
+
--secondary-soft-fg: initial;
|
|
416
|
+
--success-soft-fg: initial;
|
|
417
|
+
--warning-soft-fg: initial;
|
|
418
|
+
--danger-soft-fg: initial;
|
|
419
|
+
--neutral-soft-fg: initial;
|
|
420
|
+
}
|
|
421
|
+
|
|
413
422
|
/* Native platform controls (checkbox, radio, range, progress) that are not
|
|
414
423
|
restyled by a component class still follow the theme accent. Declared on
|
|
415
424
|
every island: accent-color inherits as a computed value, so islands must
|
|
@@ -431,20 +440,6 @@ select {
|
|
|
431
440
|
--tone-fg: var(--text);
|
|
432
441
|
}
|
|
433
442
|
|
|
434
|
-
/* Focus ring derivation — color-mix tier. Runs whether or not light-dark()
|
|
435
|
-
is also supported, so it covers the color-mix-without-light-dark browser
|
|
436
|
-
tier on its own; the light-dark block below refines it further with the
|
|
437
|
-
light/dark alpha split once both features are available. Declared on every
|
|
438
|
-
[data-theme] island (not just the built-in light/dark pair) so custom
|
|
439
|
-
islands recolor the ring for free; a preset that re-declares --focus-ring
|
|
440
|
-
later in source order intentionally pins its own ring instead. */
|
|
441
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
442
|
-
:root,
|
|
443
|
-
[data-theme] {
|
|
444
|
-
--focus-ring: color-mix(in oklch, var(--focus) 30%, transparent);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
443
|
/* Auto dark mode via light-dark(). Resolves based on the element's color-scheme:
|
|
449
444
|
:root (light dark) follows the OS, [data-theme="light"] forces light,
|
|
450
445
|
[data-theme="dark"] forces dark by overriding color-scheme.
|
|
@@ -481,22 +476,11 @@ select {
|
|
|
481
476
|
--text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%));
|
|
482
477
|
|
|
483
478
|
--border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%));
|
|
484
|
-
--focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28));
|
|
485
479
|
--hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06));
|
|
486
480
|
--shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%));
|
|
487
481
|
}
|
|
488
482
|
}
|
|
489
483
|
|
|
490
|
-
@supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) {
|
|
491
|
-
:root,
|
|
492
|
-
[data-theme] {
|
|
493
|
-
--focus-ring: light-dark(
|
|
494
|
-
color-mix(in oklch, var(--focus) 30%, transparent),
|
|
495
|
-
color-mix(in oklch, var(--focus) 28%, transparent)
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
484
|
@media (prefers-contrast: more) {
|
|
501
485
|
:root,
|
|
502
486
|
/* The repeated attribute intentionally outranks single-attribute preset
|
|
@@ -504,7 +488,7 @@ select {
|
|
|
504
488
|
[data-theme][data-theme] {
|
|
505
489
|
--border-width: 2px;
|
|
506
490
|
--focus-outline-offset: 3px;
|
|
507
|
-
--focus-ring-width:
|
|
491
|
+
--focus-ring-width: 3px;
|
|
508
492
|
--text-muted: var(--text);
|
|
509
493
|
--text-subtle: var(--text);
|
|
510
494
|
}
|
|
@@ -519,12 +503,14 @@ select {
|
|
|
519
503
|
* Public hooks:
|
|
520
504
|
* --link
|
|
521
505
|
* --hr-space
|
|
506
|
+
* --font-width / --font-width-dense (see variants.css for the dense application)
|
|
522
507
|
*/
|
|
523
508
|
|
|
524
509
|
body {
|
|
525
510
|
background: var(--surface);
|
|
526
511
|
color: var(--text);
|
|
527
512
|
font-family: var(--font-sans);
|
|
513
|
+
font-stretch: var(--font-width);
|
|
528
514
|
line-height: var(--line-height);
|
|
529
515
|
}
|
|
530
516
|
|
|
@@ -658,13 +644,9 @@ small {
|
|
|
658
644
|
* Cross-cutting variant token mapping.
|
|
659
645
|
*
|
|
660
646
|
* Variant scope: .solid/.soft/.outline/.surface set --ui-* for components that
|
|
661
|
-
* opt into shared variants.
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
* (.card, .navbar, .alert, .badge) invert through their own rules. Loaded
|
|
665
|
-
* before components, so a component with its own explicit surface (e.g.
|
|
666
|
-
* .accordion) naturally keeps that surface — .inverted never forces a
|
|
667
|
-
* non-participant.
|
|
647
|
+
* opt into shared variants. A subtree that needs another palette or scheme is
|
|
648
|
+
* a data-theme island, never a variant: only an island recomputes every token
|
|
649
|
+
* (fields, states, intents), where a remap of a few --ui-* tokens cannot.
|
|
668
650
|
* Component-specific defaults (.btn solid-by-default, .badge
|
|
669
651
|
* soft-by-default, .alert soft-by-default) live with their components:
|
|
670
652
|
* components/button.css — .btn default solid + .btn.ghost + .btn.link
|
|
@@ -672,9 +654,16 @@ small {
|
|
|
672
654
|
* components/alert.css — .alert default soft behind explicit --ui-*
|
|
673
655
|
*
|
|
674
656
|
* .compact/.spacious set inherited density tokens: spacing rhythm and geometry
|
|
675
|
-
* only. Density never changes
|
|
676
|
-
* --control-size; non-control components may
|
|
677
|
-
* context should affect their local rhythm.
|
|
657
|
+
* only, plus a slight typographic width bonus. Density never changes font size
|
|
658
|
+
* or icon size. Controls consume --control-size; non-control components may
|
|
659
|
+
* consume --density-* when the context should affect their local rhythm.
|
|
660
|
+
* .compact applies --font-width-dense through font-stretch (a variable wdth
|
|
661
|
+
* axis when available, otherwise normal width-face matching); layout must
|
|
662
|
+
* never depend on the narrowing being available. .spacious restores
|
|
663
|
+
* --font-width, so a spacious island inside a compact context returns to the
|
|
664
|
+
* normal width exactly as it restores its own gap, spacing, and control size.
|
|
665
|
+
* Themes whose typeface has no useful width axis opt out with
|
|
666
|
+
* --font-width-dense: 100%.
|
|
678
667
|
*
|
|
679
668
|
* .sm/.lg are local size roles, not shared measurements. Shared control
|
|
680
669
|
* participants below map both typography and geometry; components with
|
|
@@ -713,38 +702,18 @@ small {
|
|
|
713
702
|
--ui-border: var(--border);
|
|
714
703
|
}
|
|
715
704
|
|
|
716
|
-
/* Inverse surface treatment. Paints the element directly (so it works on any
|
|
717
|
-
bare block) and remaps the shared --ui-* tokens so opt-in components
|
|
718
|
-
(.card, .navbar) invert through their own rules. Because this loads before
|
|
719
|
-
component base surfaces, components with their own explicit surface
|
|
720
|
-
(e.g. .accordion) keep it — participation is opt-in, never forced. --heading
|
|
721
|
-
is set so global headings (var(--heading, inherit)) do not inherit an outside
|
|
722
|
-
heading color onto the dark surface; surface-owning descendants derive
|
|
723
|
-
their contextual aliases from their own local surface. */
|
|
724
|
-
.inverted {
|
|
725
|
-
--ui-bg: var(--surface-solid);
|
|
726
|
-
--ui-fg: var(--surface);
|
|
727
|
-
--ui-border: transparent;
|
|
728
|
-
--ui-hover-bg: var(--surface-solid);
|
|
729
|
-
|
|
730
|
-
--heading: var(--surface);
|
|
731
|
-
--busy-overlay-bg: var(--surface-solid);
|
|
732
|
-
|
|
733
|
-
background: var(--ui-bg);
|
|
734
|
-
color: var(--ui-fg);
|
|
735
|
-
border-color: var(--ui-border);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
705
|
.compact {
|
|
739
706
|
--gap: var(--space-20);
|
|
740
707
|
--density-space: var(--space-20);
|
|
741
708
|
--control-size: var(--control-size-sm);
|
|
709
|
+
font-stretch: var(--font-width-dense);
|
|
742
710
|
}
|
|
743
711
|
|
|
744
712
|
.spacious {
|
|
745
713
|
--gap: var(--space-50);
|
|
746
714
|
--density-space: var(--space-50);
|
|
747
715
|
--control-size: var(--control-size-lg);
|
|
716
|
+
font-stretch: var(--font-width);
|
|
748
717
|
}
|
|
749
718
|
|
|
750
719
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
@@ -816,10 +785,6 @@ small {
|
|
|
816
785
|
);
|
|
817
786
|
/* @sync soft-recipe */
|
|
818
787
|
}
|
|
819
|
-
|
|
820
|
-
.inverted {
|
|
821
|
-
--ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black);
|
|
822
|
-
}
|
|
823
788
|
}
|
|
824
789
|
|
|
825
790
|
/* Interactive hover recipes live in the components that consume them:
|
|
@@ -830,9 +795,15 @@ small {
|
|
|
830
795
|
pointer/mouse focus and interactive components layer their ring on top.
|
|
831
796
|
|
|
832
797
|
Invariant: an interactive component must never rely on box-shadow alone
|
|
833
|
-
for its keyboard focus indicator
|
|
834
|
-
|
|
835
|
-
|
|
798
|
+
for its keyboard focus indicator; forced colors keep outlines and drop
|
|
799
|
+
shadows.
|
|
800
|
+
|
|
801
|
+
Component indicators are a solid --focus-ring-width line in --focus: inset
|
|
802
|
+
on fields (inside their border box), offset by --focus-outline-offset on
|
|
803
|
+
discrete actions. The color never derives from an intent or a local
|
|
804
|
+
surface — the theme guarantees --focus at 3:1 against both --surface and
|
|
805
|
+
--surface-solid, so one token reads on the page, on a solid band and on
|
|
806
|
+
any nested surface. */
|
|
836
807
|
:focus {
|
|
837
808
|
outline: var(--focus-outline);
|
|
838
809
|
outline-offset: var(--focus-outline-offset);
|
|
@@ -1377,7 +1348,7 @@ small {
|
|
|
1377
1348
|
*
|
|
1378
1349
|
* Docs: docs/pages/layout/app-layout.md
|
|
1379
1350
|
*
|
|
1380
|
-
* Public
|
|
1351
|
+
* Public hooks:
|
|
1381
1352
|
* --app-nav-side-size
|
|
1382
1353
|
*/
|
|
1383
1354
|
.app-layout {
|
|
@@ -1706,12 +1677,12 @@ small {
|
|
|
1706
1677
|
* Docs: docs/pages/layout/column-layout.md
|
|
1707
1678
|
*
|
|
1708
1679
|
* Neither space-driven like .grid nor count-driven like .grid-N: this is a
|
|
1709
|
-
* coordinate system.
|
|
1710
|
-
*
|
|
1711
|
-
* deliberately no automatic collapse — no threshold,
|
|
1712
|
-
* .container-query behavior, no breakpoint variants. A single
|
|
1713
|
-
* threshold cannot be right for arbitrary per-region compositions,
|
|
1714
|
-
* hidden default would have to be undone rather than extended.
|
|
1680
|
+
* coordinate system. Each direct child places itself with two numbers,
|
|
1681
|
+
* --column-start and --column-span, and the author owns narrow-container
|
|
1682
|
+
* recomposition. There is deliberately no automatic collapse — no threshold,
|
|
1683
|
+
* no hidden .container-query behavior, no breakpoint variants. A single
|
|
1684
|
+
* framework threshold cannot be right for arbitrary per-region compositions,
|
|
1685
|
+
* and a hidden default would have to be undone rather than extended.
|
|
1715
1686
|
*
|
|
1716
1687
|
* See docs/design-notes/column-layout.md.
|
|
1717
1688
|
*
|
|
@@ -1723,11 +1694,12 @@ small {
|
|
|
1723
1694
|
* media + flexible content -> .media
|
|
1724
1695
|
* exact custom track templates -> --grid-columns on .grid
|
|
1725
1696
|
*
|
|
1726
|
-
* Child contract: each direct child is placed on the canvas via its own
|
|
1727
|
-
* .column-span-N / .column-start-N.
|
|
1728
|
-
*
|
|
1729
1697
|
* Unlike .grid-N, the track template does not read --gap, so the gap
|
|
1730
1698
|
* utilities are exact here at every width.
|
|
1699
|
+
*
|
|
1700
|
+
* Public hooks:
|
|
1701
|
+
* --column-start (the start line, 1-12; unset = auto-placed)
|
|
1702
|
+
* --column-span (the width in canvas units, 1-12; unset = to the canvas end)
|
|
1731
1703
|
*/
|
|
1732
1704
|
.column-layout {
|
|
1733
1705
|
display: grid;
|
|
@@ -1735,116 +1707,30 @@ small {
|
|
|
1735
1707
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
|
1736
1708
|
}
|
|
1737
1709
|
|
|
1738
|
-
/*
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1741
|
-
*
|
|
1742
|
-
*
|
|
1743
|
-
* 3. .column-span-* the end only, so it refines either of the above
|
|
1710
|
+
/* One rule covers the four placements:
|
|
1711
|
+
* neither auto-placed, spanning the whole canvas
|
|
1712
|
+
* span only auto-placed over N units, so 8 + 4 flows onto one row
|
|
1713
|
+
* start only from line S to the canvas end (span 13 - S)
|
|
1714
|
+
* start and span explicit; keep S + N - 1 <= 12
|
|
1744
1715
|
*
|
|
1745
|
-
*
|
|
1746
|
-
*
|
|
1747
|
-
* are silent. tests/css-audit.test.js locks the order and
|
|
1748
|
-
* tests/browser/column-layout.test.js locks the resulting geometry.
|
|
1716
|
+
* The start stays `auto` unless set: a definite start on every child would
|
|
1717
|
+
* take all of them out of auto-placement, and an 8 + 4 pair would stack.
|
|
1749
1718
|
*
|
|
1750
|
-
*
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
/* Tier 1. A child with no placement class stays a readable full-width region
|
|
1756
|
-
* instead of collapsing to one twelfth. Expressed as a span, not as
|
|
1757
|
-
* `1 / -1`: a definite start line would give every child a definite column
|
|
1758
|
-
* position and disable auto-placement, so `.column-span-8` next to
|
|
1759
|
-
* `.column-span-4` would stack instead of composing 8 + 4.
|
|
1719
|
+
* The reset makes the two hooks inputs of this grid item rather than an
|
|
1720
|
+
* inherited context: a nested .column-layout keeps the coordinates it was
|
|
1721
|
+
* given for its own placement, and its children start from nothing. Zero
|
|
1722
|
+
* specificity, so any author rule on the child wins.
|
|
1760
1723
|
*
|
|
1761
1724
|
* min-inline-size: 0 keeps the item box inside its track so the canvas never
|
|
1762
1725
|
* grows past its container. It does not stop content from painting outside a
|
|
1763
1726
|
* narrow track — that distinction is the primitive's contract, not a bug. */
|
|
1764
1727
|
:where(.column-layout) > * {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
/* Tier 2. A definite start line, running to the end of the canvas unless a
|
|
1770
|
-
* .column-span-* refines the end. `column-start-9` alone is therefore the
|
|
1771
|
-
* last four columns rather than an implicit 21-line overflow. */
|
|
1772
|
-
:where(.column-start-1) {
|
|
1773
|
-
grid-column: 1 / -1;
|
|
1774
|
-
}
|
|
1775
|
-
:where(.column-start-2) {
|
|
1776
|
-
grid-column: 2 / -1;
|
|
1777
|
-
}
|
|
1778
|
-
:where(.column-start-3) {
|
|
1779
|
-
grid-column: 3 / -1;
|
|
1780
|
-
}
|
|
1781
|
-
:where(.column-start-4) {
|
|
1782
|
-
grid-column: 4 / -1;
|
|
1783
|
-
}
|
|
1784
|
-
:where(.column-start-5) {
|
|
1785
|
-
grid-column: 5 / -1;
|
|
1786
|
-
}
|
|
1787
|
-
:where(.column-start-6) {
|
|
1788
|
-
grid-column: 6 / -1;
|
|
1789
|
-
}
|
|
1790
|
-
:where(.column-start-7) {
|
|
1791
|
-
grid-column: 7 / -1;
|
|
1792
|
-
}
|
|
1793
|
-
:where(.column-start-8) {
|
|
1794
|
-
grid-column: 8 / -1;
|
|
1795
|
-
}
|
|
1796
|
-
:where(.column-start-9) {
|
|
1797
|
-
grid-column: 9 / -1;
|
|
1798
|
-
}
|
|
1799
|
-
:where(.column-start-10) {
|
|
1800
|
-
grid-column: 10 / -1;
|
|
1801
|
-
}
|
|
1802
|
-
:where(.column-start-11) {
|
|
1803
|
-
grid-column: 11 / -1;
|
|
1804
|
-
}
|
|
1805
|
-
:where(.column-start-12) {
|
|
1806
|
-
grid-column: 12 / -1;
|
|
1807
|
-
}
|
|
1728
|
+
--column-start: initial;
|
|
1729
|
+
--column-span: initial;
|
|
1808
1730
|
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
* just the offending region. */
|
|
1813
|
-
:where(.column-span-1) {
|
|
1814
|
-
grid-column-end: span 1;
|
|
1815
|
-
}
|
|
1816
|
-
:where(.column-span-2) {
|
|
1817
|
-
grid-column-end: span 2;
|
|
1818
|
-
}
|
|
1819
|
-
:where(.column-span-3) {
|
|
1820
|
-
grid-column-end: span 3;
|
|
1821
|
-
}
|
|
1822
|
-
:where(.column-span-4) {
|
|
1823
|
-
grid-column-end: span 4;
|
|
1824
|
-
}
|
|
1825
|
-
:where(.column-span-5) {
|
|
1826
|
-
grid-column-end: span 5;
|
|
1827
|
-
}
|
|
1828
|
-
:where(.column-span-6) {
|
|
1829
|
-
grid-column-end: span 6;
|
|
1830
|
-
}
|
|
1831
|
-
:where(.column-span-7) {
|
|
1832
|
-
grid-column-end: span 7;
|
|
1833
|
-
}
|
|
1834
|
-
:where(.column-span-8) {
|
|
1835
|
-
grid-column-end: span 8;
|
|
1836
|
-
}
|
|
1837
|
-
:where(.column-span-9) {
|
|
1838
|
-
grid-column-end: span 9;
|
|
1839
|
-
}
|
|
1840
|
-
:where(.column-span-10) {
|
|
1841
|
-
grid-column-end: span 10;
|
|
1842
|
-
}
|
|
1843
|
-
:where(.column-span-11) {
|
|
1844
|
-
grid-column-end: span 11;
|
|
1845
|
-
}
|
|
1846
|
-
:where(.column-span-12) {
|
|
1847
|
-
grid-column-end: span 12;
|
|
1731
|
+
grid-column-start: var(--column-start, auto);
|
|
1732
|
+
grid-column-end: span var(--column-span, calc(13 - var(--column-start, 1)));
|
|
1733
|
+
min-inline-size: 0;
|
|
1848
1734
|
}
|
|
1849
1735
|
|
|
1850
1736
|
/*
|
|
@@ -2049,6 +1935,12 @@ small {
|
|
|
2049
1935
|
*/
|
|
2050
1936
|
.field {
|
|
2051
1937
|
display: grid;
|
|
1938
|
+
|
|
1939
|
+
/* why: a field stretched by a taller row sibling — the classic case is a
|
|
1940
|
+
neighbour revealing its .field-error — would otherwise hand the extra
|
|
1941
|
+
block size to its auto rows and inflate the control. Rows keep their
|
|
1942
|
+
natural height; the slack stays at the end. */
|
|
1943
|
+
align-content: start;
|
|
2052
1944
|
gap: var(--space-10);
|
|
2053
1945
|
color: var(--text);
|
|
2054
1946
|
font-size: var(--control-font-size);
|
|
@@ -2119,6 +2011,8 @@ small {
|
|
|
2119
2011
|
* --control-cursor
|
|
2120
2012
|
* --control-border-block-end-color
|
|
2121
2013
|
* --control-border-block-end-width
|
|
2014
|
+
* --control-hover-border (input and textarea)
|
|
2015
|
+
* --control-hover-bg (select)
|
|
2122
2016
|
*/
|
|
2123
2017
|
.input,
|
|
2124
2018
|
.textarea,
|
|
@@ -2163,19 +2057,24 @@ small {
|
|
|
2163
2057
|
|
|
2164
2058
|
.input:hover:not(:disabled),
|
|
2165
2059
|
.textarea:hover:not(:disabled) {
|
|
2166
|
-
--control-border: var(--text-subtle);
|
|
2060
|
+
--control-border: var(--control-hover-border, var(--text-subtle));
|
|
2167
2061
|
}
|
|
2168
2062
|
|
|
2169
2063
|
.select:hover:not(:disabled) {
|
|
2170
|
-
--control-bg: var(--surface-subtle);
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
.
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2064
|
+
--control-bg: var(--control-hover-bg, var(--surface-subtle));
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/* Field focus is an inset outline: offset = -width keeps every pixel inside
|
|
2068
|
+
the control's border box (any smaller offset bleeds outward), so it covers
|
|
2069
|
+
the border, never touches a .join neighbour or a scrollport edge, and needs
|
|
2070
|
+
no group treatment. Width follows --focus-ring-width, including its
|
|
2071
|
+
prefers-contrast step. Border = shape and validation, outline = focus. */
|
|
2072
|
+
.input:focus-visible:not(:disabled),
|
|
2073
|
+
.textarea:focus-visible:not(:disabled),
|
|
2074
|
+
.select:focus-visible:not(:disabled) {
|
|
2075
|
+
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
|
|
2076
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
2077
|
+
box-shadow: none;
|
|
2179
2078
|
}
|
|
2180
2079
|
|
|
2181
2080
|
.input[readonly],
|
|
@@ -2428,9 +2327,8 @@ small {
|
|
|
2428
2327
|
|
|
2429
2328
|
.check:focus-visible,
|
|
2430
2329
|
.radio:focus-visible {
|
|
2431
|
-
--
|
|
2432
|
-
outline:
|
|
2433
|
-
box-shadow: var(--focus-ring-shadow);
|
|
2330
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
2331
|
+
outline-offset: var(--focus-outline-offset);
|
|
2434
2332
|
}
|
|
2435
2333
|
|
|
2436
2334
|
.check:disabled,
|
|
@@ -2552,8 +2450,8 @@ small {
|
|
|
2552
2450
|
}
|
|
2553
2451
|
|
|
2554
2452
|
.file:focus-visible {
|
|
2555
|
-
outline:
|
|
2556
|
-
|
|
2453
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
2454
|
+
outline-offset: var(--focus-outline-offset);
|
|
2557
2455
|
}
|
|
2558
2456
|
|
|
2559
2457
|
/* Native color picker — keep the browser's dialog and selected-color UI. The
|
|
@@ -2589,8 +2487,8 @@ small {
|
|
|
2589
2487
|
}
|
|
2590
2488
|
|
|
2591
2489
|
.color:focus-visible {
|
|
2592
|
-
outline:
|
|
2593
|
-
|
|
2490
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
2491
|
+
outline-offset: var(--focus-outline-offset);
|
|
2594
2492
|
}
|
|
2595
2493
|
|
|
2596
2494
|
@media (forced-colors: active) {
|
|
@@ -2954,9 +2852,8 @@ small {
|
|
|
2954
2852
|
}
|
|
2955
2853
|
|
|
2956
2854
|
.switch:focus-visible {
|
|
2957
|
-
--
|
|
2958
|
-
outline:
|
|
2959
|
-
box-shadow: var(--focus-ring-shadow);
|
|
2855
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
2856
|
+
outline-offset: var(--focus-outline-offset);
|
|
2960
2857
|
}
|
|
2961
2858
|
|
|
2962
2859
|
.switch:disabled {
|
|
@@ -3054,20 +2951,22 @@ small {
|
|
|
3054
2951
|
}
|
|
3055
2952
|
|
|
3056
2953
|
/*
|
|
3057
|
-
*
|
|
3058
|
-
*
|
|
3059
|
-
*
|
|
2954
|
+
* A thumb pseudo-element takes no reliable outline, so its ring is a solid
|
|
2955
|
+
* --focus shadow (the transparent outline above covers forced colors). The
|
|
2956
|
+
* 1px surface-coloured separator keeps the ring visually distinct from the
|
|
2957
|
+
* small, coloured thumb — unlike a full-size input where the same gap would
|
|
2958
|
+
* read as an unwanted void.
|
|
3060
2959
|
*/
|
|
3061
2960
|
.range:focus-visible::-webkit-slider-thumb {
|
|
3062
2961
|
box-shadow:
|
|
3063
2962
|
0 0 0 1px var(--surface),
|
|
3064
|
-
0 0 0 calc(1px + var(--focus-ring-width)) var(--focus
|
|
2963
|
+
0 0 0 calc(1px + var(--focus-ring-width)) var(--focus);
|
|
3065
2964
|
}
|
|
3066
2965
|
|
|
3067
2966
|
.range:focus-visible::-moz-range-thumb {
|
|
3068
2967
|
box-shadow:
|
|
3069
2968
|
0 0 0 1px var(--surface),
|
|
3070
|
-
0 0 0 calc(1px + var(--focus-ring-width)) var(--focus
|
|
2969
|
+
0 0 0 calc(1px + var(--focus-ring-width)) var(--focus);
|
|
3071
2970
|
}
|
|
3072
2971
|
|
|
3073
2972
|
.range:disabled {
|
|
@@ -3077,20 +2976,33 @@ small {
|
|
|
3077
2976
|
}
|
|
3078
2977
|
|
|
3079
2978
|
/* Forced colors: the track must stay a visible line against the page (Canvas
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
2979
|
+
would merge with it) and the thumb needs a real boundary to remain
|
|
2980
|
+
distinguishable from the track. Disabled keeps GrayText via :disabled.
|
|
2981
|
+
Focus stays on the transparent outline of :focus-visible. Track and thumb
|
|
2982
|
+
fills are written on the properties, not only the hooks: an application
|
|
2983
|
+
hook override at the same specificity loads after this file and would win
|
|
2984
|
+
the variable, leaving a Canvas-remapped fill. */
|
|
3083
2985
|
@media (forced-colors: active) {
|
|
3084
2986
|
.range {
|
|
3085
2987
|
--range-track-bg: CanvasText;
|
|
3086
2988
|
--range-thumb-bg: ButtonFace;
|
|
3087
2989
|
}
|
|
3088
2990
|
|
|
2991
|
+
.range::-webkit-slider-runnable-track {
|
|
2992
|
+
background: CanvasText;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
.range::-moz-range-track {
|
|
2996
|
+
background: CanvasText;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
3089
2999
|
.range::-webkit-slider-thumb {
|
|
3000
|
+
background: ButtonFace;
|
|
3090
3001
|
border: var(--border-width) solid ButtonText;
|
|
3091
3002
|
}
|
|
3092
3003
|
|
|
3093
3004
|
.range::-moz-range-thumb {
|
|
3005
|
+
background: ButtonFace;
|
|
3094
3006
|
border: var(--border-width) solid ButtonText;
|
|
3095
3007
|
}
|
|
3096
3008
|
}
|
|
@@ -3110,12 +3022,18 @@ small {
|
|
|
3110
3022
|
* overflow: hidden will clip it. Pair with grid or flex utilities for group
|
|
3111
3023
|
* layout.
|
|
3112
3024
|
*
|
|
3025
|
+
* .chip is the compact pill form (filter chips, selectable tags): control
|
|
3026
|
+
* height, and a leading dot that turns into the check in a fixed slot, so
|
|
3027
|
+
* checking never moves the label.
|
|
3028
|
+
*
|
|
3113
3029
|
* Public hooks:
|
|
3114
3030
|
* --choice-card-pad
|
|
3115
3031
|
* --choice-card-radius
|
|
3116
3032
|
* --choice-card-border
|
|
3117
3033
|
* --choice-card-bg
|
|
3118
3034
|
* --choice-card-check-size
|
|
3035
|
+
* --choice-chip-dot (read-only, on .chip ::before: the check-slot centre as a
|
|
3036
|
+
* circle() position, for a theme that grows the dot into a fill)
|
|
3119
3037
|
*/
|
|
3120
3038
|
:where(.choice-card) {
|
|
3121
3039
|
--intent: initial;
|
|
@@ -3165,8 +3083,8 @@ small {
|
|
|
3165
3083
|
|
|
3166
3084
|
/* Focus ring on the label follows the hidden input's focus. */
|
|
3167
3085
|
.choice-card:has(:focus-visible) {
|
|
3168
|
-
outline:
|
|
3169
|
-
|
|
3086
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
3087
|
+
outline-offset: var(--focus-outline-offset);
|
|
3170
3088
|
}
|
|
3171
3089
|
|
|
3172
3090
|
.choice-card:has(:disabled) {
|
|
@@ -3211,11 +3129,13 @@ small {
|
|
|
3211
3129
|
z-index: 1;
|
|
3212
3130
|
}
|
|
3213
3131
|
|
|
3214
|
-
|
|
3132
|
+
/* :where() keeps the shape at single-class weight, so .chip can reshape the
|
|
3133
|
+
layer without out-arguing the input-type test. */
|
|
3134
|
+
.choice-card:where(:has(input[type="radio"]))::before {
|
|
3215
3135
|
border-radius: var(--radius-full);
|
|
3216
3136
|
}
|
|
3217
3137
|
|
|
3218
|
-
.choice-card:has(input[type="checkbox"])::before {
|
|
3138
|
+
.choice-card:where(:has(input[type="checkbox"]))::before {
|
|
3219
3139
|
border-radius: var(--radius-sm);
|
|
3220
3140
|
}
|
|
3221
3141
|
|
|
@@ -3232,6 +3152,54 @@ small {
|
|
|
3232
3152
|
transform: scale(1);
|
|
3233
3153
|
}
|
|
3234
3154
|
|
|
3155
|
+
/* Chip — the rules above stay in force; this section only relocates the
|
|
3156
|
+
indicator. Must follow them: several ties are won by source order. */
|
|
3157
|
+
.choice-card.chip {
|
|
3158
|
+
--choice-card-pad: 0 var(--control-pad-x);
|
|
3159
|
+
--choice-card-radius: var(--radius-full);
|
|
3160
|
+
--choice-card-check-size: 1em;
|
|
3161
|
+
|
|
3162
|
+
display: inline-flex;
|
|
3163
|
+
align-items: center;
|
|
3164
|
+
min-block-size: var(--control-size);
|
|
3165
|
+
line-height: 1.2;
|
|
3166
|
+
/* The dot layer sits under the label at z-index -1. */
|
|
3167
|
+
isolation: isolate;
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
/* ::before is a full-bleed layer clipped to a dot centred on the check slot,
|
|
3171
|
+
so a theme can grow the same clip into a selected fill. The chip itself
|
|
3172
|
+
must not clip (its focus ring is an outline), hence the inherited radius. */
|
|
3173
|
+
.choice-card.chip::before {
|
|
3174
|
+
--choice-chip-dot: calc(var(--control-pad-x) + var(--choice-card-check-size) / 2) 50%;
|
|
3175
|
+
|
|
3176
|
+
inset: 0;
|
|
3177
|
+
inline-size: auto;
|
|
3178
|
+
block-size: auto;
|
|
3179
|
+
border-radius: inherit;
|
|
3180
|
+
box-shadow: none;
|
|
3181
|
+
background: var(--text-subtle);
|
|
3182
|
+
transform: none;
|
|
3183
|
+
clip-path: circle(0.1875em at var(--choice-chip-dot));
|
|
3184
|
+
z-index: -1;
|
|
3185
|
+
transition: clip-path var(--duration);
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
.choice-card.chip:has(:checked)::before {
|
|
3189
|
+
clip-path: circle(0 at var(--choice-chip-dot));
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
/* The check keeps the card's scale-in, in flow ahead of the label. */
|
|
3193
|
+
.choice-card.chip::after {
|
|
3194
|
+
position: static;
|
|
3195
|
+
order: -1;
|
|
3196
|
+
flex: none;
|
|
3197
|
+
margin-inline-end: 0.375em;
|
|
3198
|
+
background: var(--intent, var(--state-selected));
|
|
3199
|
+
-webkit-mask-size: contain;
|
|
3200
|
+
mask-size: contain;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3235
3203
|
/* The native input is hidden and the checked marker is custom-drawn: without
|
|
3236
3204
|
this repair, forced colors pushes disc and check to the same system color
|
|
3237
3205
|
and the state disappears. Highlight/Canvas/HighlightText are preserved as-is,
|
|
@@ -3246,6 +3214,20 @@ small {
|
|
|
3246
3214
|
.choice-card:has(:checked)::after {
|
|
3247
3215
|
background: HighlightText;
|
|
3248
3216
|
}
|
|
3217
|
+
|
|
3218
|
+
/* A chip has no disc behind its check: the check carries the state alone,
|
|
3219
|
+
and dropping the checked layer also drops any theme fill. */
|
|
3220
|
+
.choice-card.chip::before {
|
|
3221
|
+
background: CanvasText;
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
.choice-card.chip:has(:checked)::before {
|
|
3225
|
+
content: none;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
.choice-card.chip:has(:checked)::after {
|
|
3229
|
+
background: CanvasText;
|
|
3230
|
+
}
|
|
3249
3231
|
}
|
|
3250
3232
|
|
|
3251
3233
|
/*
|
|
@@ -3259,7 +3241,6 @@ small {
|
|
|
3259
3241
|
* Arbitrary menu and popover behavior belongs elsewhere.
|
|
3260
3242
|
*
|
|
3261
3243
|
* Internal:
|
|
3262
|
-
* --control-border
|
|
3263
3244
|
* --control-bg
|
|
3264
3245
|
*/
|
|
3265
3246
|
/* This enhancement is excluded from forced-colors, where the native/shared
|
|
@@ -3284,11 +3265,12 @@ small {
|
|
|
3284
3265
|
box-shadow: var(--shadow-popout);
|
|
3285
3266
|
}
|
|
3286
3267
|
|
|
3268
|
+
/* Same inset outline as a focused field (control.css). */
|
|
3287
3269
|
&:open {
|
|
3288
|
-
--control-border: var(--border);
|
|
3289
3270
|
--control-bg: var(--surface-raised);
|
|
3290
|
-
outline:
|
|
3291
|
-
|
|
3271
|
+
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
|
|
3272
|
+
outline-offset: calc(var(--focus-ring-width) * -1);
|
|
3273
|
+
box-shadow: none;
|
|
3292
3274
|
}
|
|
3293
3275
|
|
|
3294
3276
|
&::picker-icon {
|
|
@@ -3617,7 +3599,6 @@ small {
|
|
|
3617
3599
|
* --btn-min-size
|
|
3618
3600
|
* --btn-pad-x
|
|
3619
3601
|
* --btn-radius
|
|
3620
|
-
* --btn-focus-color
|
|
3621
3602
|
* --btn-gap
|
|
3622
3603
|
* --btn-hover-overlay
|
|
3623
3604
|
* --btn-default-bg
|
|
@@ -3630,7 +3611,6 @@ small {
|
|
|
3630
3611
|
* --btn-fg
|
|
3631
3612
|
* --btn-border
|
|
3632
3613
|
* --btn-default-hover-bg
|
|
3633
|
-
* --btn-focus-ring-color
|
|
3634
3614
|
* --btn-border-block-end-color
|
|
3635
3615
|
* --btn-border-block-end-width
|
|
3636
3616
|
* --spinner-width
|
|
@@ -3657,7 +3637,6 @@ small {
|
|
|
3657
3637
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
3658
3638
|
--btn-default-hover-bg: var(--btn-bg);
|
|
3659
3639
|
--btn-radius: var(--radius);
|
|
3660
|
-
--btn-focus-color: var(--intent, var(--focus));
|
|
3661
3640
|
--btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
|
|
3662
3641
|
|
|
3663
3642
|
/* Positioning context for indicators, overlays, and grouped states. */
|
|
@@ -3699,12 +3678,6 @@ small {
|
|
|
3699
3678
|
padding: 0;
|
|
3700
3679
|
}
|
|
3701
3680
|
|
|
3702
|
-
@supports (color: color-mix(in oklch, red, white)) {
|
|
3703
|
-
.btn {
|
|
3704
|
-
--btn-focus-ring-color: color-mix(in oklch, var(--btn-focus-color) 28%, transparent);
|
|
3705
|
-
}
|
|
3706
|
-
}
|
|
3707
|
-
|
|
3708
3681
|
/* .solid/.soft/.outline are shared via variants.css. The baseline --btn-*
|
|
3709
3682
|
fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
|
|
3710
3683
|
.btn.ghost {
|
|
@@ -3792,15 +3765,13 @@ small {
|
|
|
3792
3765
|
text-decoration-thickness: 0.12em;
|
|
3793
3766
|
}
|
|
3794
3767
|
|
|
3768
|
+
/* why: the ring must contrast with the surface around the button, not with
|
|
3769
|
+
the button, so it is --focus for every intent and variant — the theme
|
|
3770
|
+
guarantees --focus against --surface and --surface-solid, which covers
|
|
3771
|
+
the page, a solid band and nested surfaces without tracking any context. */
|
|
3795
3772
|
.btn:focus-visible {
|
|
3796
|
-
outline:
|
|
3797
|
-
|
|
3798
|
-
}
|
|
3799
|
-
|
|
3800
|
-
.btn.link:focus-visible {
|
|
3801
|
-
outline: var(--focus-outline);
|
|
3802
|
-
outline-offset: 0.2em;
|
|
3803
|
-
box-shadow: none;
|
|
3773
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
3774
|
+
outline-offset: var(--focus-outline-offset);
|
|
3804
3775
|
}
|
|
3805
3776
|
|
|
3806
3777
|
.btn:disabled {
|
|
@@ -3846,6 +3817,98 @@ a.btn[aria-disabled="true"] {
|
|
|
3846
3817
|
}
|
|
3847
3818
|
}
|
|
3848
3819
|
|
|
3820
|
+
/*
|
|
3821
|
+
* Close button — the shared X that removes or closes the thing it sits in.
|
|
3822
|
+
*
|
|
3823
|
+
* Docs: docs/pages/components/close.md
|
|
3824
|
+
*
|
|
3825
|
+
* .close is always the X, painted from the --icon-close mask: an empty
|
|
3826
|
+
* <button class="close" aria-label="…"> needs no icon font and no text glyph.
|
|
3827
|
+
* The mechanism stays with the container (command="request-close",
|
|
3828
|
+
* method="dialog", command="--dismiss", application logic); the class only
|
|
3829
|
+
* names the visual action.
|
|
3830
|
+
*
|
|
3831
|
+
* The containers that host one (alert, badge) tune it from their own files by
|
|
3832
|
+
* declaring the size hooks and their geometry; this file owns the control
|
|
3833
|
+
* and the dialog-surface corner, shared by .modal and .drawer.
|
|
3834
|
+
*
|
|
3835
|
+
* Public hooks:
|
|
3836
|
+
* --close-size
|
|
3837
|
+
* --close-icon-size
|
|
3838
|
+
*/
|
|
3839
|
+
.close {
|
|
3840
|
+
display: inline-grid;
|
|
3841
|
+
place-items: center;
|
|
3842
|
+
flex: none;
|
|
3843
|
+
inline-size: var(--close-size, var(--control-size));
|
|
3844
|
+
block-size: var(--close-size, var(--control-size));
|
|
3845
|
+
padding: 0;
|
|
3846
|
+
border: 0;
|
|
3847
|
+
border-radius: var(--radius);
|
|
3848
|
+
background: transparent;
|
|
3849
|
+
color: inherit;
|
|
3850
|
+
cursor: pointer;
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
.close::before {
|
|
3854
|
+
content: "";
|
|
3855
|
+
inline-size: var(--close-icon-size, 1rem);
|
|
3856
|
+
block-size: var(--close-icon-size, 1rem);
|
|
3857
|
+
background: currentColor;
|
|
3858
|
+
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
3859
|
+
mask: var(--icon-close) center / contain no-repeat;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
/* An inset overlay, not a background: it tints whatever fill the context gave
|
|
3863
|
+
the control (the dialog corner's --surface-subtle) instead of replacing it. */
|
|
3864
|
+
@media (hover: hover) {
|
|
3865
|
+
.close:hover {
|
|
3866
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
.close:active {
|
|
3871
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
.close:focus-visible {
|
|
3875
|
+
outline: var(--focus-ring-width) solid var(--focus);
|
|
3876
|
+
outline-offset: var(--focus-outline-offset);
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
/* Dialog corner — out of the content flow, anchored to the positioned dialog
|
|
3880
|
+
surface rather than to the header or a scroll container inside it. Matched
|
|
3881
|
+
on the documented positions (the dialog, its form, its header, or a form
|
|
3882
|
+
inside that header) so a .close inside content — an alert in the body —
|
|
3883
|
+
keeps its own place. */
|
|
3884
|
+
:where(dialog):is(.modal, .drawer) > .close,
|
|
3885
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close,
|
|
3886
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close {
|
|
3887
|
+
position: absolute;
|
|
3888
|
+
z-index: 1;
|
|
3889
|
+
inset-block-start: var(--space-20);
|
|
3890
|
+
inset-inline-end: var(--space-20);
|
|
3891
|
+
background: var(--surface-subtle);
|
|
3892
|
+
color: var(--text-muted);
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
@media (hover: hover) {
|
|
3896
|
+
:where(dialog):is(.modal, .drawer) > .close:hover,
|
|
3897
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close:hover,
|
|
3898
|
+
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close:hover {
|
|
3899
|
+
color: var(--text);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
@media (forced-colors: active) {
|
|
3904
|
+
/* The X is a currentColor mask fill: without this the system remaps the
|
|
3905
|
+
background to Canvas and the glyph disappears. Kept on the pseudo so the
|
|
3906
|
+
host keeps its native color and focus adaptation. */
|
|
3907
|
+
.close::before {
|
|
3908
|
+
forced-color-adjust: none;
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3849
3912
|
/*
|
|
3850
3913
|
* Badges for compact labels, counts, and state hints.
|
|
3851
3914
|
*
|
|
@@ -3862,7 +3925,6 @@ a.btn[aria-disabled="true"] {
|
|
|
3862
3925
|
* --badge-icon-size
|
|
3863
3926
|
* --badge-dot-size
|
|
3864
3927
|
* --badge-radius
|
|
3865
|
-
* --badge-dismiss-icon-size
|
|
3866
3928
|
*
|
|
3867
3929
|
* Internal:
|
|
3868
3930
|
* --badge-default-bg
|
|
@@ -3893,7 +3955,10 @@ a.btn[aria-disabled="true"] {
|
|
|
3893
3955
|
--badge-font-size: var(--font-size-sm);
|
|
3894
3956
|
--badge-icon-size: 1em;
|
|
3895
3957
|
--badge-dot-size: 0.5rem;
|
|
3896
|
-
|
|
3958
|
+
/* Declared on the badge, not the button, so an author can still set them
|
|
3959
|
+
on either. Same element as --badge-size, so .sm/.lg carry through. */
|
|
3960
|
+
--close-size: max(var(--badge-size), 24px);
|
|
3961
|
+
--close-icon-size: 0.875em;
|
|
3897
3962
|
--badge-default-bg: var(--surface-subtle);
|
|
3898
3963
|
--badge-default-fg: var(--intent, var(--text));
|
|
3899
3964
|
--badge-default-border: var(--border);
|
|
@@ -3943,8 +4008,8 @@ a.btn[aria-disabled="true"] {
|
|
|
3943
4008
|
--badge-dot-size: 0.625rem;
|
|
3944
4009
|
}
|
|
3945
4010
|
|
|
3946
|
-
/* Decorative children follow the badge's optical size. Text and
|
|
3947
|
-
|
|
4011
|
+
/* Decorative children follow the badge's optical size. Text and the .close
|
|
4012
|
+
button retain their own boxes. */
|
|
3948
4013
|
.badge > :where(svg, img, [aria-hidden="true"]:not(.dot)) {
|
|
3949
4014
|
flex: none;
|
|
3950
4015
|
/* The shared rule maps --badge-icon-size onto font-size. One em is therefore
|
|
@@ -3997,45 +4062,15 @@ a.btn[aria-disabled="true"] {
|
|
|
3997
4062
|
background-color: currentColor;
|
|
3998
4063
|
}
|
|
3999
4064
|
|
|
4000
|
-
.
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
keep the badge visually compact through the negative margins, but never
|
|
4006
|
-
shrink its action target below that literal floor. */
|
|
4007
|
-
min-inline-size: max(var(--badge-size), 24px);
|
|
4008
|
-
min-block-size: max(var(--badge-size), 24px);
|
|
4065
|
+
/* Removable tag — a .close (components/close.css) inside the pill. WCAG 2.5.8
|
|
4066
|
+
floors pointer targets at 24 CSS px: density and local size may keep the
|
|
4067
|
+
badge visually compact through the negative margins, but never shrink the
|
|
4068
|
+
target below that literal floor. */
|
|
4069
|
+
.badge > .close {
|
|
4009
4070
|
margin-block: -0.25rem;
|
|
4010
4071
|
margin-inline-start: -0.15em;
|
|
4011
4072
|
margin-inline-end: -0.45em;
|
|
4012
|
-
padding: 0.25rem;
|
|
4013
|
-
border: 0;
|
|
4014
4073
|
border-radius: var(--radius-full);
|
|
4015
|
-
background: transparent;
|
|
4016
|
-
color: inherit;
|
|
4017
|
-
font: inherit;
|
|
4018
|
-
line-height: 1;
|
|
4019
|
-
cursor: pointer;
|
|
4020
|
-
}
|
|
4021
|
-
|
|
4022
|
-
/* Dismiss mark — an empty dismiss button paints its own X from the shared
|
|
4023
|
-
--icon-close mask, so no icon font or text glyph is required in the markup
|
|
4024
|
-
(same contract as .alert-dismiss and .dialog-close). A mask box is a block
|
|
4025
|
-
box, so place-items centers the ink itself; a text glyph would only center
|
|
4026
|
-
its line box and land the ink low, because a lowercase x has no descender
|
|
4027
|
-
and only x-height of ascent. A button with its own content keeps it. */
|
|
4028
|
-
.badge > button:empty::before {
|
|
4029
|
-
content: "";
|
|
4030
|
-
inline-size: var(--badge-dismiss-icon-size);
|
|
4031
|
-
block-size: var(--badge-dismiss-icon-size);
|
|
4032
|
-
background: currentColor;
|
|
4033
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
4034
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
4035
|
-
}
|
|
4036
|
-
|
|
4037
|
-
.badge > button:not(:disabled):hover {
|
|
4038
|
-
background: var(--hover-overlay);
|
|
4039
4074
|
}
|
|
4040
4075
|
|
|
4041
4076
|
/* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
|
|
@@ -4126,7 +4161,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4126
4161
|
* --alert-font-size
|
|
4127
4162
|
* --alert-radius
|
|
4128
4163
|
* --alert-icon-size
|
|
4129
|
-
* --alert-dismiss-size
|
|
4130
4164
|
* --alert-border-inline-start-color
|
|
4131
4165
|
* --alert-border-inline-start-width
|
|
4132
4166
|
*
|
|
@@ -4137,7 +4171,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4137
4171
|
* --alert-bg
|
|
4138
4172
|
* --alert-fg
|
|
4139
4173
|
* --alert-border
|
|
4140
|
-
* --alert-dismiss-icon-size
|
|
4141
4174
|
*/
|
|
4142
4175
|
/* Intent boundary — inherited intents do not tint this component.
|
|
4143
4176
|
Local intent classes still win because :where() has zero specificity. */
|
|
@@ -4186,13 +4219,13 @@ a.badge[aria-disabled="true"] {
|
|
|
4186
4219
|
The slots are excluded because they are anatomy, not content: an
|
|
4187
4220
|
admonition's title bar has to stay flush against its body. The list follows
|
|
4188
4221
|
the anatomy, so it grows with it. */
|
|
4189
|
-
.alert > :not(:where(.alert-icon, .
|
|
4222
|
+
.alert > :not(:where(.alert-icon, .close, .alert-title, .alert-body)) {
|
|
4190
4223
|
margin-block: 0;
|
|
4191
4224
|
}
|
|
4192
4225
|
|
|
4193
4226
|
.alert
|
|
4194
|
-
> :not(:where(.alert-icon, .
|
|
4195
|
-
+ :not(:where(.alert-icon, .
|
|
4227
|
+
> :not(:where(.alert-icon, .close, .alert-title, .alert-body))
|
|
4228
|
+
+ :not(:where(.alert-icon, .close, .alert-title, .alert-body)) {
|
|
4196
4229
|
margin-block-start: var(--space-10);
|
|
4197
4230
|
}
|
|
4198
4231
|
|
|
@@ -4230,42 +4263,24 @@ a.badge[aria-disabled="true"] {
|
|
|
4230
4263
|
vertical-align: -0.125em;
|
|
4231
4264
|
}
|
|
4232
4265
|
|
|
4233
|
-
/*
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
--
|
|
4241
|
-
--alert-dismiss-icon-size: 0.875rem;
|
|
4242
|
-
|
|
4243
|
-
display: inline-grid;
|
|
4244
|
-
place-items: center;
|
|
4245
|
-
inline-size: var(--alert-dismiss-size);
|
|
4246
|
-
block-size: var(--alert-dismiss-size);
|
|
4247
|
-
flex: none;
|
|
4248
|
-
padding: 0;
|
|
4249
|
-
border: 0;
|
|
4250
|
-
border-radius: var(--radius);
|
|
4251
|
-
background: transparent;
|
|
4252
|
-
color: currentColor;
|
|
4253
|
-
opacity: 0.65;
|
|
4254
|
-
cursor: pointer;
|
|
4266
|
+
/* Close (components/close.css) — compact, and at reduced opacity so it stays
|
|
4267
|
+
secondary inside an already-colored alert. Standard alerts place it as a
|
|
4268
|
+
direct trailing child; admonitions place it inside .alert-title (see
|
|
4269
|
+
below). The sizes are declared on the alert so an author can still set
|
|
4270
|
+
them on the alert or on the button. */
|
|
4271
|
+
.alert {
|
|
4272
|
+
--close-size: 1.75rem;
|
|
4273
|
+
--close-icon-size: 0.875rem;
|
|
4255
4274
|
}
|
|
4256
4275
|
|
|
4257
|
-
.alert
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
block-size: var(--alert-dismiss-icon-size);
|
|
4261
|
-
background: currentColor;
|
|
4262
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
4263
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
4276
|
+
.alert > .close,
|
|
4277
|
+
.alert > .alert-title > .close {
|
|
4278
|
+
opacity: 0.65;
|
|
4264
4279
|
}
|
|
4265
4280
|
|
|
4266
4281
|
@media (hover: hover) {
|
|
4267
|
-
.alert
|
|
4268
|
-
|
|
4282
|
+
.alert > .close:hover,
|
|
4283
|
+
.alert > .alert-title > .close:hover {
|
|
4269
4284
|
opacity: 1;
|
|
4270
4285
|
}
|
|
4271
4286
|
}
|
|
@@ -4274,7 +4289,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4274
4289
|
both. Icon and dismiss are pinned to the outer columns; anything else (the
|
|
4275
4290
|
message content) flows into the remaining text column. */
|
|
4276
4291
|
@supports selector(.alert:has(> *)) {
|
|
4277
|
-
.alert:has(> .alert-icon, > .
|
|
4292
|
+
.alert:has(> .alert-icon, > .close) {
|
|
4278
4293
|
display: grid;
|
|
4279
4294
|
grid-template-columns: minmax(0, 1fr);
|
|
4280
4295
|
align-items: var(--items-align, start);
|
|
@@ -4293,19 +4308,19 @@ a.badge[aria-disabled="true"] {
|
|
|
4293
4308
|
margin-block-start: 0.125em;
|
|
4294
4309
|
}
|
|
4295
4310
|
|
|
4296
|
-
.alert:has(> .alert-icon) > :not(.alert-icon, .
|
|
4311
|
+
.alert:has(> .alert-icon) > :not(.alert-icon, .close) {
|
|
4297
4312
|
grid-column: 2;
|
|
4298
4313
|
}
|
|
4299
4314
|
|
|
4300
|
-
.alert:has(> .
|
|
4315
|
+
.alert:has(> .close) {
|
|
4301
4316
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
4302
4317
|
}
|
|
4303
4318
|
|
|
4304
|
-
.alert:has(> .alert-icon):has(> .
|
|
4319
|
+
.alert:has(> .alert-icon):has(> .close) {
|
|
4305
4320
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
4306
4321
|
}
|
|
4307
4322
|
|
|
4308
|
-
.alert:has(> .
|
|
4323
|
+
.alert:has(> .close) > .close {
|
|
4309
4324
|
grid-column: -2 / -1;
|
|
4310
4325
|
grid-row: 1;
|
|
4311
4326
|
}
|
|
@@ -4353,8 +4368,8 @@ a.badge[aria-disabled="true"] {
|
|
|
4353
4368
|
border-start-end-radius: calc(var(--alert-radius, var(--radius-lg)) - var(--border-width));
|
|
4354
4369
|
}
|
|
4355
4370
|
|
|
4356
|
-
/*
|
|
4357
|
-
.alert.admonition > .alert-title > .
|
|
4371
|
+
/* Close inside an admonition title bar rides the inline end. */
|
|
4372
|
+
.alert.admonition > .alert-title > .close {
|
|
4358
4373
|
margin-inline-start: auto;
|
|
4359
4374
|
}
|
|
4360
4375
|
|
|
@@ -4447,7 +4462,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4447
4462
|
*
|
|
4448
4463
|
* Child contract:
|
|
4449
4464
|
* Direct .bleed children escape --card-pad (negative inset, wider media);
|
|
4450
|
-
* edge children inherit the card corners. .bleed must be a direct child
|
|
4465
|
+
* edge children inherit the card corners. .bleed must be a direct child, and
|
|
4466
|
+
* its rules live in components/bleed.css — the card relays --card-pad to the
|
|
4467
|
+
* shared --surface-pad that they read.
|
|
4451
4468
|
*
|
|
4452
4469
|
* Internal:
|
|
4453
4470
|
* --card-bg
|
|
@@ -4478,6 +4495,10 @@ a.badge[aria-disabled="true"] {
|
|
|
4478
4495
|
--card-bg: var(--ui-bg, var(--surface-raised));
|
|
4479
4496
|
--card-fg: var(--ui-fg, var(--text));
|
|
4480
4497
|
|
|
4498
|
+
/* The inset a direct .bleed child cancels (components/bleed.css). Relayed
|
|
4499
|
+
from --card-pad so .compact and an author override both reach it. */
|
|
4500
|
+
--surface-pad: var(--card-pad);
|
|
4501
|
+
|
|
4481
4502
|
/* Keep contextual descendants tied to the surface this card actually owns. */
|
|
4482
4503
|
--heading: var(--card-fg);
|
|
4483
4504
|
--busy-overlay-bg: var(--card-bg);
|
|
@@ -4543,45 +4564,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4543
4564
|
--card-pad: var(--space-20);
|
|
4544
4565
|
}
|
|
4545
4566
|
|
|
4546
|
-
/* Full-bleed children */
|
|
4547
|
-
.card > .bleed {
|
|
4548
|
-
margin-inline: calc(var(--card-pad) * -1);
|
|
4549
|
-
max-inline-size: none;
|
|
4550
|
-
}
|
|
4551
|
-
|
|
4552
|
-
.card > .bleed:first-child {
|
|
4553
|
-
margin-block-start: calc(var(--card-pad) * -1);
|
|
4554
|
-
border-start-start-radius: inherit;
|
|
4555
|
-
border-start-end-radius: inherit;
|
|
4556
|
-
}
|
|
4557
|
-
|
|
4558
|
-
.card > .bleed:last-child {
|
|
4559
|
-
margin-block-end: calc(var(--card-pad) * -1);
|
|
4560
|
-
border-end-start-radius: inherit;
|
|
4561
|
-
border-end-end-radius: inherit;
|
|
4562
|
-
}
|
|
4563
|
-
|
|
4564
|
-
/* Media bleed */
|
|
4565
|
-
.card > :where(img, picture, video, canvas, svg).bleed {
|
|
4566
|
-
display: block;
|
|
4567
|
-
inline-size: calc(100% + var(--card-pad) * 2);
|
|
4568
|
-
max-inline-size: none;
|
|
4569
|
-
}
|
|
4570
|
-
|
|
4571
|
-
.card > picture.bleed {
|
|
4572
|
-
overflow: hidden;
|
|
4573
|
-
}
|
|
4574
|
-
|
|
4575
|
-
.card > picture.bleed > img {
|
|
4576
|
-
display: block;
|
|
4577
|
-
inline-size: 100%;
|
|
4578
|
-
block-size: auto;
|
|
4579
|
-
}
|
|
4580
|
-
|
|
4581
|
-
/* Padded structural bleed */
|
|
4582
|
-
.card > :where(header, footer, figure).bleed {
|
|
4583
|
-
padding: var(--card-pad);
|
|
4584
|
-
}
|
|
4585
4567
|
@media print {
|
|
4586
4568
|
.card {
|
|
4587
4569
|
border: var(--border-width) solid CanvasText;
|
|
@@ -4603,8 +4585,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4603
4585
|
* --dialog-icon-size
|
|
4604
4586
|
* --dialog-icon-glyph-size
|
|
4605
4587
|
*
|
|
4588
|
+
* The corner close control is .close (components/close.css).
|
|
4589
|
+
*
|
|
4606
4590
|
* Internal:
|
|
4607
|
-
* --dialog-close-icon-size
|
|
4608
4591
|
* --dialog-focus-bleed
|
|
4609
4592
|
* --dialog-viewport-block
|
|
4610
4593
|
* --dialog-viewport-gap-default
|
|
@@ -4690,11 +4673,16 @@ a.badge[aria-disabled="true"] {
|
|
|
4690
4673
|
|
|
4691
4674
|
:where(dialog).modal.scrollable > form > :not(:is(header, footer)),
|
|
4692
4675
|
:where(dialog).modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
4693
|
-
--dialog-focus-bleed: calc(
|
|
4676
|
+
--dialog-focus-bleed: calc(
|
|
4677
|
+
var(--focus-outline-offset) +
|
|
4678
|
+
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
4679
|
+
);
|
|
4694
4680
|
|
|
4695
4681
|
min-block-size: 0;
|
|
4696
4682
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
4697
|
-
header and footer edges.
|
|
4683
|
+
header and footer edges. The reserve covers the thicker of the two
|
|
4684
|
+
outside indicators: the :focus baseline (2 × border) and the component
|
|
4685
|
+
line (--focus-ring-width, 3px in bootstrap-v6). */
|
|
4698
4686
|
margin-inline: calc(var(--dialog-focus-bleed) * -1);
|
|
4699
4687
|
padding-inline: var(--dialog-focus-bleed);
|
|
4700
4688
|
overflow: auto;
|
|
@@ -4717,7 +4705,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4717
4705
|
The form/.stack anatomy is structural, so it stays in :where() and never
|
|
4718
4706
|
lends .stack's specificity to a bare <form>. */
|
|
4719
4707
|
:where(dialog).modal > :where(form, .stack) > header {
|
|
4720
|
-
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
4708
|
+
padding-inline-end: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
4721
4709
|
}
|
|
4722
4710
|
|
|
4723
4711
|
:where(dialog).modal > :where(form, .stack) > footer {
|
|
@@ -4881,59 +4869,6 @@ html.has-modal-open.had-scrollbar {
|
|
|
4881
4869
|
}
|
|
4882
4870
|
}
|
|
4883
4871
|
|
|
4884
|
-
/* Close control. A close button is an action attached to a surface, so it
|
|
4885
|
-
never participates in the content flow — absolute at the panel's top
|
|
4886
|
-
inline-end, inside the surface. The glyph comes from --icon-close, masked to
|
|
4887
|
-
currentColor, so no icon font or text glyph is required in the markup. */
|
|
4888
|
-
.dialog-close {
|
|
4889
|
-
--dialog-close-icon-size: 1rem;
|
|
4890
|
-
|
|
4891
|
-
display: inline-grid;
|
|
4892
|
-
place-items: center;
|
|
4893
|
-
padding: 0;
|
|
4894
|
-
border: 0;
|
|
4895
|
-
border-radius: var(--radius);
|
|
4896
|
-
color: var(--text-muted);
|
|
4897
|
-
cursor: pointer;
|
|
4898
|
-
|
|
4899
|
-
/* Out of flow, anchored to the positioned dialog surface, not the header or
|
|
4900
|
-
any scroll container inside it. */
|
|
4901
|
-
position: absolute;
|
|
4902
|
-
z-index: 1;
|
|
4903
|
-
inset-block-start: var(--space-20);
|
|
4904
|
-
inset-inline-end: var(--space-20);
|
|
4905
|
-
|
|
4906
|
-
inline-size: var(--control-size);
|
|
4907
|
-
block-size: var(--control-size);
|
|
4908
|
-
|
|
4909
|
-
background: var(--surface-subtle);
|
|
4910
|
-
}
|
|
4911
|
-
|
|
4912
|
-
.dialog-close::before {
|
|
4913
|
-
content: "";
|
|
4914
|
-
inline-size: var(--dialog-close-icon-size);
|
|
4915
|
-
block-size: var(--dialog-close-icon-size);
|
|
4916
|
-
background: currentColor;
|
|
4917
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
4918
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
4919
|
-
}
|
|
4920
|
-
|
|
4921
|
-
@media (hover: hover) {
|
|
4922
|
-
.dialog-close:hover {
|
|
4923
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4924
|
-
color: var(--text);
|
|
4925
|
-
}
|
|
4926
|
-
}
|
|
4927
|
-
|
|
4928
|
-
.dialog-close:active {
|
|
4929
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4930
|
-
}
|
|
4931
|
-
|
|
4932
|
-
.dialog-close:focus-visible {
|
|
4933
|
-
outline: 2px solid transparent;
|
|
4934
|
-
box-shadow: var(--focus-ring-shadow);
|
|
4935
|
-
}
|
|
4936
|
-
|
|
4937
4872
|
/*
|
|
4938
4873
|
* Drawer — modal side-sheet built on the native dialog element.
|
|
4939
4874
|
*
|
|
@@ -4953,6 +4888,8 @@ html.has-modal-open.had-scrollbar {
|
|
|
4953
4888
|
* --drawer-size
|
|
4954
4889
|
* --drawer-pad
|
|
4955
4890
|
*
|
|
4891
|
+
* The corner close control is .close (components/close.css).
|
|
4892
|
+
*
|
|
4956
4893
|
* Internal:
|
|
4957
4894
|
* --drawer-closed-translate (side/RTL positioning state)
|
|
4958
4895
|
* --drawer-focus-bleed
|
|
@@ -4962,6 +4899,8 @@ html.has-modal-open.had-scrollbar {
|
|
|
4962
4899
|
:where(dialog).drawer {
|
|
4963
4900
|
--drawer-size: 20rem;
|
|
4964
4901
|
--drawer-pad: var(--space-40);
|
|
4902
|
+
/* The inset a direct .bleed child cancels (components/bleed.css). */
|
|
4903
|
+
--surface-pad: var(--drawer-pad);
|
|
4965
4904
|
--drawer-closed-translate: -100%;
|
|
4966
4905
|
/* Exit is shorter than entry so the panel clears quickly; the scrim reads
|
|
4967
4906
|
the same variable through inheritance and never lags the panel. */
|
|
@@ -5061,15 +5000,20 @@ html.has-modal-open.had-scrollbar {
|
|
|
5061
5000
|
/* The body region absorbs the panel's height and scrolls. A drawer is a
|
|
5062
5001
|
full-height shell by definition, so it always scrolls its body — unlike the
|
|
5063
5002
|
modal, which sizes to its content and opts in with .scrollable. Header and
|
|
5064
|
-
footer keep their own tracks; .
|
|
5003
|
+
footer keep their own tracks; .close is named because a generic
|
|
5065
5004
|
exclusion list is easier to trust than its `position: absolute`. */
|
|
5066
|
-
:where(dialog).drawer > :not(:is(header, footer, form, .
|
|
5067
|
-
:where(dialog).drawer > form > :not(:is(header, footer, .
|
|
5068
|
-
--drawer-focus-bleed: calc(
|
|
5005
|
+
:where(dialog).drawer > :not(:is(header, footer, form, .close)),
|
|
5006
|
+
:where(dialog).drawer > form > :not(:is(header, footer, .close)) {
|
|
5007
|
+
--drawer-focus-bleed: calc(
|
|
5008
|
+
var(--focus-outline-offset) +
|
|
5009
|
+
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
5010
|
+
);
|
|
5069
5011
|
|
|
5070
5012
|
min-block-size: 0;
|
|
5071
5013
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
5072
|
-
header and footer edges.
|
|
5014
|
+
header and footer edges. The reserve covers the thicker of the two
|
|
5015
|
+
outside indicators: the :focus baseline (2 × border) and the component
|
|
5016
|
+
line (--focus-ring-width, 3px in bootstrap-v6). */
|
|
5073
5017
|
margin-inline: calc(var(--drawer-focus-bleed) * -1);
|
|
5074
5018
|
padding-inline: var(--drawer-focus-bleed);
|
|
5075
5019
|
overflow: auto;
|
|
@@ -5141,57 +5085,87 @@ html.has-modal-open.had-scrollbar {
|
|
|
5141
5085
|
control. */
|
|
5142
5086
|
:where(dialog).drawer > header,
|
|
5143
5087
|
:where(dialog).drawer > form > header {
|
|
5144
|
-
padding-inline-end: calc(var(--control-size) + var(--space-30));
|
|
5088
|
+
padding-inline-end: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
5145
5089
|
}
|
|
5146
5090
|
|
|
5147
|
-
/*
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
.
|
|
5151
|
-
|
|
5091
|
+
/*
|
|
5092
|
+
* Full-bleed children — the shared escape hatch for a padded surface.
|
|
5093
|
+
*
|
|
5094
|
+
* Docs: docs/pages/components/bleed.md
|
|
5095
|
+
*
|
|
5096
|
+
* A surface that insets its direct children relays that inset to them as
|
|
5097
|
+
* --surface-pad; a direct .bleed child cancels it and reaches the surface
|
|
5098
|
+
* edge. Card and drawer pad at the container level, so they own the token
|
|
5099
|
+
* there; the accordion pads its summary and its panel instead, so the panel
|
|
5100
|
+
* owns it and the item does not.
|
|
5101
|
+
*
|
|
5102
|
+
* Why the token is handed to the children rather than declared on the surface:
|
|
5103
|
+
* custom properties inherit, so a surface-level declaration would also reach a
|
|
5104
|
+
* grandchild, which sits at its wrapper's edge rather than the surface's and
|
|
5105
|
+
* would pull a negative margin out of the wrong box. Relaying it one level and
|
|
5106
|
+
* clearing it below keeps the child combinator's guarantee while leaving the
|
|
5107
|
+
* .bleed rules themselves free of the surface list.
|
|
5108
|
+
*
|
|
5109
|
+
* Why the clearing rule comes first: both rules are zero-specificity, so
|
|
5110
|
+
* source order decides where they overlap, and they overlap on exactly one
|
|
5111
|
+
* element — a direct child of a surface nested inside another surface. That
|
|
5112
|
+
* child must keep the inner surface's inset, so the relay has to win.
|
|
5113
|
+
*
|
|
5114
|
+
* Public hooks:
|
|
5115
|
+
* --bleed-pad (read-only: the inset relayed to a surface's direct children;
|
|
5116
|
+
* pad a band that is not a header/footer/figure with it)
|
|
5117
|
+
*
|
|
5118
|
+
* Framework plumbing:
|
|
5119
|
+
* --surface-pad (declared by the surface)
|
|
5120
|
+
*/
|
|
5121
|
+
:where(.card, .drawer, .accordion details > summary + *) > * * {
|
|
5122
|
+
--bleed-pad: initial;
|
|
5123
|
+
}
|
|
5152
5124
|
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
border: 0;
|
|
5157
|
-
border-radius: var(--radius);
|
|
5158
|
-
color: var(--text-muted);
|
|
5159
|
-
cursor: pointer;
|
|
5125
|
+
:where(.card, .drawer, .accordion details > summary + *) > * {
|
|
5126
|
+
--bleed-pad: var(--surface-pad);
|
|
5127
|
+
}
|
|
5160
5128
|
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5129
|
+
/* Inert without a surface: the fallback resolves the negative margin to zero
|
|
5130
|
+
rather than dropping the declaration and inheriting a stray inset. */
|
|
5131
|
+
.bleed {
|
|
5132
|
+
margin-inline: calc(var(--bleed-pad, 0px) * -1);
|
|
5133
|
+
max-inline-size: none;
|
|
5134
|
+
}
|
|
5165
5135
|
|
|
5166
|
-
|
|
5167
|
-
block-
|
|
5136
|
+
.bleed:first-child {
|
|
5137
|
+
margin-block-start: calc(var(--bleed-pad, 0px) * -1);
|
|
5138
|
+
border-start-start-radius: inherit;
|
|
5139
|
+
border-start-end-radius: inherit;
|
|
5140
|
+
}
|
|
5168
5141
|
|
|
5169
|
-
|
|
5142
|
+
.bleed:last-child {
|
|
5143
|
+
margin-block-end: calc(var(--bleed-pad, 0px) * -1);
|
|
5144
|
+
border-end-start-radius: inherit;
|
|
5145
|
+
border-end-end-radius: inherit;
|
|
5170
5146
|
}
|
|
5171
5147
|
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
-webkit-mask: var(--icon-close) center / contain no-repeat;
|
|
5178
|
-
mask: var(--icon-close) center / contain no-repeat;
|
|
5148
|
+
/* Media bleed */
|
|
5149
|
+
:where(img, picture, video, canvas, svg).bleed {
|
|
5150
|
+
display: block;
|
|
5151
|
+
inline-size: calc(100% + var(--bleed-pad, 0px) * 2);
|
|
5152
|
+
max-inline-size: none;
|
|
5179
5153
|
}
|
|
5180
5154
|
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
5184
|
-
color: var(--text);
|
|
5185
|
-
}
|
|
5155
|
+
picture.bleed {
|
|
5156
|
+
overflow: hidden;
|
|
5186
5157
|
}
|
|
5187
5158
|
|
|
5188
|
-
.
|
|
5189
|
-
|
|
5159
|
+
picture.bleed > img {
|
|
5160
|
+
display: block;
|
|
5161
|
+
inline-size: 100%;
|
|
5162
|
+
block-size: auto;
|
|
5190
5163
|
}
|
|
5191
5164
|
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5165
|
+
/* Padded structural bleed — the band keeps the surface's own rhythm while its
|
|
5166
|
+
background reaches the edge. */
|
|
5167
|
+
:where(header, footer, figure).bleed {
|
|
5168
|
+
padding: var(--bleed-pad, 0px);
|
|
5195
5169
|
}
|
|
5196
5170
|
|
|
5197
5171
|
|
|
@@ -5268,6 +5242,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
5268
5242
|
border: var(--border-width) solid var(--border);
|
|
5269
5243
|
border-radius: var(--radius);
|
|
5270
5244
|
background: var(--surface-raised);
|
|
5245
|
+
color: var(--text);
|
|
5271
5246
|
box-shadow: var(--shadow-popout);
|
|
5272
5247
|
list-style: none;
|
|
5273
5248
|
opacity: 1;
|
|
@@ -5500,6 +5475,18 @@ html.has-modal-open.had-scrollbar {
|
|
|
5500
5475
|
border-block-start: var(--border-width) solid var(--border);
|
|
5501
5476
|
}
|
|
5502
5477
|
|
|
5478
|
+
@media (forced-colors: active) {
|
|
5479
|
+
/* The check mark is a currentColor mask fill and the mixed/radio marks are
|
|
5480
|
+
currentColor gradients: without this the system remaps them to Canvas or
|
|
5481
|
+
drops the image and the state disappears. Kept on the pseudo so the host
|
|
5482
|
+
keeps its native color and focus adaptation. */
|
|
5483
|
+
.menu-item[role="menuitemcheckbox"][aria-checked="true"]::before,
|
|
5484
|
+
.menu-item[role="menuitemcheckbox"][aria-checked="mixed"]::before,
|
|
5485
|
+
.menu-item[role="menuitemradio"]::before {
|
|
5486
|
+
forced-color-adjust: none;
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5503
5490
|
/*
|
|
5504
5491
|
* Tooltip — supplemental label overlaid near a trigger.
|
|
5505
5492
|
*
|
|
@@ -5661,11 +5648,15 @@ html.has-modal-open.had-scrollbar {
|
|
|
5661
5648
|
*
|
|
5662
5649
|
* Public hooks:
|
|
5663
5650
|
* --accordion-radius
|
|
5651
|
+
* --accordion-pad
|
|
5664
5652
|
* --accordion-marker-color
|
|
5653
|
+
* --accordion-marker-size
|
|
5665
5654
|
*/
|
|
5666
5655
|
.accordion {
|
|
5667
5656
|
--accordion-radius: var(--radius);
|
|
5657
|
+
--accordion-pad: var(--space-40);
|
|
5668
5658
|
--accordion-marker-color: var(--text-muted);
|
|
5659
|
+
--accordion-marker-size: 1rem;
|
|
5669
5660
|
|
|
5670
5661
|
border: var(--border-width) solid var(--border);
|
|
5671
5662
|
border-radius: var(--accordion-radius);
|
|
@@ -5682,7 +5673,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
5682
5673
|
gap: var(--space-30);
|
|
5683
5674
|
cursor: pointer;
|
|
5684
5675
|
list-style: none;
|
|
5685
|
-
padding: var(--
|
|
5676
|
+
padding: var(--accordion-pad);
|
|
5686
5677
|
color: var(--text);
|
|
5687
5678
|
font-weight: var(--font-weight-medium);
|
|
5688
5679
|
}
|
|
@@ -5698,12 +5689,17 @@ html.has-modal-open.had-scrollbar {
|
|
|
5698
5689
|
display: none;
|
|
5699
5690
|
}
|
|
5700
5691
|
|
|
5692
|
+
/* The marker is the chevron itself: the box is filled with
|
|
5693
|
+
--accordion-marker-color and then clipped to the glyph by the mask. Anything
|
|
5694
|
+
painted on this pseudo-element — a background, a ring, a shadow — is clipped
|
|
5695
|
+
with it, so a decorated marker (a chevron in a pill) needs a real element;
|
|
5696
|
+
see the accordion docs for the opt-out. */
|
|
5701
5697
|
.accordion summary::after {
|
|
5702
5698
|
content: "";
|
|
5703
5699
|
flex: 0 0 auto;
|
|
5704
5700
|
margin-inline-start: auto;
|
|
5705
|
-
inline-size:
|
|
5706
|
-
block-size:
|
|
5701
|
+
inline-size: var(--accordion-marker-size);
|
|
5702
|
+
block-size: var(--accordion-marker-size);
|
|
5707
5703
|
background-color: var(--accordion-marker-color);
|
|
5708
5704
|
-webkit-mask: var(--icon-chevron) center / contain no-repeat;
|
|
5709
5705
|
mask: var(--icon-chevron) center / contain no-repeat;
|
|
@@ -5714,9 +5710,14 @@ html.has-modal-open.had-scrollbar {
|
|
|
5714
5710
|
transform: rotate(180deg);
|
|
5715
5711
|
}
|
|
5716
5712
|
|
|
5713
|
+
/* The panel, not the item, owns the inset here — .separated paints a card on
|
|
5714
|
+
<details> but leaves it unpadded — so the panel is what a .bleed child
|
|
5715
|
+
escapes (components/bleed.css). */
|
|
5717
5716
|
.accordion details > summary + * {
|
|
5718
|
-
|
|
5719
|
-
|
|
5717
|
+
--surface-pad: var(--accordion-pad);
|
|
5718
|
+
|
|
5719
|
+
padding-block: 0 var(--accordion-pad);
|
|
5720
|
+
padding-inline: var(--accordion-pad);
|
|
5720
5721
|
color: var(--text-muted);
|
|
5721
5722
|
margin-block: 0;
|
|
5722
5723
|
}
|
|
@@ -5725,6 +5726,9 @@ html.has-modal-open.had-scrollbar {
|
|
|
5725
5726
|
padding-block-start: var(--space-10);
|
|
5726
5727
|
}
|
|
5727
5728
|
|
|
5729
|
+
/* Also cancels the lift of a first .bleed child (bleed.css, lower
|
|
5730
|
+
specificity): the panel's top edge is the summary, not the item edge, so a
|
|
5731
|
+
bleed reaches only the sides and the bottom. */
|
|
5728
5732
|
.accordion details > summary + * > :first-child {
|
|
5729
5733
|
margin-block-start: 0;
|
|
5730
5734
|
}
|
|
@@ -5749,6 +5753,12 @@ html.has-modal-open.had-scrollbar {
|
|
|
5749
5753
|
padding-inline: 0;
|
|
5750
5754
|
}
|
|
5751
5755
|
|
|
5756
|
+
/* Flush already sits at the embedding surface's edge, so there is no inline
|
|
5757
|
+
inset left for a .bleed child to cancel. */
|
|
5758
|
+
.accordion.flush details > summary + * {
|
|
5759
|
+
--surface-pad: 0px;
|
|
5760
|
+
}
|
|
5761
|
+
|
|
5752
5762
|
/* Separated — each item owns a full card. The item's own border beats the
|
|
5753
5763
|
shared details + details separator on specificity alone, so nothing needs
|
|
5754
5764
|
undoing for the second item onwards. */
|
|
@@ -5765,9 +5775,23 @@ html.has-modal-open.had-scrollbar {
|
|
|
5765
5775
|
background: var(--surface-raised);
|
|
5766
5776
|
}
|
|
5767
5777
|
|
|
5778
|
+
/* A last .bleed child inherits its bottom corners from the panel, but the
|
|
5779
|
+
rounded box is the item (.separated) or the group (last item) — relay the
|
|
5780
|
+
radius to the panel. Explicit rather than inherit: in browsers with
|
|
5781
|
+
::details-content the panel's flat-tree parent is that slot, not
|
|
5782
|
+
<details>. */
|
|
5783
|
+
.accordion.separated > details > summary + *,
|
|
5784
|
+
.accordion:not(.flush) > details:last-child > summary + * {
|
|
5785
|
+
border-end-start-radius: var(--accordion-radius);
|
|
5786
|
+
border-end-end-radius: var(--accordion-radius);
|
|
5787
|
+
}
|
|
5788
|
+
|
|
5768
5789
|
@media (forced-colors: active) {
|
|
5769
|
-
|
|
5770
|
-
|
|
5790
|
+
/* Written on the property, not the hook: an application hook override at
|
|
5791
|
+
the same specificity loads after this file and would win the variable,
|
|
5792
|
+
leaving a Canvas-remapped fill. The property keeps CanvasText. */
|
|
5793
|
+
.accordion summary::after {
|
|
5794
|
+
background-color: CanvasText;
|
|
5771
5795
|
}
|
|
5772
5796
|
}
|
|
5773
5797
|
|
|
@@ -6762,6 +6786,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
6762
6786
|
transform: none;
|
|
6763
6787
|
}
|
|
6764
6788
|
|
|
6789
|
+
@media (forced-colors: active) {
|
|
6790
|
+
/* Both toggle markers are currentColor mask fills: without this the system
|
|
6791
|
+
remaps the backgrounds to Canvas and the glyphs disappear. Kept on the
|
|
6792
|
+
pseudos so the host keeps its native color and focus adaptation. */
|
|
6793
|
+
.fab > summary::before,
|
|
6794
|
+
.fab > summary::after {
|
|
6795
|
+
forced-color-adjust: none;
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
|
|
6765
6799
|
/*
|
|
6766
6800
|
* Rating — integer star rating drawn on native radio inputs.
|
|
6767
6801
|
*
|
|
@@ -7286,6 +7320,15 @@ a.avatar[aria-disabled="true"] {
|
|
|
7286
7320
|
color: var(--text);
|
|
7287
7321
|
}
|
|
7288
7322
|
|
|
7323
|
+
/* Same contextual hover as .nav-link (navbar.css): a tint of the tile's ink
|
|
7324
|
+
reads on any --ui-* bar; the baseline keeps a legible light chip. */
|
|
7325
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
7326
|
+
.app-nav > a:hover {
|
|
7327
|
+
background: color-mix(in oklch, currentColor 10%, transparent);
|
|
7328
|
+
color: var(--ui-fg, var(--text));
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
|
|
7289
7332
|
/* Current tile is already a full selected surface. Selection never changes
|
|
7290
7333
|
text metrics: the shared medium weight is kept, no permanent indicator is
|
|
7291
7334
|
added here. */
|
|
@@ -7373,11 +7416,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
7373
7416
|
display: inline-flex;
|
|
7374
7417
|
align-items: center;
|
|
7375
7418
|
min-block-size: var(--control-size-sm);
|
|
7419
|
+
padding-block: 0;
|
|
7376
7420
|
padding-inline: var(--space-20);
|
|
7421
|
+
border: 0;
|
|
7377
7422
|
border-radius: var(--radius);
|
|
7423
|
+
background: transparent;
|
|
7378
7424
|
color: var(--ui-fg, var(--text-muted));
|
|
7425
|
+
font: inherit;
|
|
7379
7426
|
font-weight: var(--font-weight-medium);
|
|
7380
7427
|
text-decoration: none;
|
|
7428
|
+
cursor: pointer;
|
|
7381
7429
|
}
|
|
7382
7430
|
|
|
7383
7431
|
/* nav-link serves two contracts: plain page navigation (aria-current="page")
|
|
@@ -7402,6 +7450,28 @@ a.avatar[aria-disabled="true"] {
|
|
|
7402
7450
|
color: var(--state-selected);
|
|
7403
7451
|
}
|
|
7404
7452
|
|
|
7453
|
+
/* On a bar with its own --ui-bg/--ui-fg the fill is a tint of the link's own
|
|
7454
|
+
ink, the .btn.ghost hover recipe, so it reads on light and dark bars alike
|
|
7455
|
+
instead of snapping back to --surface-subtle. Not --ui-hover-bg: that is the
|
|
7456
|
+
bar's own hover and can equal its fill. The ink follows --ui-fg too — the
|
|
7457
|
+
selected accent has no contrast pair with an arbitrary bar. The baseline
|
|
7458
|
+
above keeps the light chip where color-mix is missing, with ink that is
|
|
7459
|
+
legible on it. */
|
|
7460
|
+
@supports (color: color-mix(in oklch, red, white)) {
|
|
7461
|
+
.nav-link:is(:hover, [aria-current]) {
|
|
7462
|
+
background: color-mix(in oklch, currentColor 10%, transparent);
|
|
7463
|
+
}
|
|
7464
|
+
|
|
7465
|
+
.nav-link:hover {
|
|
7466
|
+
color: var(--ui-fg, var(--text));
|
|
7467
|
+
}
|
|
7468
|
+
|
|
7469
|
+
.nav-link[aria-current],
|
|
7470
|
+
.nav-link[aria-current]:hover {
|
|
7471
|
+
color: var(--ui-fg, var(--state-selected));
|
|
7472
|
+
}
|
|
7473
|
+
}
|
|
7474
|
+
|
|
7405
7475
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
7406
7476
|
Links fill each row so the whole line is an interactive target; the
|
|
7407
7477
|
horizontal .navbar-nav links keep their intrinsic size. */
|
|
@@ -7697,6 +7767,9 @@ a.avatar[aria-disabled="true"] {
|
|
|
7697
7767
|
* A plain <dl> handles richer relationships (several terms or descriptions)
|
|
7698
7768
|
* and stays untouched by this component.
|
|
7699
7769
|
*
|
|
7770
|
+
* Variants:
|
|
7771
|
+
* .stacked — value under its term rather than beside it.
|
|
7772
|
+
*
|
|
7700
7773
|
* Why margin resets: neutralizes .prose list margins if .data-list appears
|
|
7701
7774
|
* inside an authored rich-text scope; the grid owns row/column rhythm.
|
|
7702
7775
|
*/
|
|
@@ -7718,6 +7791,21 @@ a.avatar[aria-disabled="true"] {
|
|
|
7718
7791
|
margin: 0;
|
|
7719
7792
|
}
|
|
7720
7793
|
|
|
7794
|
+
/* Stacked — the value sits under its term instead of beside it, for narrow
|
|
7795
|
+
columns and for values too long to sit in a second column.
|
|
7796
|
+
Why the pair separation rides on the term's margin: the children alternate
|
|
7797
|
+
dt/dd with no element around a pair, so the grid has no row boundary to give
|
|
7798
|
+
a larger gap to. The tight gap is the row gap; the term reopens the space. */
|
|
7799
|
+
.data-list.stacked {
|
|
7800
|
+
grid-template-columns: minmax(0, 1fr);
|
|
7801
|
+
gap: var(--space-10);
|
|
7802
|
+
}
|
|
7803
|
+
|
|
7804
|
+
.data-list.stacked > dt + dt,
|
|
7805
|
+
.data-list.stacked > dd + dt {
|
|
7806
|
+
margin-block-start: var(--space-30);
|
|
7807
|
+
}
|
|
7808
|
+
|
|
7721
7809
|
/*
|
|
7722
7810
|
* Application list — repeated rows with optional leading and trailing regions.
|
|
7723
7811
|
* Content determines the number of lines; the component only owns the row
|
|
@@ -8071,6 +8159,11 @@ a.list-item[aria-current]::before {
|
|
|
8071
8159
|
min-block-size: var(--control-size);
|
|
8072
8160
|
padding-inline: var(--space-30);
|
|
8073
8161
|
border: var(--border-width) solid var(--control-border);
|
|
8162
|
+
/* An addon is a field segment: it honours the same lower-edge extension
|
|
8163
|
+
points as text controls (control.css), so a theme's underline or accent
|
|
8164
|
+
edge reaches it instead of stopping at the field. */
|
|
8165
|
+
border-block-end-color: var(--control-border-block-end-color, var(--control-border));
|
|
8166
|
+
border-block-end-width: var(--control-border-block-end-width, var(--border-width));
|
|
8074
8167
|
border-radius: var(--radius);
|
|
8075
8168
|
background: var(--control-bg);
|
|
8076
8169
|
color: var(--control-fg);
|
|
@@ -8118,38 +8211,49 @@ a.list-item[aria-current]::before {
|
|
|
8118
8211
|
z-index: 1;
|
|
8119
8212
|
}
|
|
8120
8213
|
|
|
8121
|
-
/* Keep an active focus ring above an adjacent hovered control. This matters
|
|
8122
|
-
on platforms where clicking a button does not move focus from the input. */
|
|
8123
|
-
.join > :is(:focus, :focus-within) {
|
|
8124
|
-
z-index: 4;
|
|
8125
|
-
}
|
|
8126
|
-
|
|
8127
8214
|
/* An input's border must read as the "primary" edge against an attached
|
|
8128
8215
|
action (e.g. a password-reveal button) at all times, not only while
|
|
8129
8216
|
hovered or focused — otherwise the shared edge flips ownership as the
|
|
8130
|
-
user's pointer moves, which reads as a rendering glitch.
|
|
8131
|
-
|
|
8217
|
+
user's pointer moves, which reads as a rendering glitch. The :has()
|
|
8218
|
+
variant covers a control nested one wrapper level deep (e.g. a combobox
|
|
8219
|
+
element): the wrapper is the flex item, so it carries the z-index — a
|
|
8220
|
+
z-index on the inner control would be a no-op on a static grandchild. */
|
|
8221
|
+
.join > :is(.input, .textarea, .select),
|
|
8222
|
+
.join > :has(> :is(.input, .textarea, .select)) {
|
|
8132
8223
|
z-index: 3;
|
|
8133
8224
|
}
|
|
8134
8225
|
|
|
8135
|
-
/*
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8226
|
+
/* Keep an active focus line above every neighbour, including an adjacent
|
|
8227
|
+
field and a hovered control (some platforms keep focus on the input while
|
|
8228
|
+
a button is clicked). It must follow the primary-edge rule above: both are
|
|
8229
|
+
(0,2,0), and when it came first a focused field stayed at 3 and the next
|
|
8230
|
+
field, painted later, covered the 1px shared edge of its line. */
|
|
8231
|
+
.join > :is(:focus, :focus-within) {
|
|
8232
|
+
z-index: 4;
|
|
8142
8233
|
}
|
|
8143
8234
|
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8235
|
+
/* One wrapper level (e.g. a combobox element around the native control) is
|
|
8236
|
+
supported generically — no widget is named here. The wrapper is the flex
|
|
8237
|
+
item that receives the corner longhands above; the inner control inherits
|
|
8238
|
+
them. The wrapper is also what align-items stretches: block-size: 100%
|
|
8239
|
+
resolves against that stretched size, so the field fills it. :hover and
|
|
8240
|
+
:focus-within already propagate to the wrapper, so the z-index rules above
|
|
8241
|
+
need no duplication. */
|
|
8242
|
+
.join > * > :is(.input, .textarea, .select) {
|
|
8243
|
+
block-size: 100%;
|
|
8244
|
+
border-radius: inherit;
|
|
8147
8245
|
}
|
|
8148
8246
|
|
|
8247
|
+
/* Text controls paint their own inset focus outline (control.css), so a
|
|
8248
|
+
.join never moves the ring to the group: button-only groups and wrapped
|
|
8249
|
+
inputs alike keep per-control focus, with no :has() group treatment. */
|
|
8250
|
+
|
|
8149
8251
|
/* A default outline action belongs to the composite field border. An explicit
|
|
8150
8252
|
intent still provides its own outline color — --intent is guaranteed-invalid
|
|
8151
|
-
for a neutral button (the :where(.btn) boundary), so the fallback applies.
|
|
8152
|
-
|
|
8253
|
+
for a neutral button (the :where(.btn) boundary), so the fallback applies.
|
|
8254
|
+
The second :has() covers a control nested one wrapper level deep. */
|
|
8255
|
+
.join:has(> :is(.input, .textarea, .select)) > .btn.outline,
|
|
8256
|
+
.join:has(> * > :is(.input, .textarea, .select)) > .btn.outline {
|
|
8153
8257
|
--btn-border-color: var(--intent, var(--border));
|
|
8154
8258
|
}
|
|
8155
8259
|
|
|
@@ -8518,9 +8622,9 @@ a.list-item[aria-current]::before {
|
|
|
8518
8622
|
}
|
|
8519
8623
|
|
|
8520
8624
|
/*
|
|
8521
|
-
*
|
|
8522
|
-
*
|
|
8523
|
-
*
|
|
8625
|
+
* Extra utility helpers — explicit property/value names beside the curated
|
|
8626
|
+
* shortcuts of base.css. Both are imported by utilities/index.css and are
|
|
8627
|
+
* equally public (docs/design-notes/naming.md § Utilities).
|
|
8524
8628
|
* They are escape hatches for layout nudges, not a full atomic scale.
|
|
8525
8629
|
*
|
|
8526
8630
|
* Core principle:
|
|
@@ -8696,8 +8800,9 @@ a.list-item[aria-current]::before {
|
|
|
8696
8800
|
overflow-y: auto;
|
|
8697
8801
|
}
|
|
8698
8802
|
|
|
8699
|
-
/* Text
|
|
8700
|
-
.
|
|
8803
|
+
/* Text. .text-wrap is the counterpart of base's .text-nowrap: it restores
|
|
8804
|
+
wrapping inside a component that sets nowrap (.badge). */
|
|
8805
|
+
.text-wrap {
|
|
8701
8806
|
white-space: normal;
|
|
8702
8807
|
}
|
|
8703
8808
|
|
|
@@ -8770,7 +8875,7 @@ a.list-item[aria-current]::before {
|
|
|
8770
8875
|
/* Surface. These are semantic surfaces, not arbitrary paint: each owns its
|
|
8771
8876
|
matching ink and rebinds --heading to that ink, so it stays readable when it
|
|
8772
8877
|
recreates a light surface inside a context that installed a dark one
|
|
8773
|
-
(.
|
|
8878
|
+
(e.g. a --surface-solid band). Intent ink is not inherited through a surface utility; use
|
|
8774
8879
|
.intent-color for that. */
|
|
8775
8880
|
.background-surface {
|
|
8776
8881
|
--heading: currentColor;
|