actual-css 0.11.0 → 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 +73 -0
- package/README.md +29 -5
- package/components.json +15 -17
- package/dist/actual.full.css +403 -227
- 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 +4 -2
- package/docs/pages/components/alert.md +2 -10
- package/docs/pages/components/badge.md +3 -9
- package/docs/pages/components/bleed.md +5 -4
- package/docs/pages/components/card.md +31 -2
- package/docs/pages/components/close.md +9 -3
- package/docs/pages/components/dialog.md +248 -31
- package/docs/pages/components/drawer.md +2 -2
- package/docs/pages/components/meter.md +2 -0
- 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 +3 -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 +36 -0
- 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 +3 -2
- package/reserved-classes.json +3 -3
- package/skills/actual-css/references/enhancements.md +38 -0
- package/src/cli/actual-css.js +75 -6
- package/src/css/components/avatar.css +16 -9
- package/src/css/components/badge.css +1 -5
- package/src/css/components/bleed.css +5 -5
- package/src/css/components/button.css +17 -10
- package/src/css/components/card.css +18 -10
- package/src/css/components/chat.css +4 -5
- package/src/css/components/close.css +17 -6
- 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/meter.css +5 -2
- package/src/css/components/modal.css +157 -69
- 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/forms/native.css +5 -1
- 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-components.js +286 -0
- package/src/tooling/design-export.js +69 -0
- package/src/tooling/design-tokens.js +474 -0
- package/src/tooling/penpot-plugin/manifest.json +7 -0
- package/src/tooling/penpot-plugin/plugin.js +272 -0
- package/src/tooling/penpot-plugin/ui.html +58 -0
- 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
|
|
@@ -2448,7 +2515,11 @@ small {
|
|
|
2448
2515
|
the whole control, not just the filename text. */
|
|
2449
2516
|
min-block-size: var(--control-size);
|
|
2450
2517
|
padding-block: var(--space-10);
|
|
2451
|
-
|
|
2518
|
+
/* Border-box height: both borders come out of the padding, as on .btn. */
|
|
2519
|
+
padding-block: max(
|
|
2520
|
+
var(--space-10),
|
|
2521
|
+
calc((var(--control-size) - 1lh - 2 * var(--border-width)) / 2)
|
|
2522
|
+
);
|
|
2452
2523
|
padding-inline: var(--space-20);
|
|
2453
2524
|
border: var(--border-width) solid var(--border);
|
|
2454
2525
|
border-radius: var(--radius-sm);
|
|
@@ -2625,32 +2696,37 @@ small {
|
|
|
2625
2696
|
}
|
|
2626
2697
|
|
|
2627
2698
|
/*
|
|
2628
|
-
* Form actions —
|
|
2699
|
+
* Form actions — the role of a .cluster action row that closes a form.
|
|
2629
2700
|
*
|
|
2630
2701
|
* Docs: docs/pages/forms/form-actions.md
|
|
2631
2702
|
*
|
|
2632
|
-
*
|
|
2633
|
-
*
|
|
2634
|
-
*
|
|
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.
|
|
2635
2710
|
*
|
|
2636
2711
|
* Public hooks:
|
|
2637
2712
|
* --form-actions-align
|
|
2638
2713
|
* --form-actions-justify
|
|
2639
|
-
* --form-actions-margin-block-start
|
|
2640
2714
|
*
|
|
2641
2715
|
* Internal:
|
|
2642
2716
|
* --form-actions-sticky-block-offset (owned by .form-actions.sticky)
|
|
2643
2717
|
* --form-actions-sticky-inline-offset (owned by .form-actions.sticky)
|
|
2644
2718
|
* --form-actions-sticky-padding (owned by .form-actions.sticky)
|
|
2645
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. */
|
|
2646
2727
|
.form-actions {
|
|
2647
|
-
display: flex;
|
|
2648
|
-
flex-direction: row;
|
|
2649
|
-
flex-wrap: wrap;
|
|
2650
|
-
gap: var(--gap);
|
|
2651
2728
|
align-items: var(--form-actions-align, center);
|
|
2652
2729
|
justify-content: var(--form-actions-justify, flex-start);
|
|
2653
|
-
margin-block-start: var(--form-actions-margin-block-start, var(--space-50));
|
|
2654
2730
|
}
|
|
2655
2731
|
|
|
2656
2732
|
/* Opt-in sticky variant. When the bar is not stuck (at rest) the translucent
|
|
@@ -3732,9 +3808,9 @@ small {
|
|
|
3732
3808
|
* --btn-bg
|
|
3733
3809
|
* --btn-fg
|
|
3734
3810
|
* --btn-border
|
|
3735
|
-
* --btn-default-hover-bg
|
|
3736
3811
|
* --btn-border-block-end-color
|
|
3737
3812
|
* --btn-border-block-end-width
|
|
3813
|
+
* --btn-border-block
|
|
3738
3814
|
* --spinner-width
|
|
3739
3815
|
*/
|
|
3740
3816
|
|
|
@@ -3757,9 +3833,12 @@ small {
|
|
|
3757
3833
|
--btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
|
|
3758
3834
|
--btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
|
|
3759
3835
|
--btn-border: var(--ui-border, var(--btn-default-border, transparent));
|
|
3760
|
-
--btn-default-hover-bg: var(--btn-bg);
|
|
3761
3836
|
--btn-radius: var(--radius);
|
|
3762
|
-
--btn-hover-overlay: var(--hover-overlay,
|
|
3837
|
+
--btn-hover-overlay: var(--hover-overlay-solid, var(--hover-overlay));
|
|
3838
|
+
--btn-border-block: calc(
|
|
3839
|
+
var(--border-width) +
|
|
3840
|
+
var(--btn-border-block-end-width, var(--border-width))
|
|
3841
|
+
);
|
|
3763
3842
|
|
|
3764
3843
|
/* Positioning context for indicators, overlays, and grouped states. */
|
|
3765
3844
|
position: relative;
|
|
@@ -3769,7 +3848,13 @@ small {
|
|
|
3769
3848
|
gap: var(--btn-gap, 0.375em);
|
|
3770
3849
|
min-block-size: var(--btn-min-size);
|
|
3771
3850
|
padding-block: var(--space-20);
|
|
3772
|
-
|
|
3851
|
+
/* why: centres one line in --btn-min-size and keeps that rhythm when the
|
|
3852
|
+
label wraps. min-block-size is a border-box length, so both borders come
|
|
3853
|
+
out of it; without them .btn stood 2px taller than .input. */
|
|
3854
|
+
padding-block: max(
|
|
3855
|
+
var(--space-10),
|
|
3856
|
+
calc((var(--btn-min-size) - 1lh - var(--btn-border-block)) / 2)
|
|
3857
|
+
);
|
|
3773
3858
|
padding-inline: var(--btn-pad-x);
|
|
3774
3859
|
border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
|
|
3775
3860
|
border-block-end-color: var(
|
|
@@ -3829,10 +3914,6 @@ small {
|
|
|
3829
3914
|
}
|
|
3830
3915
|
|
|
3831
3916
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
3832
|
-
.btn {
|
|
3833
|
-
--btn-default-hover-bg: color-mix(in oklch, var(--btn-bg) 88%, black);
|
|
3834
|
-
}
|
|
3835
|
-
|
|
3836
3917
|
.btn.soft {
|
|
3837
3918
|
--ui-hover-bg: color-mix(
|
|
3838
3919
|
in oklab,
|
|
@@ -3848,9 +3929,9 @@ small {
|
|
|
3848
3929
|
}
|
|
3849
3930
|
|
|
3850
3931
|
/* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
|
|
3851
|
-
the overlay darkening for solid
|
|
3932
|
+
the overlay darkening for solid is .btn-specific. */
|
|
3852
3933
|
.btn:not(:where(:disabled, [aria-disabled="true"])):hover {
|
|
3853
|
-
background: var(--ui-hover-bg, var(--btn-
|
|
3934
|
+
background: var(--ui-hover-bg, var(--btn-bg));
|
|
3854
3935
|
/* Huge inset spread paints the overlay over any border-radius without a
|
|
3855
3936
|
pseudo-element; a plain background would replace the fill instead of
|
|
3856
3937
|
darkening it. */
|
|
@@ -3866,6 +3947,8 @@ small {
|
|
|
3866
3947
|
--ui-fg: var(--intent, currentColor);
|
|
3867
3948
|
--ui-border: transparent;
|
|
3868
3949
|
--btn-radius: var(--radius-sm);
|
|
3950
|
+
/* border: 0 below; the padding must not subtract a border it lacks. */
|
|
3951
|
+
--btn-border-block: 0px;
|
|
3869
3952
|
|
|
3870
3953
|
min-block-size: auto;
|
|
3871
3954
|
padding-inline: 0;
|
|
@@ -3957,8 +4040,17 @@ a.btn[aria-disabled="true"] {
|
|
|
3957
4040
|
* Public hooks:
|
|
3958
4041
|
* --close-size
|
|
3959
4042
|
* --close-icon-size
|
|
4043
|
+
* --close-bg
|
|
4044
|
+
* --close-color
|
|
4045
|
+
* --close-hover-color
|
|
4046
|
+
* --close-hover-overlay
|
|
3960
4047
|
*/
|
|
3961
4048
|
.close {
|
|
4049
|
+
/* Routes the hover/active wash, as --btn-hover-overlay does for .btn. A
|
|
4050
|
+
context that wants ink-only feedback (the dialog corner) sets it
|
|
4051
|
+
transparent. */
|
|
4052
|
+
--close-hover-overlay: var(--hover-overlay);
|
|
4053
|
+
|
|
3962
4054
|
display: inline-grid;
|
|
3963
4055
|
place-items: center;
|
|
3964
4056
|
flex: none;
|
|
@@ -3985,12 +4077,12 @@ a.btn[aria-disabled="true"] {
|
|
|
3985
4077
|
the control (the dialog corner's --surface-subtle) instead of replacing it. */
|
|
3986
4078
|
@media (hover: hover) {
|
|
3987
4079
|
.close:hover {
|
|
3988
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4080
|
+
box-shadow: inset 0 0 0 999px var(--close-hover-overlay);
|
|
3989
4081
|
}
|
|
3990
4082
|
}
|
|
3991
4083
|
|
|
3992
4084
|
.close:active {
|
|
3993
|
-
box-shadow: inset 0 0 0 999px var(--hover-overlay);
|
|
4085
|
+
box-shadow: inset 0 0 0 999px var(--close-hover-overlay);
|
|
3994
4086
|
}
|
|
3995
4087
|
|
|
3996
4088
|
.close:focus-visible {
|
|
@@ -4002,23 +4094,25 @@ a.btn[aria-disabled="true"] {
|
|
|
4002
4094
|
surface rather than to the header or a scroll container inside it. Matched
|
|
4003
4095
|
on the documented positions (the dialog, its form, its header, or a form
|
|
4004
4096
|
inside that header) so a .close inside content — an alert in the body —
|
|
4005
|
-
keeps its own place. */
|
|
4097
|
+
keeps its own place. The corner drops the wash: only the ink changes. */
|
|
4006
4098
|
:where(dialog):is(.modal, .drawer) > .close,
|
|
4007
4099
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close,
|
|
4008
4100
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close {
|
|
4101
|
+
--close-hover-overlay: transparent;
|
|
4102
|
+
|
|
4009
4103
|
position: absolute;
|
|
4010
4104
|
z-index: 1;
|
|
4011
4105
|
inset-block-start: var(--space-20);
|
|
4012
4106
|
inset-inline-end: var(--space-20);
|
|
4013
|
-
background: var(--surface-subtle);
|
|
4014
|
-
color: var(--text-muted);
|
|
4107
|
+
background: var(--close-bg, var(--surface-subtle));
|
|
4108
|
+
color: var(--close-color, var(--text-muted));
|
|
4015
4109
|
}
|
|
4016
4110
|
|
|
4017
4111
|
@media (hover: hover) {
|
|
4018
4112
|
:where(dialog):is(.modal, .drawer) > .close:hover,
|
|
4019
4113
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > .close:hover,
|
|
4020
4114
|
:where(dialog):is(.modal, .drawer) > :is(form, header) > :is(form, header) > .close:hover {
|
|
4021
|
-
color: var(--text);
|
|
4115
|
+
color: var(--close-hover-color, var(--text));
|
|
4022
4116
|
}
|
|
4023
4117
|
}
|
|
4024
4118
|
|
|
@@ -4202,10 +4296,6 @@ a.btn[aria-disabled="true"] {
|
|
|
4202
4296
|
}
|
|
4203
4297
|
|
|
4204
4298
|
@supports (color: color-mix(in oklch, red, white)) {
|
|
4205
|
-
:is(a.badge, button.badge):is(.solid, :empty) {
|
|
4206
|
-
--ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
|
|
4207
|
-
}
|
|
4208
|
-
|
|
4209
4299
|
:is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
|
|
4210
4300
|
--badge-default-hover-bg: color-mix(
|
|
4211
4301
|
in oklab,
|
|
@@ -4232,7 +4322,7 @@ a.btn[aria-disabled="true"] {
|
|
|
4232
4322
|
:where(:disabled, [aria-disabled="true"])
|
|
4233
4323
|
):hover {
|
|
4234
4324
|
/* Huge inset spread paints the overlay over the pill radius (see button.css). */
|
|
4235
|
-
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));
|
|
4236
4326
|
}
|
|
4237
4327
|
|
|
4238
4328
|
button.badge {
|
|
@@ -4533,34 +4623,6 @@ a.badge[aria-disabled="true"] {
|
|
|
4533
4623
|
}
|
|
4534
4624
|
}
|
|
4535
4625
|
|
|
4536
|
-
/*
|
|
4537
|
-
* Actions — a horizontal action-button cluster.
|
|
4538
|
-
*
|
|
4539
|
-
* Docs: docs/pages/patterns/actions.md
|
|
4540
|
-
*
|
|
4541
|
-
* Composes with .btn (or any inline element) and feeds layout via the shared
|
|
4542
|
-
* --gap token.
|
|
4543
|
-
*
|
|
4544
|
-
* Internal:
|
|
4545
|
-
* --gap (local value for the shared gap token)
|
|
4546
|
-
*/
|
|
4547
|
-
.actions {
|
|
4548
|
-
--gap: var(--space-20);
|
|
4549
|
-
|
|
4550
|
-
display: flex;
|
|
4551
|
-
flex-direction: row;
|
|
4552
|
-
flex-wrap: wrap;
|
|
4553
|
-
align-items: center;
|
|
4554
|
-
gap: var(--gap);
|
|
4555
|
-
margin: 0;
|
|
4556
|
-
padding: 0;
|
|
4557
|
-
list-style: none;
|
|
4558
|
-
}
|
|
4559
|
-
|
|
4560
|
-
.actions > li {
|
|
4561
|
-
margin: 0;
|
|
4562
|
-
}
|
|
4563
|
-
|
|
4564
4626
|
|
|
4565
4627
|
/* Surfaces */
|
|
4566
4628
|
/*
|
|
@@ -4569,11 +4631,11 @@ a.badge[aria-disabled="true"] {
|
|
|
4569
4631
|
* Docs: docs/pages/components/card.md
|
|
4570
4632
|
*
|
|
4571
4633
|
* Surface, border, radius, padding, density, and elevation stay local here.
|
|
4572
|
-
* Direct header/footer children are structural slots
|
|
4573
|
-
* anchored to the card's bottom edge when extra block space
|
|
4574
|
-
* price/action rows align across equal-height cards in a
|
|
4575
|
-
* owns its direct-child rhythm
|
|
4576
|
-
* 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
|
|
4577
4639
|
* primitives outside the card.
|
|
4578
4640
|
*
|
|
4579
4641
|
* Public hooks:
|
|
@@ -4611,6 +4673,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4611
4673
|
|
|
4612
4674
|
.card {
|
|
4613
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). */
|
|
4614
4679
|
--card-gap: var(--space-30);
|
|
4615
4680
|
--card-radius: var(--radius-lg);
|
|
4616
4681
|
--card-max-inline-size: none;
|
|
@@ -4652,7 +4717,11 @@ a.badge[aria-disabled="true"] {
|
|
|
4652
4717
|
margin-block: 0;
|
|
4653
4718
|
}
|
|
4654
4719
|
|
|
4655
|
-
|
|
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) {
|
|
4656
4725
|
display: flex;
|
|
4657
4726
|
flex-wrap: wrap;
|
|
4658
4727
|
align-items: var(--items-align, center);
|
|
@@ -4661,15 +4730,15 @@ a.badge[aria-disabled="true"] {
|
|
|
4661
4730
|
min-inline-size: 0;
|
|
4662
4731
|
}
|
|
4663
4732
|
|
|
4733
|
+
:where(.card > header) {
|
|
4734
|
+
align-items: var(--items-align, flex-start);
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4664
4737
|
.card > :where(header, footer) > * {
|
|
4665
4738
|
min-inline-size: 0;
|
|
4666
4739
|
margin-block: 0;
|
|
4667
4740
|
}
|
|
4668
4741
|
|
|
4669
|
-
.card > header {
|
|
4670
|
-
align-items: var(--items-align, flex-start);
|
|
4671
|
-
}
|
|
4672
|
-
|
|
4673
4742
|
/* A direct footer is pinned to the bottom when the card has free space.
|
|
4674
4743
|
Specificity beats .stack > * so a .card.stack keeps this contract. */
|
|
4675
4744
|
.card > footer {
|
|
@@ -4684,6 +4753,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4684
4753
|
}
|
|
4685
4754
|
.card.compact {
|
|
4686
4755
|
--card-pad: var(--space-20);
|
|
4756
|
+
--card-gap: var(--space-20);
|
|
4687
4757
|
}
|
|
4688
4758
|
|
|
4689
4759
|
@media print {
|
|
@@ -4703,13 +4773,20 @@ a.badge[aria-disabled="true"] {
|
|
|
4703
4773
|
*
|
|
4704
4774
|
* Public hooks:
|
|
4705
4775
|
* --modal-size
|
|
4776
|
+
* --modal-pad
|
|
4706
4777
|
* --dialog-viewport-gap
|
|
4707
4778
|
* --dialog-icon-size
|
|
4708
4779
|
* --dialog-icon-glyph-size
|
|
4709
4780
|
*
|
|
4710
4781
|
* The corner close control is .close (components/close.css).
|
|
4711
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
|
+
*
|
|
4712
4787
|
* Internal:
|
|
4788
|
+
* --dialog-inner-radius
|
|
4789
|
+
* --dialog-close-reserve (header room for a corner close; zero without a close)
|
|
4713
4790
|
* --dialog-focus-bleed
|
|
4714
4791
|
* --dialog-viewport-block
|
|
4715
4792
|
* --dialog-viewport-gap-default
|
|
@@ -4718,16 +4795,28 @@ a.badge[aria-disabled="true"] {
|
|
|
4718
4795
|
:where(dialog).modal {
|
|
4719
4796
|
/* --dialog-viewport-gap stays fallback-only so the responsive default can
|
|
4720
4797
|
live in an internal token: a media query moves it without closing the
|
|
4721
|
-
public hook.
|
|
4722
|
-
|
|
4723
|
-
calc() multiplication is not in the supported browser range, so the
|
|
4724
|
-
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. */
|
|
4725
4800
|
--dialog-viewport-gap-default: var(--space-40);
|
|
4726
4801
|
--dialog-viewport-gap-2x: calc(
|
|
4727
4802
|
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default)) +
|
|
4728
4803
|
var(--dialog-viewport-gap, var(--dialog-viewport-gap-default))
|
|
4729
4804
|
);
|
|
4730
4805
|
|
|
4806
|
+
/* The panel is borderless, so its inner and outer radius are the same. Kept
|
|
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. */
|
|
4809
|
+
--dialog-inner-radius: var(--radius-lg);
|
|
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
|
+
|
|
4731
4820
|
/* Keep the surface anchored to the viewport in the top layer. This also
|
|
4732
4821
|
provides the containing block for the absolutely positioned close button. */
|
|
4733
4822
|
position: fixed;
|
|
@@ -4741,7 +4830,9 @@ a.badge[aria-disabled="true"] {
|
|
|
4741
4830
|
dynamic token; only the modal pins the small viewport. */
|
|
4742
4831
|
--dialog-viewport-block: var(--viewport-block);
|
|
4743
4832
|
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
4744
|
-
|
|
4833
|
+
/* Zero rather than absent: the UA stylesheet draws `border: solid` on
|
|
4834
|
+
dialog, and an authored edge reads as a hard seam against .bleed bands. */
|
|
4835
|
+
border: 0;
|
|
4745
4836
|
border-radius: var(--radius-lg);
|
|
4746
4837
|
background: var(--surface-raised);
|
|
4747
4838
|
color: var(--text);
|
|
@@ -4758,19 +4849,81 @@ a.badge[aria-disabled="true"] {
|
|
|
4758
4849
|
* both edges, which reads as horizontal movement.
|
|
4759
4850
|
*/
|
|
4760
4851
|
transform: translateY(0.35rem);
|
|
4761
|
-
transition:
|
|
4762
|
-
|
|
4763
|
-
|
|
4852
|
+
transition-property: opacity, transform;
|
|
4853
|
+
transition-duration: var(--duration);
|
|
4854
|
+
transition-timing-function: var(--ease-exit);
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4857
|
+
@media (forced-colors: active) {
|
|
4858
|
+
/* With no authored border the panel edge disappears once colors collapse.
|
|
4859
|
+
The browser forces the border color, so only the width is authored. */
|
|
4860
|
+
:where(dialog).modal {
|
|
4861
|
+
border: var(--border-width) solid CanvasText;
|
|
4862
|
+
}
|
|
4764
4863
|
}
|
|
4765
4864
|
|
|
4766
4865
|
:where(dialog).modal > form,
|
|
4767
4866
|
:where(dialog).modal > .stack {
|
|
4867
|
+
--surface-pad: var(--modal-pad, var(--space-40));
|
|
4868
|
+
|
|
4768
4869
|
display: grid;
|
|
4769
4870
|
gap: var(--space-40);
|
|
4770
4871
|
max-block-size: calc(var(--dialog-viewport-block) - var(--dialog-viewport-gap-2x));
|
|
4872
|
+
border-radius: var(--dialog-inner-radius);
|
|
4771
4873
|
overflow: auto;
|
|
4772
4874
|
overscroll-behavior: contain;
|
|
4773
|
-
padding: var(--
|
|
4875
|
+
padding: var(--surface-pad);
|
|
4876
|
+
}
|
|
4877
|
+
|
|
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. */
|
|
4880
|
+
:where(dialog).modal > header {
|
|
4881
|
+
display: flex;
|
|
4882
|
+
align-items: center;
|
|
4883
|
+
justify-content: space-between;
|
|
4884
|
+
gap: var(--space-30);
|
|
4885
|
+
min-block-size: var(--close-size, var(--control-size));
|
|
4886
|
+
padding: var(--modal-pad, var(--space-40));
|
|
4887
|
+
border-start-start-radius: var(--dialog-inner-radius);
|
|
4888
|
+
border-start-end-radius: var(--dialog-inner-radius);
|
|
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);
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4899
|
+
:where(dialog).modal > header > .media {
|
|
4900
|
+
flex: 1 1 auto;
|
|
4901
|
+
min-inline-size: 0;
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
:where(dialog).modal > header > .media > :is(h1, h2, h3, h4, h5, h6) {
|
|
4905
|
+
display: flex;
|
|
4906
|
+
align-items: center;
|
|
4907
|
+
min-block-size: var(--close-size, var(--control-size));
|
|
4908
|
+
margin-block: 0;
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
:where(dialog).modal > header > .cluster {
|
|
4912
|
+
--cluster-wrap: nowrap;
|
|
4913
|
+
|
|
4914
|
+
flex: none;
|
|
4915
|
+
}
|
|
4916
|
+
|
|
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
|
+
}
|
|
4923
|
+
|
|
4924
|
+
:where(dialog).modal:has(> header) > :is(form, .stack) {
|
|
4925
|
+
min-block-size: 0;
|
|
4926
|
+
max-block-size: none;
|
|
4774
4927
|
}
|
|
4775
4928
|
|
|
4776
4929
|
@supports (block-size: 100svh) {
|
|
@@ -4789,10 +4942,21 @@ a.badge[aria-disabled="true"] {
|
|
|
4789
4942
|
|
|
4790
4943
|
:where(dialog).modal.scrollable > form,
|
|
4791
4944
|
:where(dialog).modal.scrollable > .stack {
|
|
4792
|
-
|
|
4945
|
+
display: flex;
|
|
4946
|
+
flex-direction: column;
|
|
4793
4947
|
overflow: hidden;
|
|
4794
4948
|
}
|
|
4795
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
|
+
|
|
4796
4960
|
:where(dialog).modal.scrollable > form > :not(:is(header, footer)),
|
|
4797
4961
|
:where(dialog).modal.scrollable > .stack > :not(:is(header, footer)) {
|
|
4798
4962
|
--dialog-focus-bleed: calc(
|
|
@@ -4800,6 +4964,7 @@ a.badge[aria-disabled="true"] {
|
|
|
4800
4964
|
max(var(--focus-ring-width), var(--border-width) * 2)
|
|
4801
4965
|
);
|
|
4802
4966
|
|
|
4967
|
+
flex: 1 1 auto;
|
|
4803
4968
|
min-block-size: 0;
|
|
4804
4969
|
/* Keep focus paint inside the clip without moving the body off the fixed
|
|
4805
4970
|
header and footer edges. The reserve covers the thicker of the two
|
|
@@ -4822,39 +4987,36 @@ a.badge[aria-disabled="true"] {
|
|
|
4822
4987
|
margin-block-end: 0;
|
|
4823
4988
|
}
|
|
4824
4989
|
|
|
4825
|
-
/*
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
:where(dialog).modal > :where(form, .stack) > header {
|
|
4830
|
-
padding-inline-end: calc(var(--close-size, var(--control-size)) + var(--space-30));
|
|
4990
|
+
/* A media row inside the structural header is title anatomy, so its heading
|
|
4991
|
+
does not bring prose margins into the icon alignment. */
|
|
4992
|
+
:where(dialog).modal > :where(form, .stack) > header > .media > :is(h1, h2, h3, h4, h5, h6) {
|
|
4993
|
+
margin-block: 0;
|
|
4831
4994
|
}
|
|
4832
4995
|
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
gap: var(--gap);
|
|
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;
|
|
4838
5000
|
}
|
|
4839
5001
|
|
|
4840
|
-
/*
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
:where(
|
|
4844
|
-
|
|
4845
|
-
|
|
5002
|
+
/* The close button is anchored to the dialog at top inline-end, out of the
|
|
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>. */
|
|
5007
|
+
:where(dialog).modal > :where(form, .stack) > header {
|
|
5008
|
+
padding-inline-end: var(--dialog-close-reserve);
|
|
4846
5009
|
}
|
|
4847
5010
|
|
|
4848
|
-
:where(dialog).modal
|
|
4849
|
-
|
|
4850
|
-
padding: var(--space-50);
|
|
5011
|
+
:where(dialog).modal > :where(form, .stack) > header:where(.bleed) {
|
|
5012
|
+
padding-inline-end: max(var(--bleed-pad, 0px), var(--dialog-close-reserve));
|
|
4851
5013
|
}
|
|
4852
5014
|
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
5015
|
+
/* A corner .close that is a direct child of the chrome header needs the same
|
|
5016
|
+
reserve, but never below the header's own pad. Kept as its own rule so an
|
|
5017
|
+
unsupported :has() drops only this geometry, not the wrapper reserve. */
|
|
5018
|
+
:where(dialog).modal > header:has(> .close) {
|
|
5019
|
+
padding-inline-end: max(var(--modal-pad, var(--space-40)), var(--dialog-close-reserve));
|
|
4858
5020
|
}
|
|
4859
5021
|
|
|
4860
5022
|
/* Intent boundary — inherited intents do not tint the icon well. A local
|
|
@@ -4867,14 +5029,17 @@ a.badge[aria-disabled="true"] {
|
|
|
4867
5029
|
--ui-fg: initial;
|
|
4868
5030
|
}
|
|
4869
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. */
|
|
4870
5038
|
.dialog-icon {
|
|
4871
|
-
--dialog-icon-size: 3rem;
|
|
4872
|
-
--dialog-icon-glyph-size: 1.5rem;
|
|
4873
|
-
|
|
4874
5039
|
display: inline-grid;
|
|
4875
5040
|
place-items: center;
|
|
4876
|
-
inline-size: var(--dialog-icon-size);
|
|
4877
|
-
block-size: var(--dialog-icon-size);
|
|
5041
|
+
inline-size: var(--dialog-icon-size, 3rem);
|
|
5042
|
+
block-size: var(--dialog-icon-size, 3rem);
|
|
4878
5043
|
flex: none;
|
|
4879
5044
|
border-radius: var(--radius-full);
|
|
4880
5045
|
background: var(--ui-bg, var(--surface-subtle));
|
|
@@ -4884,25 +5049,27 @@ a.badge[aria-disabled="true"] {
|
|
|
4884
5049
|
.dialog-icon > * {
|
|
4885
5050
|
display: inline-grid;
|
|
4886
5051
|
place-items: center;
|
|
4887
|
-
inline-size: var(--dialog-icon-glyph-size);
|
|
4888
|
-
block-size: var(--dialog-icon-glyph-size);
|
|
4889
|
-
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);
|
|
4890
5055
|
line-height: 1;
|
|
4891
5056
|
}
|
|
4892
5057
|
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
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 {
|
|
5063
|
+
display: flex;
|
|
5064
|
+
align-items: center;
|
|
5065
|
+
min-block-size: var(--dialog-icon-size, 3rem);
|
|
5066
|
+
margin-block: 0;
|
|
4898
5067
|
}
|
|
4899
5068
|
|
|
4900
5069
|
:where(dialog).modal[open] {
|
|
4901
5070
|
opacity: 1;
|
|
4902
5071
|
transform: none;
|
|
4903
|
-
transition:
|
|
4904
|
-
opacity var(--duration) var(--ease-enter),
|
|
4905
|
-
transform var(--duration) var(--ease-enter);
|
|
5072
|
+
transition-timing-function: var(--ease-enter);
|
|
4906
5073
|
}
|
|
4907
5074
|
|
|
4908
5075
|
@starting-style {
|
|
@@ -4917,31 +5084,22 @@ a.badge[aria-disabled="true"] {
|
|
|
4917
5084
|
opacity: 0;
|
|
4918
5085
|
/* Scrim curve stays neutral `ease`; only the panel carries the character
|
|
4919
5086
|
curves. */
|
|
4920
|
-
transition: opacity
|
|
5087
|
+
transition-property: opacity;
|
|
5088
|
+
transition-duration: var(--duration);
|
|
5089
|
+
transition-timing-function: ease;
|
|
4921
5090
|
}
|
|
4922
5091
|
|
|
4923
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. */
|
|
4924
5095
|
:where(dialog).modal {
|
|
4925
|
-
transition:
|
|
4926
|
-
|
|
4927
|
-
transform var(--duration) var(--ease-exit),
|
|
4928
|
-
overlay var(--duration) allow-discrete,
|
|
4929
|
-
display var(--duration) allow-discrete;
|
|
4930
|
-
}
|
|
4931
|
-
|
|
4932
|
-
:where(dialog).modal[open] {
|
|
4933
|
-
transition:
|
|
4934
|
-
opacity var(--duration) var(--ease-enter),
|
|
4935
|
-
transform var(--duration) var(--ease-enter),
|
|
4936
|
-
overlay var(--duration) allow-discrete,
|
|
4937
|
-
display var(--duration) allow-discrete;
|
|
5096
|
+
transition-property: opacity, transform, overlay, display;
|
|
5097
|
+
transition-behavior: allow-discrete;
|
|
4938
5098
|
}
|
|
4939
5099
|
|
|
4940
5100
|
:where(dialog).modal::backdrop {
|
|
4941
|
-
transition:
|
|
4942
|
-
|
|
4943
|
-
overlay var(--duration) allow-discrete,
|
|
4944
|
-
display var(--duration) allow-discrete;
|
|
5101
|
+
transition-property: opacity, overlay, display;
|
|
5102
|
+
transition-behavior: allow-discrete;
|
|
4945
5103
|
}
|
|
4946
5104
|
}
|
|
4947
5105
|
|
|
@@ -5015,6 +5173,7 @@ html.has-modal-open.had-scrollbar {
|
|
|
5015
5173
|
* Internal:
|
|
5016
5174
|
* --drawer-closed-translate (side/RTL positioning state)
|
|
5017
5175
|
* --drawer-focus-bleed
|
|
5176
|
+
* --drawer-close-reserve (header room for a corner close; zero without a close)
|
|
5018
5177
|
* --drawer-motion-duration (shared by panel and ::backdrop so exit duration
|
|
5019
5178
|
* changes in one place; entry state redefines it longer)
|
|
5020
5179
|
*/
|
|
@@ -5024,6 +5183,10 @@ html.has-modal-open.had-scrollbar {
|
|
|
5024
5183
|
/* The inset a direct .bleed child cancels (components/bleed.css). */
|
|
5025
5184
|
--surface-pad: var(--drawer-pad);
|
|
5026
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));
|
|
5027
5190
|
/* Exit is shorter than entry so the panel clears quickly; the scrim reads
|
|
5028
5191
|
the same variable through inheritance and never lags the panel. */
|
|
5029
5192
|
--drawer-motion-duration: var(--duration);
|
|
@@ -5203,11 +5366,22 @@ html.has-modal-open.had-scrollbar {
|
|
|
5203
5366
|
}
|
|
5204
5367
|
}
|
|
5205
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
|
+
|
|
5206
5375
|
/* The header reserves inline room so the title never runs under the close
|
|
5207
|
-
control. */
|
|
5376
|
+
control. A .bleed header keeps at least its band inset. */
|
|
5208
5377
|
:where(dialog).drawer > header,
|
|
5209
|
-
:where(dialog).drawer > form > header {
|
|
5210
|
-
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));
|
|
5211
5385
|
}
|
|
5212
5386
|
|
|
5213
5387
|
/*
|
|
@@ -5217,9 +5391,9 @@ html.has-modal-open.had-scrollbar {
|
|
|
5217
5391
|
*
|
|
5218
5392
|
* A surface that insets its direct children relays that inset to them as
|
|
5219
5393
|
* --surface-pad; a direct .bleed child cancels it and reaches the surface
|
|
5220
|
-
* edge. Card and drawer pad at the container level,
|
|
5221
|
-
*
|
|
5222
|
-
* owns it and the item does not.
|
|
5394
|
+
* edge. Card and drawer pad at the container level, while modal pads its inner
|
|
5395
|
+
* wrapper; each owns the token where that inset lives. The accordion pads its
|
|
5396
|
+
* summary and panel instead, so the panel owns it and the item does not.
|
|
5223
5397
|
*
|
|
5224
5398
|
* Why the token is handed to the children rather than declared on the surface:
|
|
5225
5399
|
* custom properties inherit, so a surface-level declaration would also reach a
|
|
@@ -5240,11 +5414,11 @@ html.has-modal-open.had-scrollbar {
|
|
|
5240
5414
|
* Framework plumbing:
|
|
5241
5415
|
* --surface-pad (declared by the surface)
|
|
5242
5416
|
*/
|
|
5243
|
-
:where(.card, .drawer, .accordion details > summary + *) > * * {
|
|
5417
|
+
:where(.card, .drawer, dialog.modal > :is(form, .stack), .accordion details > summary + *) > * * {
|
|
5244
5418
|
--bleed-pad: initial;
|
|
5245
5419
|
}
|
|
5246
5420
|
|
|
5247
|
-
:where(.card, .drawer, .accordion details > summary + *) > * {
|
|
5421
|
+
:where(.card, .drawer, dialog.modal > :is(form, .stack), .accordion details > summary + *) > * {
|
|
5248
5422
|
--bleed-pad: var(--surface-pad);
|
|
5249
5423
|
}
|
|
5250
5424
|
|
|
@@ -5344,6 +5518,9 @@ picture.bleed > img {
|
|
|
5344
5518
|
position: absolute;
|
|
5345
5519
|
inset-block-start: calc(100% + var(--space-10));
|
|
5346
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. */
|
|
5347
5524
|
display: grid;
|
|
5348
5525
|
gap: var(--space-10);
|
|
5349
5526
|
inline-size: var(--flyout-inline-size, max-content);
|
|
@@ -6540,19 +6717,26 @@ a.avatar[aria-disabled="true"] {
|
|
|
6540
6717
|
--avatar-stack-font-size: var(--font-size-lg);
|
|
6541
6718
|
}
|
|
6542
6719
|
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
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
|
+
}
|
|
6548
6727
|
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
}
|
|
6728
|
+
.avatar-stack:has(> .avatar.lg) {
|
|
6729
|
+
--avatar-stack-size: 3rem;
|
|
6730
|
+
--avatar-stack-font-size: var(--font-size-lg);
|
|
6553
6731
|
}
|
|
6554
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. */
|
|
6555
6737
|
.avatar-stack > .avatar {
|
|
6738
|
+
background-color: var(--surface-opaque);
|
|
6739
|
+
background-image: linear-gradient(var(--avatar-bg), var(--avatar-bg));
|
|
6556
6740
|
box-shadow: 0 0 0 var(--avatar-stack-ring) var(--surface-opaque);
|
|
6557
6741
|
}
|
|
6558
6742
|
|
|
@@ -6773,11 +6957,10 @@ a.avatar[aria-disabled="true"] {
|
|
|
6773
6957
|
}
|
|
6774
6958
|
|
|
6775
6959
|
/* Remove the reserved avatar gutter when a message has no avatar. This is a
|
|
6776
|
-
cosmetic enhancement; the two-column baseline remains fully readable
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
}
|
|
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;
|
|
6781
6964
|
}
|
|
6782
6965
|
|
|
6783
6966
|
/*
|
|
@@ -7476,8 +7659,8 @@ a.avatar[aria-disabled="true"] {
|
|
|
7476
7659
|
*
|
|
7477
7660
|
* Docs: docs/pages/components/navbar.md
|
|
7478
7661
|
*
|
|
7479
|
-
* .navbar is a horizontal bar; .navbar-brand is the brand link;
|
|
7480
|
-
*
|
|
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
|
|
7481
7664
|
* list shared with .drawer, fed by the shared --gap token.
|
|
7482
7665
|
*
|
|
7483
7666
|
* Internal:
|
|
@@ -7526,17 +7709,6 @@ a.avatar[aria-disabled="true"] {
|
|
|
7526
7709
|
border-radius: var(--radius-sm);
|
|
7527
7710
|
}
|
|
7528
7711
|
|
|
7529
|
-
.navbar-nav {
|
|
7530
|
-
display: flex;
|
|
7531
|
-
flex-direction: row;
|
|
7532
|
-
flex-wrap: wrap;
|
|
7533
|
-
align-items: center;
|
|
7534
|
-
gap: var(--space-10);
|
|
7535
|
-
margin: 0;
|
|
7536
|
-
padding: 0;
|
|
7537
|
-
list-style: none;
|
|
7538
|
-
}
|
|
7539
|
-
|
|
7540
7712
|
.nav-link {
|
|
7541
7713
|
display: inline-flex;
|
|
7542
7714
|
align-items: center;
|
|
@@ -7559,19 +7731,16 @@ a.avatar[aria-disabled="true"] {
|
|
|
7559
7731
|
removed everywhere else, never serialized as aria-current="false".
|
|
7560
7732
|
Selection never changes text metrics: the shared medium weight is kept and
|
|
7561
7733
|
the current link is carried by the selected accent. */
|
|
7562
|
-
.nav-link[aria-current] {
|
|
7563
|
-
background: var(--surface-subtle);
|
|
7564
|
-
color: var(--state-selected);
|
|
7565
|
-
}
|
|
7566
|
-
|
|
7567
7734
|
.nav-link:hover {
|
|
7568
7735
|
background: var(--surface-subtle);
|
|
7569
7736
|
color: var(--text);
|
|
7570
7737
|
}
|
|
7571
7738
|
|
|
7572
|
-
/*
|
|
7573
|
-
|
|
7574
|
-
|
|
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);
|
|
7575
7744
|
color: var(--state-selected);
|
|
7576
7745
|
}
|
|
7577
7746
|
|
|
@@ -7591,19 +7760,22 @@ a.avatar[aria-disabled="true"] {
|
|
|
7591
7760
|
color: var(--ui-fg, var(--text));
|
|
7592
7761
|
}
|
|
7593
7762
|
|
|
7594
|
-
.
|
|
7595
|
-
.nav-link[aria-current]
|
|
7763
|
+
/* After :hover so a hovered current link keeps the selected accent. */
|
|
7764
|
+
.nav-link[aria-current] {
|
|
7596
7765
|
color: var(--ui-fg, var(--state-selected));
|
|
7597
7766
|
}
|
|
7598
7767
|
}
|
|
7599
7768
|
|
|
7600
7769
|
/* Nav list — a vertical navigation list. Used by .drawer for stacked links.
|
|
7601
7770
|
Links fill each row so the whole line is an interactive target; the
|
|
7602
|
-
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. */
|
|
7603
7775
|
.nav-list {
|
|
7604
7776
|
display: grid;
|
|
7605
7777
|
grid-template-columns: minmax(0, 1fr);
|
|
7606
|
-
--gap: var(--space-
|
|
7778
|
+
--gap: var(--space-10);
|
|
7607
7779
|
gap: var(--gap);
|
|
7608
7780
|
margin: 0;
|
|
7609
7781
|
padding: 0;
|
|
@@ -8158,11 +8330,14 @@ a.list-item[aria-current]::before {
|
|
|
8158
8330
|
* .sm/.lg are deliberately not supported.
|
|
8159
8331
|
*
|
|
8160
8332
|
* Public hooks:
|
|
8333
|
+
* --meter-track
|
|
8161
8334
|
* --meter-value
|
|
8162
8335
|
* --bar-height
|
|
8163
8336
|
*/
|
|
8164
8337
|
|
|
8165
8338
|
.meter {
|
|
8339
|
+
--meter-track: var(--surface-subtle);
|
|
8340
|
+
|
|
8166
8341
|
appearance: none;
|
|
8167
8342
|
-webkit-appearance: none;
|
|
8168
8343
|
display: block;
|
|
@@ -8171,7 +8346,7 @@ a.list-item[aria-current]::before {
|
|
|
8171
8346
|
overflow: hidden;
|
|
8172
8347
|
border: 0;
|
|
8173
8348
|
border-radius: var(--radius-full);
|
|
8174
|
-
background: var(--
|
|
8349
|
+
background: var(--meter-track);
|
|
8175
8350
|
}
|
|
8176
8351
|
|
|
8177
8352
|
/* A non-native meter is a weighted flex container. The element type keeps the
|
|
@@ -8198,7 +8373,7 @@ a.list-item[aria-current]::before {
|
|
|
8198
8373
|
block-size: var(--bar-height);
|
|
8199
8374
|
border: 0;
|
|
8200
8375
|
border-radius: var(--radius-full);
|
|
8201
|
-
background: var(--
|
|
8376
|
+
background: var(--meter-track);
|
|
8202
8377
|
}
|
|
8203
8378
|
|
|
8204
8379
|
.meter::-webkit-meter-optimum-value {
|
|
@@ -8720,6 +8895,11 @@ a.list-item[aria-current]::before {
|
|
|
8720
8895
|
align-items: end;
|
|
8721
8896
|
}
|
|
8722
8897
|
|
|
8898
|
+
.items-baseline {
|
|
8899
|
+
--items-align: baseline;
|
|
8900
|
+
align-items: baseline;
|
|
8901
|
+
}
|
|
8902
|
+
|
|
8723
8903
|
.muted {
|
|
8724
8904
|
color: var(--text-muted);
|
|
8725
8905
|
}
|
|
@@ -8841,9 +9021,6 @@ a.list-item[aria-current]::before {
|
|
|
8841
9021
|
* - utilities consume the current context instead of defining their own -sm/-lg scale.
|
|
8842
9022
|
* - optional utilities extend core utilities, they do not re-express them.
|
|
8843
9023
|
* - follow verbose .{property}-{value} naming.
|
|
8844
|
-
*
|
|
8845
|
-
* Internal:
|
|
8846
|
-
* --cluster-justify (local relay consumed by .cluster)
|
|
8847
9024
|
*/
|
|
8848
9025
|
|
|
8849
9026
|
/* Display */
|
|
@@ -8918,23 +9095,22 @@ a.list-item[aria-current]::before {
|
|
|
8918
9095
|
flex-wrap: nowrap;
|
|
8919
9096
|
}
|
|
8920
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. */
|
|
8921
9101
|
.justify-content-start {
|
|
8922
|
-
--cluster-justify: flex-start;
|
|
8923
9102
|
justify-content: flex-start;
|
|
8924
9103
|
}
|
|
8925
9104
|
|
|
8926
9105
|
.justify-content-center {
|
|
8927
|
-
--cluster-justify: center;
|
|
8928
9106
|
justify-content: center;
|
|
8929
9107
|
}
|
|
8930
9108
|
|
|
8931
9109
|
.justify-content-end {
|
|
8932
|
-
--cluster-justify: flex-end;
|
|
8933
9110
|
justify-content: flex-end;
|
|
8934
9111
|
}
|
|
8935
9112
|
|
|
8936
9113
|
.justify-content-space-between {
|
|
8937
|
-
--cluster-justify: space-between;
|
|
8938
9114
|
justify-content: space-between;
|
|
8939
9115
|
}
|
|
8940
9116
|
|