actual-css 0.11.1 → 0.12.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 +47 -0
- package/README.md +26 -6
- package/components.json +4 -16
- package/dist/actual.full.css +296 -267
- package/dist/actual.full.min.css +1 -1
- package/docs/brand/favicon.svg +5 -0
- package/docs/brand/lockup.svg +12 -0
- package/docs/brand/logo-dark.svg +9 -0
- package/docs/brand/logo-light.svg +9 -0
- package/docs/brand/logo.svg +11 -0
- package/docs/brand/mark.svg +11 -0
- package/docs/navigation.json +3 -2
- package/docs/pages/components/alert.md +2 -10
- package/docs/pages/components/badge.md +3 -9
- package/docs/pages/components/card.md +31 -2
- package/docs/pages/components/dialog.md +121 -33
- package/docs/pages/components/drawer.md +2 -2
- package/docs/pages/components/navbar.md +8 -9
- package/docs/pages/components/table.md +3 -3
- package/docs/pages/enhancements/flyout.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +1 -1
- package/docs/pages/enhancements/status-bar.md +2 -2
- package/docs/pages/examples/overview.md +2 -1
- package/docs/pages/forms/date-picker.md +90 -0
- package/docs/pages/forms/detached-actions.md +37 -30
- package/docs/pages/forms/form-actions.md +61 -28
- package/docs/pages/forms/input-policy.md +1 -1
- package/docs/pages/forms/overview.md +6 -5
- package/docs/pages/forms/sticky-actions.md +85 -83
- package/docs/pages/forms/validation.md +5 -5
- package/docs/pages/foundations/philosophy.md +5 -3
- package/docs/pages/foundations/theming.md +15 -4
- package/docs/pages/foundations/tokens.md +50 -33
- package/docs/pages/foundations/typography.md +4 -7
- package/docs/pages/guides/bootstrap.md +2 -2
- package/docs/pages/guides/design-tools.md +2 -2
- package/docs/pages/guides/integrating-actual-css.md +5 -1
- package/docs/pages/guides/introduction.md +3 -4
- package/docs/pages/guides/modular-import.md +3 -0
- package/docs/pages/guides/progressive-enhancement.md +2 -2
- package/docs/pages/guides/tailwind.md +70 -0
- package/docs/pages/guides/upgrading.md +74 -0
- package/docs/pages/layout/cluster.md +28 -2
- package/docs/pages/layout/overview.md +1 -1
- package/docs/pages/layout/stack.md +21 -16
- package/docs/pages/layout/topbar.md +1 -1
- package/docs/pages/patterns/navigation-list.md +16 -46
- package/docs/pages/patterns/overview.md +3 -4
- package/docs/pages/utilities/flex.md +30 -20
- package/docs/pages/utilities/sizing-wrapping.md +1 -1
- package/docs/pages/utilities/spacing.md +4 -3
- package/package.json +2 -2
- package/reserved-classes.json +3 -3
- package/skills/actual-css/references/enhancements.md +10 -7
- package/src/cli/actual-css.js +39 -13
- package/src/css/components/avatar.css +16 -9
- package/src/css/components/badge.css +1 -5
- package/src/css/components/button.css +3 -9
- package/src/css/components/card.css +18 -10
- package/src/css/components/chat.css +4 -5
- package/src/css/components/drawer.css +19 -3
- package/src/css/components/flyout.css +3 -0
- package/src/css/components/index.css +0 -1
- package/src/css/components/modal.css +96 -124
- package/src/css/components/navbar.css +14 -25
- package/src/css/core/base.css +1 -1
- package/src/css/core/reset.css +2 -1
- package/src/css/core/theme.css +4 -0
- package/src/css/core/tokens.css +28 -10
- package/src/css/core/variants.css +41 -14
- package/src/css/forms/form-actions.css +15 -10
- package/src/css/layout/cluster.css +11 -0
- package/src/css/layout/topbar.css +12 -6
- package/src/css/utilities/base.css +5 -0
- package/src/css/utilities/extra.css +3 -7
- package/src/tooling/color.js +65 -0
- package/src/tooling/design-tokens.js +9 -1
- package/src/tooling/theme-contrast.js +209 -0
- package/docs/pages/patterns/actions.md +0 -52
- package/src/css/components/actions.css +0 -27
package/dist/actual.full.css
CHANGED
|
@@ -41,7 +41,8 @@ select {
|
|
|
41
41
|
|
|
42
42
|
*,
|
|
43
43
|
*::before,
|
|
44
|
-
*::after
|
|
44
|
+
*::after,
|
|
45
|
+
::backdrop {
|
|
45
46
|
animation-duration: 0.01ms !important;
|
|
46
47
|
animation-delay: 0s !important;
|
|
47
48
|
animation-iteration-count: 1 !important;
|
|
@@ -72,7 +73,6 @@ select {
|
|
|
72
73
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
73
74
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
74
75
|
--font-width: 100%;
|
|
75
|
-
--font-width-dense: 95%;
|
|
76
76
|
--line-height: 1.5;
|
|
77
77
|
--line-height-tight: 1.25;
|
|
78
78
|
--line-height-relaxed: 1.75;
|
|
@@ -156,8 +156,9 @@ select {
|
|
|
156
156
|
--bar-height: 0.65rem;
|
|
157
157
|
|
|
158
158
|
/* Controls — inputs and buttons share --control-size so heights always match.
|
|
159
|
-
.compact/.spacious (variants.css) switch geometry
|
|
160
|
-
--control-font-size. Local .sm/.lg
|
|
159
|
+
.compact/.spacious (variants.css) switch geometry (--control-size,
|
|
160
|
+
--control-pad-x) without changing --control-font-size. Local .sm/.lg
|
|
161
|
+
component sizes switch all three.
|
|
161
162
|
|
|
162
163
|
Inline choices (checkbox, radio, switch) are typographic, not field-sized:
|
|
163
164
|
they sit on a line of text, so they follow --control-font-size through
|
|
@@ -168,17 +169,35 @@ select {
|
|
|
168
169
|
|
|
169
170
|
--control-pad-x is the opposite kind of token: a resolved geometry length,
|
|
170
171
|
not an em ratio. Every part of one control must see the same horizontal
|
|
171
|
-
distance, so it is stated in
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
distance, so it is stated in lengths rather than an em whose value would
|
|
173
|
+
change on a descendant with its own font size (the input-icon glyph, made
|
|
174
|
+
larger by --input-icon-size).
|
|
175
|
+
|
|
176
|
+
Like --control-size, it is a scale plus a selection: themes set the
|
|
177
|
+
-sm/-md/-lg steps, and :root, .sm/.lg and .compact/.spacious (variants.css)
|
|
178
|
+
pick one. A theme that wrote --control-pad-x itself would be overwritten
|
|
179
|
+
by every size and density boundary — bootstrap-v6's 12px became a wider
|
|
180
|
+
13px inside .compact. The selection is rebound at each boundary because a
|
|
181
|
+
var() inside a custom property resolves where it is declared.
|
|
182
|
+
|
|
183
|
+
The boundaries also write --control-step-sm/-md/-lg: which step is
|
|
184
|
+
selected, as 0/1 weights that mean the same under every theme. A theme
|
|
185
|
+
that sets its own steps rebuilds the selection from them in its own block
|
|
186
|
+
(bootstrap-v6.css), so a nested island of it reads its steps without
|
|
187
|
+
resetting the density it sits in. Never rebind on every [data-theme]: a
|
|
188
|
+
palette-only island then became a geometry boundary that an inherited
|
|
189
|
+
--control-size or --control-pad-x no longer crossed. */
|
|
177
190
|
--control-size-sm: 2rem;
|
|
178
191
|
--control-size-md: 2.375rem;
|
|
179
192
|
--control-size-lg: 2.75rem;
|
|
180
193
|
--control-size: var(--control-size-md);
|
|
181
|
-
--control-pad-x: var(--font-size-
|
|
194
|
+
--control-pad-x-sm: var(--font-size-xs);
|
|
195
|
+
--control-pad-x-md: var(--font-size-md);
|
|
196
|
+
--control-pad-x-lg: var(--font-size-lg);
|
|
197
|
+
--control-pad-x: var(--control-pad-x-md);
|
|
198
|
+
--control-step-sm: 0;
|
|
199
|
+
--control-step-md: 1;
|
|
200
|
+
--control-step-lg: 0;
|
|
182
201
|
--control-font-size: var(--font-size-md);
|
|
183
202
|
--choice-control-size: 1.125em;
|
|
184
203
|
--disabled-opacity: 0.65;
|
|
@@ -367,6 +386,10 @@ select {
|
|
|
367
386
|
fields, a solid band), which rules out the primary itself. */
|
|
368
387
|
--focus: hsl(268 30% 55%);
|
|
369
388
|
--hover-overlay: hsl(260 20% 10% / 0.04);
|
|
389
|
+
/* Solid fills take a stronger wash than the subtle shared overlay. Black in
|
|
390
|
+
both schemes on purpose: a solid button darkens on hover even in dark mode
|
|
391
|
+
(see themes/gradient.css), unlike the neutral washes. */
|
|
392
|
+
--hover-overlay-solid: rgb(0 0 0 / 0.14);
|
|
370
393
|
|
|
371
394
|
/* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
|
|
372
395
|
--radius: 0.5rem;
|
|
@@ -515,7 +538,7 @@ select {
|
|
|
515
538
|
* Public hooks:
|
|
516
539
|
* --link
|
|
517
540
|
* --hr-space
|
|
518
|
-
* --font-width
|
|
541
|
+
* --font-width (the document's font-stretch; a theme typography choice)
|
|
519
542
|
*/
|
|
520
543
|
|
|
521
544
|
body {
|
|
@@ -665,17 +688,21 @@ small {
|
|
|
665
688
|
* components/badge.css — .badge default soft behind explicit --ui-*
|
|
666
689
|
* components/alert.css — .alert default soft behind explicit --ui-*
|
|
667
690
|
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
691
|
+
* Three axes, each predictable on its own:
|
|
692
|
+
* .sm/.lg size — typography + geometry, local to a component
|
|
693
|
+
* .compact/.spacious density — rhythm + geometry, inherited by a region
|
|
694
|
+
* .tight/.loose rhythm — --gap only, inherited by a region
|
|
695
|
+
*
|
|
696
|
+
* Density never touches typography: no font size, icon size or font width.
|
|
697
|
+
* A width bonus through font-stretch only showed on typefaces with a wdth axis,
|
|
698
|
+
* so .compact meant something different per OS and font; a condensed table is
|
|
699
|
+
* a typographic decision for the application. Controls consume --control-size
|
|
700
|
+
* and --control-pad-x, so a compact control is shorter and narrower (a height
|
|
701
|
+
* change alone read as the same control squashed); non-control components may
|
|
671
702
|
* consume --density-* when the context should affect their local rhythm.
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
* --font-width, so a spacious island inside a compact context returns to the
|
|
676
|
-
* normal width exactly as it restores its own gap, spacing, and control size.
|
|
677
|
-
* Themes whose typeface has no useful width axis opt out with
|
|
678
|
-
* --font-width-dense: 100%.
|
|
703
|
+
*
|
|
704
|
+
* .tight/.loose reuse the density --gap steps, so a tight row and a compact
|
|
705
|
+
* one share one rhythm.
|
|
679
706
|
*
|
|
680
707
|
* .sm/.lg are local size roles, not shared measurements. Shared control
|
|
681
708
|
* participants below map both typography and geometry; components with
|
|
@@ -714,18 +741,35 @@ small {
|
|
|
714
741
|
--ui-border: var(--border);
|
|
715
742
|
}
|
|
716
743
|
|
|
744
|
+
/* Each boundary selects a control step twice: the values, resolved here, and
|
|
745
|
+
the 0/1 step weights a nested [data-theme] island rebuilds them from
|
|
746
|
+
(tokens.css). */
|
|
717
747
|
.compact {
|
|
718
748
|
--gap: var(--space-20);
|
|
719
749
|
--density-space: var(--space-20);
|
|
720
750
|
--control-size: var(--control-size-sm);
|
|
721
|
-
|
|
751
|
+
--control-pad-x: var(--control-pad-x-sm);
|
|
752
|
+
--control-step-sm: 1;
|
|
753
|
+
--control-step-md: 0;
|
|
754
|
+
--control-step-lg: 0;
|
|
722
755
|
}
|
|
723
756
|
|
|
724
757
|
.spacious {
|
|
725
758
|
--gap: var(--space-50);
|
|
726
759
|
--density-space: var(--space-50);
|
|
727
760
|
--control-size: var(--control-size-lg);
|
|
728
|
-
|
|
761
|
+
--control-pad-x: var(--control-pad-x-lg);
|
|
762
|
+
--control-step-sm: 0;
|
|
763
|
+
--control-step-md: 0;
|
|
764
|
+
--control-step-lg: 1;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
.tight {
|
|
768
|
+
--gap: var(--space-20);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.loose {
|
|
772
|
+
--gap: var(--space-50);
|
|
729
773
|
}
|
|
730
774
|
|
|
731
775
|
/* Shared control scale. Wrappers are listed when their descendants form one
|
|
@@ -754,7 +798,10 @@ small {
|
|
|
754
798
|
).sm {
|
|
755
799
|
--control-size: var(--control-size-sm);
|
|
756
800
|
--control-font-size: var(--font-size-sm);
|
|
757
|
-
--control-pad-x: var(--
|
|
801
|
+
--control-pad-x: var(--control-pad-x-sm);
|
|
802
|
+
--control-step-sm: 1;
|
|
803
|
+
--control-step-md: 0;
|
|
804
|
+
--control-step-lg: 0;
|
|
758
805
|
}
|
|
759
806
|
|
|
760
807
|
:where(
|
|
@@ -778,7 +825,10 @@ small {
|
|
|
778
825
|
).lg {
|
|
779
826
|
--control-size: var(--control-size-lg);
|
|
780
827
|
--control-font-size: var(--font-size-lg);
|
|
781
|
-
--control-pad-x: var(--
|
|
828
|
+
--control-pad-x: var(--control-pad-x-lg);
|
|
829
|
+
--control-step-sm: 0;
|
|
830
|
+
--control-step-md: 0;
|
|
831
|
+
--control-step-lg: 1;
|
|
782
832
|
}
|
|
783
833
|
|
|
784
834
|
/* Modern color enhancement. Baseline variant values above remain usable
|
|
@@ -1298,6 +1348,7 @@ small {
|
|
|
1298
1348
|
* --cluster-wrap
|
|
1299
1349
|
*
|
|
1300
1350
|
* Child contract: direct-child margins are reset, spacing comes from --gap.
|
|
1351
|
+
* A ul/ol/menu keeps its semantics but loses list chrome (see below).
|
|
1301
1352
|
*/
|
|
1302
1353
|
.cluster {
|
|
1303
1354
|
display: flex;
|
|
@@ -1312,6 +1363,16 @@ small {
|
|
|
1312
1363
|
margin: 0;
|
|
1313
1364
|
}
|
|
1314
1365
|
|
|
1366
|
+
/* Horizontal, wrapping layout is incompatible with list markers and native
|
|
1367
|
+
indentation: a ul/ol/menu applied with .cluster stays a list in the DOM, but
|
|
1368
|
+
its visual chrome would be accidental. .stack intentionally leaves this
|
|
1369
|
+
alone — a vertical list can keep its numbers. */
|
|
1370
|
+
:where(ul, ol, menu).cluster {
|
|
1371
|
+
margin: 0;
|
|
1372
|
+
padding: 0;
|
|
1373
|
+
list-style: none;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1315
1376
|
/*
|
|
1316
1377
|
* Centered content column with a maximum measure.
|
|
1317
1378
|
*
|
|
@@ -1883,8 +1944,9 @@ small {
|
|
|
1883
1944
|
/*
|
|
1884
1945
|
* Sticky, frosted top bar shell for app/dashboard shells (persistent
|
|
1885
1946
|
* sidebar + topbar layouts). Owns positioning, stacking, and the frosted
|
|
1886
|
-
* background
|
|
1887
|
-
*
|
|
1947
|
+
* background; it also lays its children out as a default row that a layout
|
|
1948
|
+
* primitive on the same element (such as .cluster) replaces. Row content
|
|
1949
|
+
* (search, breadcrumb, actions) stays local to the page.
|
|
1888
1950
|
*
|
|
1889
1951
|
* Docs: docs/pages/layout/topbar.md
|
|
1890
1952
|
*
|
|
@@ -1898,16 +1960,21 @@ small {
|
|
|
1898
1960
|
inset-block-start: 0;
|
|
1899
1961
|
z-index: var(--z-sticky);
|
|
1900
1962
|
isolation: isolate;
|
|
1901
|
-
display: flex;
|
|
1902
|
-
flex-direction: row;
|
|
1903
|
-
align-items: center;
|
|
1904
|
-
gap: var(--gap);
|
|
1905
1963
|
padding-block: var(--space-30);
|
|
1906
1964
|
padding-inline: var(--space-40);
|
|
1907
1965
|
border-block-end: var(--border-width) solid var(--border);
|
|
1908
1966
|
background: var(--surface);
|
|
1909
1967
|
}
|
|
1910
1968
|
|
|
1969
|
+
/* The row is a default at zero specificity: .topbar and the primitives share
|
|
1970
|
+
layout/, so at class weight their file order alone would pick the winner. */
|
|
1971
|
+
:where(.topbar) {
|
|
1972
|
+
display: flex;
|
|
1973
|
+
flex-direction: row;
|
|
1974
|
+
align-items: center;
|
|
1975
|
+
gap: var(--gap);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1911
1978
|
/* Both conditions are required: the translucent background contains var(),
|
|
1912
1979
|
so a browser with backdrop-filter but not color-mix (e.g. Safari < 16.2)
|
|
1913
1980
|
would accept the declaration at parse time, fail it at computed-value
|
|
@@ -2629,32 +2696,37 @@ small {
|
|
|
2629
2696
|
}
|
|
2630
2697
|
|
|
2631
2698
|
/*
|
|
2632
|
-
* Form actions —
|
|
2699
|
+
* Form actions — the role of a .cluster action row that closes a form.
|
|
2633
2700
|
*
|
|
2634
2701
|
* Docs: docs/pages/forms/form-actions.md
|
|
2635
2702
|
*
|
|
2636
|
-
*
|
|
2637
|
-
*
|
|
2638
|
-
*
|
|
2703
|
+
* Markup: `<div class="cluster form-actions">`. The cluster owns the row
|
|
2704
|
+
* (flex, wrap, gap); this role owns the form-specific alignment policy and the
|
|
2705
|
+
* opt-in sticky variant. The distance to the fields above belongs to the
|
|
2706
|
+
* parent layout (.stack on the form), never to a margin here: a margin would
|
|
2707
|
+
* add to a stack's gap. The sticky variant docks to the viewport bottom with a
|
|
2708
|
+
* translucent blur where supported; at rest it stays visually neutral so it
|
|
2709
|
+
* does not double as a separator.
|
|
2639
2710
|
*
|
|
2640
2711
|
* Public hooks:
|
|
2641
2712
|
* --form-actions-align
|
|
2642
2713
|
* --form-actions-justify
|
|
2643
|
-
* --form-actions-margin-block-start
|
|
2644
2714
|
*
|
|
2645
2715
|
* Internal:
|
|
2646
2716
|
* --form-actions-sticky-block-offset (owned by .form-actions.sticky)
|
|
2647
2717
|
* --form-actions-sticky-inline-offset (owned by .form-actions.sticky)
|
|
2648
2718
|
* --form-actions-sticky-padding (owned by .form-actions.sticky)
|
|
2649
2719
|
*/
|
|
2720
|
+
|
|
2721
|
+
/* The policy hooks write the properties themselves rather than relaying
|
|
2722
|
+
--cluster-*: a relay would inherit into any .cluster nested in the row.
|
|
2723
|
+
These are equal-specificity overrides of .cluster, so they win only because
|
|
2724
|
+
forms load after layout in the official bundle (tests/css-audit.test.js
|
|
2725
|
+
guards the order); utilities load later and keep the author's last word. On
|
|
2726
|
+
this row --cluster-justify/--cluster-align have no effect. */
|
|
2650
2727
|
.form-actions {
|
|
2651
|
-
display: flex;
|
|
2652
|
-
flex-direction: row;
|
|
2653
|
-
flex-wrap: wrap;
|
|
2654
|
-
gap: var(--gap);
|
|
2655
2728
|
align-items: var(--form-actions-align, center);
|
|
2656
2729
|
justify-content: var(--form-actions-justify, flex-start);
|
|
2657
|
-
margin-block-start: var(--form-actions-margin-block-start, var(--space-50));
|
|
2658
2730
|
}
|
|
2659
2731
|
|
|
2660
2732
|
/* Opt-in sticky variant. When the bar is not stuck (at rest) the translucent
|
|
@@ -3736,7 +3808,6 @@ small {
|
|
|
3736
3808
|
* --btn-bg
|
|
3737
3809
|
* --btn-fg
|
|
3738
3810
|
* --btn-border
|
|
3739
|
-
* --btn-default-hover-bg
|
|
3740
3811
|
* --btn-border-block-end-color
|
|
3741
3812
|
* --btn-border-block-end-width
|
|
3742
3813
|
* --btn-border-block
|
|
@@ -3762,9 +3833,8 @@ small {
|
|
|
3762
3833
|
--btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
|
|
3763
3834
|
--btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
|
|
3764
3835
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
3765
|
-
--btn-default-hover-bg: var(--btn-bg);
|
|
3766
3836
|
--btn-radius: var(--radius);
|
|
3767
|
-
--btn-hover-overlay: var(--hover-overlay,
|
|
3837
|
+
--btn-hover-overlay: var(--hover-overlay-solid, var(--hover-overlay));
|
|
3768
3838
|
--btn-border-block: calc(
|
|
3769
3839
|
var(--border-width) +
|
|
3770
3840
|
var(--btn-border-block-end-width, var(--border-width))
|
|
@@ -3844,10 +3914,6 @@ small {
|
|
|
3844
3914
|
}
|
|
3845
3915
|
|
|
3846
3916
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
3847
|
-
.btn {
|
|
3848
|
-
--btn-default-hover-bg: color-mix(in oklch, var(--btn-bg) 88%, black);
|
|
3849
|
-
}
|
|
3850
|
-
|
|
3851
3917
|
.btn.soft {
|
|
3852
3918
|
--ui-hover-bg: color-mix(
|
|
3853
3919
|
in oklab,
|
|
@@ -3863,9 +3929,9 @@ small {
|
|
|
3863
3929
|
}
|
|
3864
3930
|
|
|
3865
3931
|
/* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
|
|
3866
|
-
the overlay darkening for solid
|
|
3932
|
+
the overlay darkening for solid is .btn-specific. */
|
|
3867
3933
|
.btn:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
3868
|
-
background: var(--ui-hover-bg, var(--btn-
|
|
3934
|
+
background: var(--ui-hover-bg, var(--btn-bg));
|
|
3869
3935
|
/* Huge inset spread paints the overlay over any border-radius without a
|
|
3870
3936
|
pseudo-element; a plain background would replace the fill instead of
|
|
3871
3937
|
darkening it. */
|
|
@@ -4230,10 +4296,6 @@ a.btn[aria-disabled="true"] {
|
|
|
4230
4296
|
}
|
|
4231
4297
|
|
|
4232
4298
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
4233
|
-
:is(a.badge, button.badge):is(.solid, :empty) {
|
|
4234
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
4235
|
-
}
|
|
4236
|
-
|
|
4237
4299
|
:is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
|
|
4238
4300
|
--badge-default-hover-bg: color-mix(
|
|
4239
4301
|
in oklab,
|
|
@@ -4260,7 +4322,7 @@ a.btn[aria-disabled="true"] {
|
|
|
4260
4322
|
:where(:disabled, [aria-disabled="true"])
|
|
4261
4323
|
):hover {
|
|
4262
4324
|
/* Huge inset spread paints the overlay over the pill radius (see button.css). */
|
|
4263
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4325
|
+
box-shadow: inset 0 0 0 999px var(--hover-overlay-solid, var(--hover-overlay));
|
|
4264
4326
|
}
|
|
4265
4327
|
|
|
4266
4328
|
button.badge {
|
|
@@ -4561,34 +4623,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4561
4623
|
}
|
|
4562
4624
|
}
|
|
4563
4625
|
|
|
4564
|
-
/*
|
|
4565
|
-
* Actions — a horizontal action-button cluster.
|
|
4566
|
-
*
|
|
4567
|
-
* Docs: docs/pages/patterns/actions.md
|
|
4568
|
-
*
|
|
4569
|
-
* Composes with .btn (or any inline element) and feeds layout via the shared
|
|
4570
|
-
* --gap token.
|
|
4571
|
-
*
|
|
4572
|
-
* Internal:
|
|
4573
|
-
* --gap (local value for the shared gap token)
|
|
4574
|
-
*/
|
|
4575
|
-
.actions {
|
|
4576
|
-
--gap: var(--space-20);
|
|
4577
|
-
|
|
4578
|
-
display: flex;
|
|
4579
|
-
flex-direction: row;
|
|
4580
|
-
flex-wrap: wrap;
|
|
4581
|
-
align-items: center;
|
|
4582
|
-
gap: var(--gap);
|
|
4583
|
-
margin: 0;
|
|
4584
|
-
padding: 0;
|
|
4585
|
-
list-style: none;
|
|
4586
|
-
}
|
|
4587
|
-
|
|
4588
|
-
.actions > li {
|
|
4589
|
-
margin: 0;
|
|
4590
|
-
}
|
|
4591
|
-
|
|
4592
4626
|
|
|
4593
4627
|
/* Surfaces */
|
|
4594
4628
|
/*
|
|
@@ -4597,11 +4631,11 @@ a.badge[aria-disabled="true"] {
|
|
|
4597
4631
|
* Docs: docs/pages/components/card.md
|
|
4598
4632
|
*
|
|
4599
4633
|
* Surface, border, radius, padding, density, and elevation stay local here.
|
|
4600
|
-
* Direct header/footer children are structural slots
|
|
4601
|
-
* anchored to the card's bottom edge when extra block space
|
|
4602
|
-
* price/action rows align across equal-height cards in a
|
|
4603
|
-
* owns its direct-child rhythm
|
|
4604
|
-
* over that layout. Page layout remains the job of markup and layout
|
|
4634
|
+
* Direct header/footer children are structural slots with a default split row;
|
|
4635
|
+
* a direct footer is anchored to the card's bottom edge when extra block space
|
|
4636
|
+
* is available (so price/action rows align across equal-height cards in a
|
|
4637
|
+
* grid). A bare card owns its direct-child rhythm and its slot rows; a layout
|
|
4638
|
+
* primitive on the card or on a slot takes over that layout. Page layout remains the job of markup and layout
|
|
4605
4639
|
* primitives outside the card.
|
|
4606
4640
|
*
|
|
4607
4641
|
* Public hooks:
|
|
@@ -4639,6 +4673,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4639
4673
|
|
|
4640
4674
|
.card {
|
|
4641
4675
|
--card-pad: var(--space-40);
|
|
4676
|
+
/* Local anatomy, like the padding. Never var(--gap): that token is
|
|
4677
|
+
inherited, so the --gap of a parent grid became the rhythm inside every
|
|
4678
|
+
card (+79px per card on a 38px grid). */
|
|
4642
4679
|
--card-gap: var(--space-30);
|
|
4643
4680
|
--card-radius: var(--radius-lg);
|
|
4644
4681
|
--card-max-inline-size: none;
|
|
@@ -4680,7 +4717,11 @@ a.badge[aria-disabled="true"] {
|
|
|
4680
4717
|
margin-block: 0;
|
|
4681
4718
|
}
|
|
4682
4719
|
|
|
4683
|
-
|
|
4720
|
+
/* The slot row is a convenience default, not anatomy: like the root box it
|
|
4721
|
+
lives at zero specificity, so a primitive, role or utility on the slot
|
|
4722
|
+
(.cluster, .media, .form-actions, .justify-content-*) replaces it instead of
|
|
4723
|
+
losing to components/ on source order. */
|
|
4724
|
+
:where(.card > header, .card > footer) {
|
|
4684
4725
|
display: flex;
|
|
4685
4726
|
flex-wrap: wrap;
|
|
4686
4727
|
align-items: var(--items-align, center);
|
|
@@ -4689,15 +4730,15 @@ a.badge[aria-disabled="true"] {
|
|
|
4689
4730
|
min-inline-size: 0;
|
|
4690
4731
|
}
|
|
4691
4732
|
|
|
4733
|
+
:where(.card > header) {
|
|
4734
|
+
align-items: var(--items-align, flex-start);
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4692
4737
|
.card > :where(header, footer) > * {
|
|
4693
4738
|
min-inline-size: 0;
|
|
4694
4739
|
margin-block: 0;
|
|
4695
4740
|
}
|
|
4696
4741
|
|
|
4697
|
-
.card > header {
|
|
4698
|
-
align-items: var(--items-align, flex-start);
|
|
4699
|
-
}
|
|
4700
|
-
|
|
4701
4742
|
/* A direct footer is pinned to the bottom when the card has free space.
|
|
4702
4743
|
Specificity beats .stack > * so a .card.stack keeps this contract. */
|
|
4703
4744
|
.card > footer {
|
|
@@ -4712,6 +4753,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4712
4753
|
}
|
|
4713
4754
|
.card.compact {
|
|
4714
4755
|
--card-pad: var(--space-20);
|
|
4756
|
+
--card-gap: var(--space-20);
|
|
4715
4757
|
}
|
|
4716
4758
|
|
|
4717
4759
|
@media print {
|
|
@@ -4732,15 +4774,19 @@ a.badge[aria-disabled="true"] {
|
|
|
4732
4774
|
* Public hooks:
|
|
4733
4775
|
* --modal-size
|
|
4734
4776
|
* --modal-pad
|
|
4735
|
-
* --modal-header-bg
|
|
4736
4777
|
* --dialog-viewport-gap
|
|
4737
4778
|
* --dialog-icon-size
|
|
4738
4779
|
* --dialog-icon-glyph-size
|
|
4739
4780
|
*
|
|
4740
4781
|
* The corner close control is .close (components/close.css).
|
|
4741
4782
|
*
|
|
4783
|
+
* The body footer is a structural slot with no layout of its own: its action
|
|
4784
|
+
* row is composed in markup (`<footer class="cluster justify-content-end">`),
|
|
4785
|
+
* as in the drawer. A default row here would fight every composed one.
|
|
4786
|
+
*
|
|
4742
4787
|
* Internal:
|
|
4743
4788
|
* --dialog-inner-radius
|
|
4789
|
+
* --dialog-close-reserve (header room for a corner close; zero without a close)
|
|
4744
4790
|
* --dialog-focus-bleed
|
|
4745
4791
|
* --dialog-viewport-block
|
|
4746
4792
|
* --dialog-viewport-gap-default
|
|
@@ -4749,10 +4795,8 @@ a.badge[aria-disabled="true"] {
|
|
|
4749
4795
|
:where(dialog).modal {
|
|
4750
4796
|
/* --dialog-viewport-gap stays fallback-only so the responsive default can
|
|
4751
4797
|
live in an internal token: a media query moves it without closing the
|
|
4752
|
-
public hook.
|
|
4753
|
-
|
|
4754
|
-
calc() multiplication is not in the supported browser range, so the
|
|
4755
|
-
doubled gap is spelled out with an additive calc. */
|
|
4798
|
+
public hook. `-2x` is the gap on the two viewport edges, shared by the
|
|
4799
|
+
width and height caps. */
|
|
4756
4800
|
--dialog-viewport-gap-default: var(--space-40);
|
|
4757
4801
|
--dialog-viewport-gap-2x: calc(
|
|
4758
4802
|
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default)) +
|
|
@@ -4760,10 +4804,19 @@ a.badge[aria-disabled="true"] {
|
|
|
4760
4804
|
);
|
|
4761
4805
|
|
|
4762
4806
|
/* The panel is borderless, so its inner and outer radius are the same. Kept
|
|
4763
|
-
as one name for the padded wrapper and
|
|
4764
|
-
|
|
4807
|
+
as one name for the padded wrapper and the header band; a .bleed band
|
|
4808
|
+
inherits it from the wrapper, so none of them drifts from the corner. */
|
|
4765
4809
|
--dialog-inner-radius: var(--radius-lg);
|
|
4766
4810
|
|
|
4811
|
+
/* Header room for a corner close. Unconditional baseline: without :has()
|
|
4812
|
+
(Degraded tier) a header keeps too much room rather than a title running
|
|
4813
|
+
under the close. Zeroed when the dialog has no close. */
|
|
4814
|
+
--dialog-close-reserve: calc(
|
|
4815
|
+
var(--close-size, var(--control-size)) +
|
|
4816
|
+
var(--space-20) +
|
|
4817
|
+
var(--space-30)
|
|
4818
|
+
);
|
|
4819
|
+
|
|
4767
4820
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
4768
4821
|
provides the containing block for the absolutely positioned close button. */
|
|
4769
4822
|
position: fixed;
|
|
@@ -4796,9 +4849,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4796
4849
|
* both edges, which reads as horizontal movement.
|
|
4797
4850
|
*/
|
|
4798
4851
|
transform: translateY(0.35rem);
|
|
4799
|
-
transition:
|
|
4800
|
-
|
|
4801
|
-
|
|
4852
|
+
transition-property: opacity, transform;
|
|
4853
|
+
transition-duration: var(--duration);
|
|
4854
|
+
transition-timing-function: var(--ease-exit);
|
|
4802
4855
|
}
|
|
4803
4856
|
|
|
4804
4857
|
@media (forced-colors: active) {
|
|
@@ -4822,10 +4875,8 @@ a.badge[aria-disabled="true"] {
|
|
|
4822
4875
|
padding: var(--surface-pad);
|
|
4823
4876
|
}
|
|
4824
4877
|
|
|
4825
|
-
/*
|
|
4826
|
-
|
|
4827
|
-
the trailing cluster keeps it in flow instead of invoking the absolute
|
|
4828
|
-
corner treatment from close.css. */
|
|
4878
|
+
/* Dialog chrome beside the padded body wrapper. Wrapping the close in the
|
|
4879
|
+
trailing cluster keeps it in flow, not the absolute corner overlay. */
|
|
4829
4880
|
:where(dialog).modal > header {
|
|
4830
4881
|
display: flex;
|
|
4831
4882
|
align-items: center;
|
|
@@ -4835,7 +4886,14 @@ a.badge[aria-disabled="true"] {
|
|
|
4835
4886
|
padding: var(--modal-pad, var(--space-40));
|
|
4836
4887
|
border-start-start-radius: var(--dialog-inner-radius);
|
|
4837
4888
|
border-start-end-radius: var(--dialog-inner-radius);
|
|
4838
|
-
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
/* Paint is not anatomy: the default band fill has zero specificity, so a
|
|
4892
|
+
.background-* utility on the header replaces it. The layout above stays at
|
|
4893
|
+
the component's weight — a composed .cluster must not reset the title/close
|
|
4894
|
+
alignment. */
|
|
4895
|
+
:where(dialog.modal > header) {
|
|
4896
|
+
background: var(--surface-subtle);
|
|
4839
4897
|
}
|
|
4840
4898
|
|
|
4841
4899
|
:where(dialog).modal > header > .media {
|
|
@@ -4856,18 +4914,16 @@ a.badge[aria-disabled="true"] {
|
|
|
4856
4914
|
flex: none;
|
|
4857
4915
|
}
|
|
4858
4916
|
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
:
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
}
|
|
4917
|
+
/* The header stays fixed while the body wrapper owns overflow. Without :has()
|
|
4918
|
+
these drop individually and compact dialogs keep ordinary block flow. */
|
|
4919
|
+
:where(dialog).modal[open]:has(> header) {
|
|
4920
|
+
display: grid;
|
|
4921
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
4922
|
+
}
|
|
4866
4923
|
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
}
|
|
4924
|
+
:where(dialog).modal:has(> header) > :is(form, .stack) {
|
|
4925
|
+
min-block-size: 0;
|
|
4926
|
+
max-block-size: none;
|
|
4871
4927
|
}
|
|
4872
4928
|
|
|
4873
4929
|
@supports (block-size: 100svh) {
|
|
@@ -4886,10 +4942,21 @@ a.badge[aria-disabled="true"] {
|
|
|
4886
4942
|
|
|
4887
4943
|
:where(dialog).modal.scrollable > form,
|
|
4888
4944
|
:where(dialog).modal.scrollable > .stack {
|
|
4889
|
-
|
|
4945
|
+
display: flex;
|
|
4946
|
+
flex-direction: column;
|
|
4890
4947
|
overflow: hidden;
|
|
4891
4948
|
}
|
|
4892
4949
|
|
|
4950
|
+
/* Regions carry their own behaviour: a structural header or footer keeps its
|
|
4951
|
+
size, every other child absorbs the free space and scrolls. Nothing here has
|
|
4952
|
+
to detect which regions exist — an external header leaves the wrapper with
|
|
4953
|
+
content + footer and this still holds. Each non-chrome child is its own
|
|
4954
|
+
scrollport; wrap the body in one region for a single one. */
|
|
4955
|
+
:where(dialog).modal.scrollable > form > :is(header, footer),
|
|
4956
|
+
:where(dialog).modal.scrollable > .stack > :is(header, footer) {
|
|
4957
|
+
flex: none;
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4893
4960
|
:where(dialog).modal.scrollable > form > :not(:is(header, footer)),
|
|
4894
4961
|
:where(dialog).modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
4895
4962
|
--dialog-focus-bleed: calc(
|
|
@@ -4897,6 +4964,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4897
4964
|
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
4898
4965
|
);
|
|
4899
4966
|
|
|
4967
|
+
flex: 1 1 auto;
|
|
4900
4968
|
min-block-size: 0;
|
|
4901
4969
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
4902
4970
|
header and footer edges. The reserve covers the thicker of the two
|
|
@@ -4925,59 +4993,30 @@ a.badge[aria-disabled="true"] {
|
|
|
4925
4993
|
margin-block: 0;
|
|
4926
4994
|
}
|
|
4927
4995
|
|
|
4996
|
+
/* No close anywhere: reclaim the reserve. Deliberately coarse — a close in
|
|
4997
|
+
the body keeps the reserve, which costs only room, never overlap. */
|
|
4998
|
+
:where(dialog).modal:not(:has(.close)) {
|
|
4999
|
+
--dialog-close-reserve: 0px;
|
|
5000
|
+
}
|
|
5001
|
+
|
|
4928
5002
|
/* The close button is anchored to the dialog at top inline-end, out of the
|
|
4929
|
-
content flow; headers reserve inline room so titles never run under it.
|
|
4930
|
-
|
|
4931
|
-
lends .stack's specificity to
|
|
5003
|
+
content flow; headers reserve inline room so titles never run under it. A
|
|
5004
|
+
.bleed header keeps at least its band inset. The form/.stack anatomy is
|
|
5005
|
+
structural, so it stays in :where() and never lends .stack's specificity to
|
|
5006
|
+
a bare <form>. */
|
|
4932
5007
|
:where(dialog).modal > :where(form, .stack) > header {
|
|
4933
|
-
padding-inline-end:
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
);
|
|
5008
|
+
padding-inline-end: var(--dialog-close-reserve);
|
|
5009
|
+
}
|
|
5010
|
+
|
|
5011
|
+
:where(dialog).modal > :where(form, .stack) > header:where(.bleed) {
|
|
5012
|
+
padding-inline-end: max(var(--bleed-pad, 0px), var(--dialog-close-reserve));
|
|
4938
5013
|
}
|
|
4939
5014
|
|
|
4940
5015
|
/* A corner .close that is a direct child of the chrome header needs the same
|
|
4941
5016
|
reserve, but never below the header's own pad. Kept as its own rule so an
|
|
4942
5017
|
unsupported :has() drops only this geometry, not the wrapper reserve. */
|
|
4943
5018
|
:where(dialog).modal > header:has(> .close) {
|
|
4944
|
-
padding-inline-end: max(
|
|
4945
|
-
var(--modal-pad, var(--space-40)),
|
|
4946
|
-
calc(var(--close-size, var(--control-size)) + var(--space-20) + var(--space-30))
|
|
4947
|
-
);
|
|
4948
|
-
}
|
|
4949
|
-
|
|
4950
|
-
:where(dialog).modal > :where(form, .stack) > footer {
|
|
4951
|
-
display: flex;
|
|
4952
|
-
flex-wrap: wrap;
|
|
4953
|
-
justify-content: flex-end;
|
|
4954
|
-
gap: var(--gap);
|
|
4955
|
-
}
|
|
4956
|
-
|
|
4957
|
-
/* Confirmation layout — a focused message followed by a visually distinct
|
|
4958
|
-
action band. The media primitive owns icon/content alignment; this variant
|
|
4959
|
-
only removes the standard form inset so both regions can own their padding. */
|
|
4960
|
-
:where(dialog).modal.dialog-confirmation > form {
|
|
4961
|
-
--surface-pad: 0px;
|
|
4962
|
-
|
|
4963
|
-
gap: 0;
|
|
4964
|
-
padding: 0;
|
|
4965
|
-
}
|
|
4966
|
-
|
|
4967
|
-
:where(dialog).modal.dialog-confirmation > form > :is(.media, header) {
|
|
4968
|
-
gap: var(--space-40);
|
|
4969
|
-
padding: var(--space-50);
|
|
4970
|
-
}
|
|
4971
|
-
|
|
4972
|
-
/* The action band splits a decision pair: the cancel-style action holds the
|
|
4973
|
-
leading edge and the committing action the trailing one, so the destructive
|
|
4974
|
-
choice is never adjacent to the dismiss. */
|
|
4975
|
-
:where(dialog).modal.dialog-confirmation > form > footer {
|
|
4976
|
-
justify-content: space-between;
|
|
4977
|
-
padding: var(--space-40) var(--space-50);
|
|
4978
|
-
border-end-start-radius: var(--dialog-inner-radius);
|
|
4979
|
-
border-end-end-radius: var(--dialog-inner-radius);
|
|
4980
|
-
background: var(--surface-subtle);
|
|
5019
|
+
padding-inline-end: max(var(--modal-pad, var(--space-40)), var(--dialog-close-reserve));
|
|
4981
5020
|
}
|
|
4982
5021
|
|
|
4983
5022
|
/* Intent boundary — inherited intents do not tint the icon well. A local
|
|
@@ -4990,14 +5029,17 @@ a.badge[aria-disabled="true"] {
|
|
|
4990
5029
|
--ui-fg: initial;
|
|
4991
5030
|
}
|
|
4992
5031
|
|
|
5032
|
+
/* The size hooks are read here, never declared: an author sets them on the
|
|
5033
|
+
icon's media row (or the dialog) so both the circle and the title that
|
|
5034
|
+
centers on it inherit one value. A local declaration on .dialog-icon would
|
|
5035
|
+
shadow that value for the circle while the title kept the inherited one —
|
|
5036
|
+
the two centers would drift. An override on the icon alone is deliberately
|
|
5037
|
+
local and moves only the circle. */
|
|
4993
5038
|
.dialog-icon {
|
|
4994
|
-
--dialog-icon-size: 3rem;
|
|
4995
|
-
--dialog-icon-glyph-size: 1.5rem;
|
|
4996
|
-
|
|
4997
5039
|
display: inline-grid;
|
|
4998
5040
|
place-items: center;
|
|
4999
|
-
inline-size: var(--dialog-icon-size);
|
|
5000
|
-
block-size: var(--dialog-icon-size);
|
|
5041
|
+
inline-size: var(--dialog-icon-size, 3rem);
|
|
5042
|
+
block-size: var(--dialog-icon-size, 3rem);
|
|
5001
5043
|
flex: none;
|
|
5002
5044
|
border-radius: var(--radius-full);
|
|
5003
5045
|
background: var(--ui-bg, var(--surface-subtle));
|
|
@@ -5007,46 +5049,27 @@ a.badge[aria-disabled="true"] {
|
|
|
5007
5049
|
.dialog-icon > * {
|
|
5008
5050
|
display: inline-grid;
|
|
5009
5051
|
place-items: center;
|
|
5010
|
-
inline-size: var(--dialog-icon-glyph-size);
|
|
5011
|
-
block-size: var(--dialog-icon-glyph-size);
|
|
5012
|
-
font-size: var(--dialog-icon-glyph-size);
|
|
5052
|
+
inline-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
5053
|
+
block-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
5054
|
+
font-size: var(--dialog-icon-glyph-size, 1.5rem);
|
|
5013
5055
|
line-height: 1;
|
|
5014
5056
|
}
|
|
5015
5057
|
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
:where(dialog).modal.dialog-confirmation > form > .media > header > * {
|
|
5022
|
-
margin-block: 0;
|
|
5023
|
-
}
|
|
5024
|
-
|
|
5025
|
-
/* The first heading occupies the icon row, so its text centers on the icon
|
|
5026
|
-
without a font-relative nudge. Following copy stays in the same column. */
|
|
5027
|
-
:where(dialog).modal.dialog-confirmation
|
|
5028
|
-
> form
|
|
5029
|
-
> .media
|
|
5030
|
-
> header
|
|
5031
|
-
> :is(h1, h2, h3, h4, h5, h6):first-child {
|
|
5058
|
+
/* A leading icon well centers the first heading of the text column beside it:
|
|
5059
|
+
the heading occupies the icon row, so its text centers without a
|
|
5060
|
+
font-relative nudge, and following copy stays in the same column. Keyed on
|
|
5061
|
+
the media anatomy, not on the dialog, so a card or page row composes it too. */
|
|
5062
|
+
.media > .dialog-icon + * > :is(h1, h2, h3, h4, h5, h6):first-child {
|
|
5032
5063
|
display: flex;
|
|
5033
5064
|
align-items: center;
|
|
5034
5065
|
min-block-size: var(--dialog-icon-size, 3rem);
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
@media (max-width: 30rem) {
|
|
5038
|
-
:where(dialog).modal.dialog-confirmation > form > :is(.media, header),
|
|
5039
|
-
:where(dialog).modal.dialog-confirmation > form > footer {
|
|
5040
|
-
padding: var(--space-40);
|
|
5041
|
-
}
|
|
5066
|
+
margin-block: 0;
|
|
5042
5067
|
}
|
|
5043
5068
|
|
|
5044
5069
|
:where(dialog).modal[open] {
|
|
5045
5070
|
opacity: 1;
|
|
5046
5071
|
transform: none;
|
|
5047
|
-
transition:
|
|
5048
|
-
opacity var(--duration) var(--ease-enter),
|
|
5049
|
-
transform var(--duration) var(--ease-enter);
|
|
5072
|
+
transition-timing-function: var(--ease-enter);
|
|
5050
5073
|
}
|
|
5051
5074
|
|
|
5052
5075
|
@starting-style {
|
|
@@ -5061,31 +5084,22 @@ a.badge[aria-disabled="true"] {
|
|
|
5061
5084
|
opacity: 0;
|
|
5062
5085
|
/* Scrim curve stays neutral `ease`; only the panel carries the character
|
|
5063
5086
|
curves. */
|
|
5064
|
-
transition: opacity
|
|
5087
|
+
transition-property: opacity;
|
|
5088
|
+
transition-duration: var(--duration);
|
|
5089
|
+
transition-timing-function: ease;
|
|
5065
5090
|
}
|
|
5066
5091
|
|
|
5067
5092
|
@supports (transition-behavior: allow-discrete) {
|
|
5093
|
+
/* Add the discrete top-layer pair to the property list. Duration and timing
|
|
5094
|
+
stay on the base rules, so [open] still only swaps the easing. */
|
|
5068
5095
|
:where(dialog).modal {
|
|
5069
|
-
transition:
|
|
5070
|
-
|
|
5071
|
-
transform var(--duration) var(--ease-exit),
|
|
5072
|
-
overlay var(--duration) allow-discrete,
|
|
5073
|
-
display var(--duration) allow-discrete;
|
|
5074
|
-
}
|
|
5075
|
-
|
|
5076
|
-
:where(dialog).modal[open] {
|
|
5077
|
-
transition:
|
|
5078
|
-
opacity var(--duration) var(--ease-enter),
|
|
5079
|
-
transform var(--duration) var(--ease-enter),
|
|
5080
|
-
overlay var(--duration) allow-discrete,
|
|
5081
|
-
display var(--duration) allow-discrete;
|
|
5096
|
+
transition-property: opacity, transform, overlay, display;
|
|
5097
|
+
transition-behavior: allow-discrete;
|
|
5082
5098
|
}
|
|
5083
5099
|
|
|
5084
5100
|
:where(dialog).modal::backdrop {
|
|
5085
|
-
transition:
|
|
5086
|
-
|
|
5087
|
-
overlay var(--duration) allow-discrete,
|
|
5088
|
-
display var(--duration) allow-discrete;
|
|
5101
|
+
transition-property: opacity, overlay, display;
|
|
5102
|
+
transition-behavior: allow-discrete;
|
|
5089
5103
|
}
|
|
5090
5104
|
}
|
|
5091
5105
|
|
|
@@ -5159,6 +5173,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
5159
5173
|
* Internal:
|
|
5160
5174
|
* --drawer-closed-translate (side/RTL positioning state)
|
|
5161
5175
|
* --drawer-focus-bleed
|
|
5176
|
+
* --drawer-close-reserve (header room for a corner close; zero without a close)
|
|
5162
5177
|
* --drawer-motion-duration (shared by panel and ::backdrop so exit duration
|
|
5163
5178
|
* changes in one place; entry state redefines it longer)
|
|
5164
5179
|
*/
|
|
@@ -5168,6 +5183,10 @@ html.has-modal-open.had-scrollbar {
|
|
|
5168
5183
|
/* The inset a direct .bleed child cancels (components/bleed.css). */
|
|
5169
5184
|
--surface-pad: var(--drawer-pad);
|
|
5170
5185
|
--drawer-closed-translate: -100%;
|
|
5186
|
+
/* Header room for a corner close. Unconditional baseline: without :has()
|
|
5187
|
+
(Degraded tier) a header keeps too much room rather than a title running
|
|
5188
|
+
under the close. Zeroed when the drawer has no close. */
|
|
5189
|
+
--drawer-close-reserve: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
5171
5190
|
/* Exit is shorter than entry so the panel clears quickly; the scrim reads
|
|
5172
5191
|
the same variable through inheritance and never lags the panel. */
|
|
5173
5192
|
--drawer-motion-duration: var(--duration);
|
|
@@ -5347,11 +5366,22 @@ html.has-modal-open.had-scrollbar {
|
|
|
5347
5366
|
}
|
|
5348
5367
|
}
|
|
5349
5368
|
|
|
5369
|
+
/* No close anywhere: reclaim the reserve. Deliberately coarse — a close in
|
|
5370
|
+
the body keeps the reserve, which costs only room, never overlap. */
|
|
5371
|
+
:where(dialog).drawer:not(:has(.close)) {
|
|
5372
|
+
--drawer-close-reserve: 0px;
|
|
5373
|
+
}
|
|
5374
|
+
|
|
5350
5375
|
/* The header reserves inline room so the title never runs under the close
|
|
5351
|
-
control. */
|
|
5376
|
+
control. A .bleed header keeps at least its band inset. */
|
|
5352
5377
|
:where(dialog).drawer > header,
|
|
5353
|
-
:where(dialog).drawer > form > header {
|
|
5354
|
-
padding-inline-end:
|
|
5378
|
+
:where(dialog).drawer > :where(form) > header {
|
|
5379
|
+
padding-inline-end: var(--drawer-close-reserve);
|
|
5380
|
+
}
|
|
5381
|
+
|
|
5382
|
+
:where(dialog).drawer > header:where(.bleed),
|
|
5383
|
+
:where(dialog).drawer > :where(form) > header:where(.bleed) {
|
|
5384
|
+
padding-inline-end: max(var(--bleed-pad, 0px), var(--drawer-close-reserve));
|
|
5355
5385
|
}
|
|
5356
5386
|
|
|
5357
5387
|
/*
|
|
@@ -5488,6 +5518,9 @@ picture.bleed > img {
|
|
|
5488
5518
|
position: absolute;
|
|
5489
5519
|
inset-block-start: calc(100% + var(--space-10));
|
|
5490
5520
|
inset-inline-start: 0;
|
|
5521
|
+
/* Anatomy, not a default to replace: `flyout grid-3` (mega menu) takes its
|
|
5522
|
+
columns from the grid preset and keeps this tight gap, because each
|
|
5523
|
+
section pads itself — the visible gutter is section padding + gap. */
|
|
5491
5524
|
display: grid;
|
|
5492
5525
|
gap: var(--space-10);
|
|
5493
5526
|
inline-size: var(--flyout-inline-size, max-content);
|
|
@@ -6684,19 +6717,26 @@ a.avatar[aria-disabled="true"] {
|
|
|
6684
6717
|
--avatar-stack-font-size: var(--font-size-lg);
|
|
6685
6718
|
}
|
|
6686
6719
|
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6720
|
+
/* Infer the stack scale from its avatars. Progressive: without :has() the
|
|
6721
|
+
explicit .avatar-stack.sm/.lg classes and the default size still size the
|
|
6722
|
+
group, only the inference is lost. */
|
|
6723
|
+
.avatar-stack:has(> .avatar.sm) {
|
|
6724
|
+
--avatar-stack-size: 1.75rem;
|
|
6725
|
+
--avatar-stack-font-size: var(--font-size-xs);
|
|
6726
|
+
}
|
|
6692
6727
|
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
}
|
|
6728
|
+
.avatar-stack:has(> .avatar.lg) {
|
|
6729
|
+
--avatar-stack-size: 3rem;
|
|
6730
|
+
--avatar-stack-font-size: var(--font-size-lg);
|
|
6697
6731
|
}
|
|
6698
6732
|
|
|
6733
|
+
/* Overlapping avatars hide their neighbour: a translucent fill (--surface-subtle
|
|
6734
|
+
on a glass palette) would show the next avatar's edge through it, most of all
|
|
6735
|
+
when hover raises one above the other. The fill is layered over the opaque
|
|
6736
|
+
role; on an opaque fill the base never shows. */
|
|
6699
6737
|
.avatar-stack > .avatar {
|
|
6738
|
+
background-color: var(--surface-opaque);
|
|
6739
|
+
background-image: linear-gradient(var(--avatar-bg), var(--avatar-bg));
|
|
6700
6740
|
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface-opaque);
|
|
6701
6741
|
}
|
|
6702
6742
|
|
|
@@ -6917,11 +6957,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
6917
6957
|
}
|
|
6918
6958
|
|
|
6919
6959
|
/* Remove the reserved avatar gutter when a message has no avatar. This is a
|
|
6920
|
-
cosmetic enhancement; the two-column baseline remains fully readable
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
}
|
|
6960
|
+
cosmetic enhancement; the two-column baseline remains fully readable, and a
|
|
6961
|
+
browser without :has() drops only this rule. */
|
|
6962
|
+
.chat:not(:has(> .chat-avatar)) {
|
|
6963
|
+
column-gap: 0;
|
|
6925
6964
|
}
|
|
6926
6965
|
|
|
6927
6966
|
/*
|
|
@@ -7620,8 +7659,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
7620
7659
|
*
|
|
7621
7660
|
* Docs: docs/pages/components/navbar.md
|
|
7622
7661
|
*
|
|
7623
|
-
* .navbar is a horizontal bar; .navbar-brand is the brand link;
|
|
7624
|
-
*
|
|
7662
|
+
* .navbar is a horizontal bar; .navbar-brand is the brand link; the inline
|
|
7663
|
+
* links are a .cluster using .nav-link items. .nav-list is the vertical nav
|
|
7625
7664
|
* list shared with .drawer, fed by the shared --gap token.
|
|
7626
7665
|
*
|
|
7627
7666
|
* Internal:
|
|
@@ -7670,17 +7709,6 @@ a.avatar[aria-disabled="true"] {
|
|
|
7670
7709
|
border-radius: var(--radius-sm);
|
|
7671
7710
|
}
|
|
7672
7711
|
|
|
7673
|
-
.navbar-nav {
|
|
7674
|
-
display: flex;
|
|
7675
|
-
flex-direction: row;
|
|
7676
|
-
flex-wrap: wrap;
|
|
7677
|
-
align-items: center;
|
|
7678
|
-
gap: var(--space-10);
|
|
7679
|
-
margin: 0;
|
|
7680
|
-
padding: 0;
|
|
7681
|
-
list-style: none;
|
|
7682
|
-
}
|
|
7683
|
-
|
|
7684
7712
|
.nav-link {
|
|
7685
7713
|
display: inline-flex;
|
|
7686
7714
|
align-items: center;
|
|
@@ -7703,19 +7731,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
7703
7731
|
removed everywhere else, never serialized as aria-current="false".
|
|
7704
7732
|
Selection never changes text metrics: the shared medium weight is kept and
|
|
7705
7733
|
the current link is carried by the selected accent. */
|
|
7706
|
-
.nav-link[aria-current] {
|
|
7707
|
-
background: var(--surface-subtle);
|
|
7708
|
-
color: var(--state-selected);
|
|
7709
|
-
}
|
|
7710
|
-
|
|
7711
7734
|
.nav-link:hover {
|
|
7712
7735
|
background: var(--surface-subtle);
|
|
7713
7736
|
color: var(--text);
|
|
7714
7737
|
}
|
|
7715
7738
|
|
|
7716
|
-
/*
|
|
7717
|
-
|
|
7718
|
-
|
|
7739
|
+
/* Current last, equal specificity to :hover (0,2,0): a hovered current link
|
|
7740
|
+
keeps the selected accent instead of dropping to --text. The surface fill is
|
|
7741
|
+
shared with the hover rule above. */
|
|
7742
|
+
.nav-link[aria-current] {
|
|
7743
|
+
background: var(--surface-subtle);
|
|
7719
7744
|
color: var(--state-selected);
|
|
7720
7745
|
}
|
|
7721
7746
|
|
|
@@ -7735,19 +7760,22 @@ a.avatar[aria-disabled="true"] {
|
|
|
7735
7760
|
color: var(--ui-fg, var(--text));
|
|
7736
7761
|
}
|
|
7737
7762
|
|
|
7738
|
-
.
|
|
7739
|
-
.nav-link[aria-current]
|
|
7763
|
+
/* After :hover so a hovered current link keeps the selected accent. */
|
|
7764
|
+
.nav-link[aria-current] {
|
|
7740
7765
|
color: var(--ui-fg, var(--state-selected));
|
|
7741
7766
|
}
|
|
7742
7767
|
}
|
|
7743
7768
|
|
|
7744
7769
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
7745
7770
|
Links fill each row so the whole line is an interactive target; the
|
|
7746
|
-
horizontal .
|
|
7771
|
+
horizontal .cluster links keep their intrinsic size. */
|
|
7772
|
+
/* A vertical nav reads as one list, not a stack of separate buttons: each row
|
|
7773
|
+
is already a control-height target, so the rows sit a hair apart. The grid
|
|
7774
|
+
is the component's own layout; for a horizontal row use .cluster instead. */
|
|
7747
7775
|
.nav-list {
|
|
7748
7776
|
display: grid;
|
|
7749
7777
|
grid-template-columns: minmax(0, 1fr);
|
|
7750
|
-
--gap: var(--space-
|
|
7778
|
+
--gap: var(--space-10);
|
|
7751
7779
|
gap: var(--gap);
|
|
7752
7780
|
margin: 0;
|
|
7753
7781
|
padding: 0;
|
|
@@ -8867,6 +8895,11 @@ a.list-item[aria-current]::before {
|
|
|
8867
8895
|
align-items: end;
|
|
8868
8896
|
}
|
|
8869
8897
|
|
|
8898
|
+
.items-baseline {
|
|
8899
|
+
--items-align: baseline;
|
|
8900
|
+
align-items: baseline;
|
|
8901
|
+
}
|
|
8902
|
+
|
|
8870
8903
|
.muted {
|
|
8871
8904
|
color: var(--text-muted);
|
|
8872
8905
|
}
|
|
@@ -8988,9 +9021,6 @@ a.list-item[aria-current]::before {
|
|
|
8988
9021
|
* - utilities consume the current context instead of defining their own -sm/-lg scale.
|
|
8989
9022
|
* - optional utilities extend core utilities, they do not re-express them.
|
|
8990
9023
|
* - follow verbose .{property}-{value} naming.
|
|
8991
|
-
*
|
|
8992
|
-
* Internal:
|
|
8993
|
-
* --cluster-justify (local relay consumed by .cluster)
|
|
8994
9024
|
*/
|
|
8995
9025
|
|
|
8996
9026
|
/* Display */
|
|
@@ -9065,23 +9095,22 @@ a.list-item[aria-current]::before {
|
|
|
9065
9095
|
flex-wrap: nowrap;
|
|
9066
9096
|
}
|
|
9067
9097
|
|
|
9098
|
+
/* The property is written directly, never relayed through the cluster hook:
|
|
9099
|
+
a relay would inherit into every .cluster nested inside. Utilities load last,
|
|
9100
|
+
so the direct declaration already beats .cluster at equal specificity. */
|
|
9068
9101
|
.justify-content-start {
|
|
9069
|
-
--cluster-justify: flex-start;
|
|
9070
9102
|
justify-content: flex-start;
|
|
9071
9103
|
}
|
|
9072
9104
|
|
|
9073
9105
|
.justify-content-center {
|
|
9074
|
-
--cluster-justify: center;
|
|
9075
9106
|
justify-content: center;
|
|
9076
9107
|
}
|
|
9077
9108
|
|
|
9078
9109
|
.justify-content-end {
|
|
9079
|
-
--cluster-justify: flex-end;
|
|
9080
9110
|
justify-content: flex-end;
|
|
9081
9111
|
}
|
|
9082
9112
|
|
|
9083
9113
|
.justify-content-space-between {
|
|
9084
|
-
--cluster-justify: space-between;
|
|
9085
9114
|
justify-content: space-between;
|
|
9086
9115
|
}
|
|
9087
9116
|
|