actual-css 0.3.0 → 0.3.1

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/dist/actual.css CHANGED
@@ -530,6 +530,207 @@ small {
530
530
  --intent-fg: var(--neutral-fg);
531
531
  }
532
532
 
533
+ /* Prose before layout: layout primitives (.stack, .cluster) reset child
534
+ margins and must win over .prose's flow margins at equal specificity —
535
+ source order is the tie-breaker, so prose loads first. */
536
+ /*
537
+ * Prose is the opt-in rich text scope for authored content.
538
+ *
539
+ * Global element styles stay mild so app UI is not shaped accidentally. Page
540
+ * layout, forms, cards, navigation, and app tables remain outside this scope.
541
+ *
542
+ * Public hooks:
543
+ * --prose-size
544
+ * --prose-line-height
545
+ * --prose-measure
546
+ * --prose-flow
547
+ * --prose-heading-line-height
548
+ * --prose-h1
549
+ * --prose-h2
550
+ * --prose-h3
551
+ * --prose-h4
552
+ */
553
+
554
+ .prose {
555
+ /* Prose scale */
556
+ --prose-size: 1rem;
557
+ --prose-line-height: 1.7;
558
+ --prose-measure: 70ch;
559
+ --prose-flow: 1em;
560
+ --prose-heading-line-height: 1.2;
561
+ --prose-h1: 2rem;
562
+ --prose-h2: 1.5rem;
563
+ --prose-h3: 1.25rem;
564
+ --prose-h4: 1.125rem;
565
+
566
+ max-inline-size: var(--prose-measure);
567
+ color: var(--text);
568
+ font-size: var(--prose-size);
569
+ line-height: var(--prose-line-height);
570
+ overflow-wrap: break-word;
571
+ }
572
+
573
+ .prose :where(p, ul, ol, dl, blockquote, pre, table, figure) {
574
+ margin-block: var(--prose-flow) 0;
575
+ }
576
+
577
+ .prose :where(h1, h2, h3, h4, h5, h6) {
578
+ margin-block: 1.5em 0;
579
+ color: var(--text);
580
+ font-weight: var(--font-weight-strong);
581
+ line-height: var(--prose-heading-line-height);
582
+ }
583
+
584
+ .prose :where(p, li, blockquote) {
585
+ text-wrap: pretty;
586
+ }
587
+
588
+ .prose > :first-child {
589
+ margin-block-start: 0;
590
+ }
591
+
592
+ .prose h1 {
593
+ font-size: var(--prose-h1);
594
+ }
595
+
596
+ .prose h2 {
597
+ font-size: var(--prose-h2);
598
+ }
599
+
600
+ .prose h3 {
601
+ font-size: var(--prose-h3);
602
+ }
603
+
604
+ .prose h4 {
605
+ font-size: var(--prose-h4);
606
+ }
607
+
608
+ .prose a {
609
+ color: var(--primary);
610
+ text-decoration-line: underline;
611
+ text-decoration-thickness: 0.08em;
612
+ text-underline-offset: 0.18em;
613
+ }
614
+
615
+ .prose a:hover {
616
+ text-decoration-thickness: 0.12em;
617
+ }
618
+
619
+ @supports (color: color-mix(in oklch, red, white)) {
620
+ .prose a:hover {
621
+ color: color-mix(in oklch, var(--primary), var(--text) 15%);
622
+ }
623
+ }
624
+
625
+ .prose :where(ul, ol) {
626
+ padding-inline-start: 1.5em;
627
+ }
628
+
629
+ .prose li + li {
630
+ margin-block-start: 0.35em;
631
+ }
632
+
633
+ .prose code {
634
+ padding: 0.15em 0.3em;
635
+ border-radius: var(--radius-sm);
636
+ background: var(--surface-subtle);
637
+ color: var(--text);
638
+ font-size: 0.875em;
639
+ }
640
+
641
+ /* Visual twin of .key (components/key.css) — keep the two recipes in sync. */
642
+ .prose kbd {
643
+ display: inline-flex;
644
+ align-items: center;
645
+ justify-content: center;
646
+ min-inline-size: 1.75em;
647
+ min-block-size: 1.6em;
648
+ padding-inline: 0.45em;
649
+ border: var(--border-width) solid var(--border);
650
+ border-block-end-width: calc(var(--border-width) * 2);
651
+ border-radius: var(--radius-sm);
652
+ background: var(--surface-raised);
653
+ color: var(--text);
654
+ font-family: var(--font-mono);
655
+ font-size: 0.875em;
656
+ font-weight: var(--font-weight-medium);
657
+ line-height: 1;
658
+ white-space: nowrap;
659
+ vertical-align: 0.08em;
660
+ }
661
+
662
+ .prose pre {
663
+ overflow-x: auto;
664
+ padding: 1em;
665
+ border: var(--border-width) solid var(--border);
666
+ border-radius: var(--radius);
667
+ background: var(--surface-subtle);
668
+ }
669
+
670
+ .prose pre code {
671
+ padding: 0;
672
+ background: transparent;
673
+ }
674
+
675
+ .prose blockquote {
676
+ padding-inline-start: 1em;
677
+ border-inline-start: var(--border-width) solid var(--border);
678
+ color: var(--text-muted);
679
+ }
680
+
681
+ .prose figure {
682
+ margin-inline: 0;
683
+ }
684
+
685
+ .prose figcaption {
686
+ margin-block-start: 0.5em;
687
+ color: var(--text-muted);
688
+ font-size: 0.875em;
689
+ }
690
+
691
+ .prose table {
692
+ inline-size: 100%;
693
+ border-collapse: collapse;
694
+ }
695
+
696
+ .prose th,
697
+ .prose td {
698
+ padding: 0.5em;
699
+ border-block-end: var(--border-width) solid var(--border);
700
+ text-align: start;
701
+ }
702
+
703
+ /* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
704
+ Non-fluid and always available — use .text-lead from the optional fluid
705
+ module for marketing/display surfaces. */
706
+ .lead {
707
+ font-size: 1.125em;
708
+ line-height: var(--line-height-relaxed);
709
+ }
710
+
711
+ /* Readable content measure. Composes with .center for centering or with
712
+ margin-inline: auto for a one-off centered block. */
713
+ .measure {
714
+ max-inline-size: var(--prose-measure, 70ch);
715
+ }
716
+
717
+ .prose.sm {
718
+ --prose-size: 0.9375rem;
719
+ --prose-h1: 1.75rem;
720
+ --prose-h2: 1.375rem;
721
+ --prose-h3: 1.125rem;
722
+ --prose-h4: 1rem;
723
+ }
724
+
725
+ .prose.lg {
726
+ --prose-size: 1.125rem;
727
+ --prose-measure: 72ch;
728
+ --prose-h1: 2.25rem;
729
+ --prose-h2: 1.625rem;
730
+ --prose-h3: 1.375rem;
731
+ --prose-h4: 1.25rem;
732
+ }
733
+
533
734
  /*
534
735
  * Core layout primitives: stack, cluster, center, frame, app-shell, media,
535
736
  * sidebar-layout. Each primitive owns a single, readable behavior; they are
@@ -545,13 +746,26 @@ small {
545
746
  * --sidebar-layout-size
546
747
  *
547
748
  * Child contracts:
548
- * .stack owns vertical rhythm: direct-child margins are reset, spacing comes
549
- * from --gap.
749
+ * .stack owns vertical rhythm: direct-child block-axis margins are reset,
750
+ * spacing comes from --gap, inline margins stay free.
550
751
  * .cluster owns inline rhythm: direct-child margins are reset, spacing comes
551
752
  * from --gap.
552
753
  * .frame sizes direct media children (img/video/iframe/canvas/svg) to fill the
553
754
  * frame.
554
755
  */
756
+
757
+ /* Responsive recipes:
758
+ - avatar/icon + flexible content -> .media
759
+ - inline items that wrap when space runs out -> .cluster (keep wrapping)
760
+ - content + aside -> .sidebar-layout inside .container-query
761
+ - row that becomes a column when constrained -> .switcher (optional module)
762
+
763
+ Anti-patterns:
764
+ - viewport breakpoints for ordinary composition
765
+ - fixed grid templates (--grid-columns) where .grid/.sidebar-layout express
766
+ the layout
767
+ - nowrap on page-level compositions; prefer wrapping and container-aware
768
+ primitives */
555
769
  .container-query {
556
770
  container-type: inline-size;
557
771
  }
@@ -562,8 +776,10 @@ small {
562
776
  gap: var(--gap);
563
777
  }
564
778
 
779
+ /* .stack owns the block axis only: block margins reset, inline margins stay
780
+ free (self-centering children via margin-inline: auto keep working). */
565
781
  .stack > * {
566
- margin: 0;
782
+ margin-block: 0;
567
783
  }
568
784
 
569
785
  .cluster {
@@ -646,6 +862,19 @@ small {
646
862
  * --grid-min
647
863
  * --grid-columns
648
864
  */
865
+
866
+ /* --grid-columns replaces the intrinsic auto-fit template: once set, the
867
+ grid no longer collapses on its own — the author owns narrow-container
868
+ collapse. Prefer .sidebar-layout inside .container-query for content +
869
+ aside layouts; reserve --grid-columns for genuinely fixed or asymmetric
870
+ templates. */
871
+
872
+ /* Recipe:
873
+ - N equal cards that collapse when narrow -> .grid (auto-fit, tune
874
+ --grid-min) or .grid-2/3/4/6 inside .container-query
875
+ - content + aside -> .sidebar-layout inside .container-query
876
+ - fixed or asymmetric columns -> --grid-columns with an explicit collapse
877
+ rule of your own */
649
878
  .grid {
650
879
  display: grid;
651
880
  gap: var(--gap);
@@ -740,14 +969,18 @@ small {
740
969
  * Cross-cutting variant token mapping.
741
970
  *
742
971
  * Variant scope: .solid/.soft/.outline set --ui-* for components that opt into
743
- * shared variants. .inverted is a surface modifier, not a variant: it also
744
- * paints the element directly (background/color/border-color) so it works on
745
- * any block, competing with component surface treatments (.card.subtle).
972
+ * shared variants. .inverted is a surface modifier, not a variant: it paints
973
+ * the element directly (background/color/border-color) so it works on any bare
974
+ * block, and remaps the shared --ui-* tokens so participating components
975
+ * (.card, .navbar, .alert, .badge) invert through their own rules. Loaded
976
+ * before components, so a component with its own explicit surface (e.g.
977
+ * .accordion) naturally keeps that surface — .inverted never forces a
978
+ * non-participant.
746
979
  * Component-specific defaults (.btn solid-by-default, .badge
747
980
  * soft-by-default, .alert soft-by-default) live with their components:
748
981
  * components/button.css — .btn default solid + .btn.ghost + .btn.link
749
- * components/badge.css — .badge default soft
750
- * components/alert.css — .alert default soft (consumes --ui-* directly)
982
+ * components/badge.css — .badge default soft behind explicit --ui-*
983
+ * components/alert.css — .alert default soft behind explicit --ui-*
751
984
  *
752
985
  * .sm/.lg set shared density tokens: spacing rhythm and geometry only.
753
986
  * Density never changes typography — controls keep their font-size and
@@ -775,18 +1008,23 @@ small {
775
1008
 
776
1009
  .outline {
777
1010
  --ui-bg: transparent;
778
- --ui-fg: var(--intent, var(--text));
779
- --ui-border: var(--intent, var(--border));
780
- }
781
-
782
- /* Inverse surface treatment. Components (e.g. .card) compose through --ui-*;
783
- the direct background/color/border-color also make .inverted useful on a
784
- generic element. --heading is set so global headings (var(--heading, inherit))
785
- do not inherit an outside heading color onto the dark surface. */
1011
+ --ui-fg: var(--intent, currentColor);
1012
+ --ui-border: var(--intent, currentColor);
1013
+ }
1014
+
1015
+ /* Inverse surface treatment. Paints the element directly (so it works on any
1016
+ bare block) and remaps the shared --ui-* tokens so opt-in components
1017
+ (.card, .navbar) invert through their own rules. Because this loads before
1018
+ component base surfaces, components with their own explicit surface
1019
+ (e.g. .accordion) keep it — participation is opt-in, never forced. --heading
1020
+ is set so global headings (var(--heading, inherit)) do not inherit an outside
1021
+ heading color onto the dark surface; surface-owning descendants derive
1022
+ their contextual aliases from their own local surface. */
786
1023
  .inverted {
787
1024
  --ui-bg: var(--surface-solid);
788
1025
  --ui-fg: var(--surface);
789
1026
  --ui-border: transparent;
1027
+ --ui-hover-bg: var(--surface-solid);
790
1028
 
791
1029
  --heading: var(--surface);
792
1030
  --busy-overlay-bg: var(--surface-solid);
@@ -822,208 +1060,14 @@ small {
822
1060
  );
823
1061
  /* @sync soft-recipe */
824
1062
  }
825
- }
826
-
827
- /* Interactive hover recipes live in the components that consume them:
828
- components/button.css and components/badge.css. */
829
1063
 
830
- /*
831
- * Prose is the opt-in rich text scope for authored content.
832
- *
833
- * Global element styles stay mild so app UI is not shaped accidentally. Page
834
- * layout, forms, cards, navigation, and app tables remain outside this scope.
835
- *
836
- * Public hooks:
837
- * --prose-size
838
- * --prose-line-height
839
- * --prose-measure
840
- * --prose-flow
841
- * --prose-heading-line-height
842
- * --prose-h1
843
- * --prose-h2
844
- * --prose-h3
845
- * --prose-h4
846
- */
847
-
848
- .prose {
849
- /* Prose scale */
850
- --prose-size: 1rem;
851
- --prose-line-height: 1.7;
852
- --prose-measure: 70ch;
853
- --prose-flow: 1em;
854
- --prose-heading-line-height: 1.2;
855
- --prose-h1: 2rem;
856
- --prose-h2: 1.5rem;
857
- --prose-h3: 1.25rem;
858
- --prose-h4: 1.125rem;
859
-
860
- max-inline-size: var(--prose-measure);
861
- color: var(--text);
862
- font-size: var(--prose-size);
863
- line-height: var(--prose-line-height);
864
- overflow-wrap: break-word;
865
- }
866
-
867
- .prose :where(p, ul, ol, dl, blockquote, pre, table, figure) {
868
- margin-block: var(--prose-flow) 0;
869
- }
870
-
871
- .prose :where(h1, h2, h3, h4, h5, h6) {
872
- margin-block: 1.5em 0;
873
- color: var(--text);
874
- font-weight: var(--font-weight-strong);
875
- line-height: var(--prose-heading-line-height);
876
- }
877
-
878
- .prose :where(p, li, blockquote) {
879
- text-wrap: pretty;
880
- }
881
-
882
- .prose > :first-child {
883
- margin-block-start: 0;
884
- }
885
-
886
- .prose h1 {
887
- font-size: var(--prose-h1);
888
- }
889
-
890
- .prose h2 {
891
- font-size: var(--prose-h2);
892
- }
893
-
894
- .prose h3 {
895
- font-size: var(--prose-h3);
896
- }
897
-
898
- .prose h4 {
899
- font-size: var(--prose-h4);
900
- }
901
-
902
- .prose a {
903
- color: var(--primary);
904
- text-decoration-line: underline;
905
- text-decoration-thickness: 0.08em;
906
- text-underline-offset: 0.18em;
907
- }
908
-
909
- .prose a:hover {
910
- text-decoration-thickness: 0.12em;
911
- }
912
-
913
- @supports (color: color-mix(in oklch, red, white)) {
914
- .prose a:hover {
915
- color: color-mix(in oklch, var(--primary), var(--text) 15%);
1064
+ .inverted {
1065
+ --ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black);
916
1066
  }
917
1067
  }
918
1068
 
919
- .prose :where(ul, ol) {
920
- padding-inline-start: 1.5em;
921
- }
922
-
923
- .prose li + li {
924
- margin-block-start: 0.35em;
925
- }
926
-
927
- .prose code {
928
- padding: 0.15em 0.3em;
929
- border-radius: var(--radius-sm);
930
- background: var(--surface-subtle);
931
- color: var(--text);
932
- font-size: 0.875em;
933
- }
934
-
935
- /* Visual twin of .key (components/key.css) — keep the two recipes in sync. */
936
- .prose kbd {
937
- display: inline-flex;
938
- align-items: center;
939
- justify-content: center;
940
- min-inline-size: 1.75em;
941
- min-block-size: 1.6em;
942
- padding-inline: 0.45em;
943
- border: var(--border-width) solid var(--border);
944
- border-block-end-width: calc(var(--border-width) * 2);
945
- border-radius: var(--radius-sm);
946
- background: var(--surface-raised);
947
- color: var(--text);
948
- font-family: var(--font-mono);
949
- font-size: 0.875em;
950
- font-weight: var(--font-weight-medium);
951
- line-height: 1;
952
- white-space: nowrap;
953
- vertical-align: 0.08em;
954
- }
955
-
956
- .prose pre {
957
- overflow-x: auto;
958
- padding: 1em;
959
- border: var(--border-width) solid var(--border);
960
- border-radius: var(--radius);
961
- background: var(--surface-subtle);
962
- }
963
-
964
- .prose pre code {
965
- padding: 0;
966
- background: transparent;
967
- }
968
-
969
- .prose blockquote {
970
- padding-inline-start: 1em;
971
- border-inline-start: var(--border-width) solid var(--border);
972
- color: var(--text-muted);
973
- }
974
-
975
- .prose figure {
976
- margin-inline: 0;
977
- }
978
-
979
- .prose figcaption {
980
- margin-block-start: 0.5em;
981
- color: var(--text-muted);
982
- font-size: 0.875em;
983
- }
984
-
985
- .prose table {
986
- inline-size: 100%;
987
- border-collapse: collapse;
988
- }
989
-
990
- .prose th,
991
- .prose td {
992
- padding: 0.5em;
993
- border-block-end: var(--border-width) solid var(--border);
994
- text-align: start;
995
- }
996
-
997
- /* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
998
- Non-fluid and always available — use .text-lead from the optional fluid
999
- module for marketing/display surfaces. */
1000
- .lead {
1001
- font-size: 1.125em;
1002
- line-height: var(--line-height-relaxed);
1003
- }
1004
-
1005
- /* Readable content measure. Composes with .center for centering or with
1006
- margin-inline: auto for a one-off centered block. */
1007
- .measure {
1008
- max-inline-size: var(--prose-measure, 70ch);
1009
- }
1010
-
1011
- .prose.sm {
1012
- --prose-size: 0.9375rem;
1013
- --prose-h1: 1.75rem;
1014
- --prose-h2: 1.375rem;
1015
- --prose-h3: 1.125rem;
1016
- --prose-h4: 1rem;
1017
- }
1018
-
1019
- .prose.lg {
1020
- --prose-size: 1.125rem;
1021
- --prose-measure: 72ch;
1022
- --prose-h1: 2.25rem;
1023
- --prose-h2: 1.625rem;
1024
- --prose-h3: 1.375rem;
1025
- --prose-h4: 1.25rem;
1026
- }
1069
+ /* Interactive hover recipes live in the components that consume them:
1070
+ components/button.css and components/badge.css. */
1027
1071
 
1028
1072
  /* Controls */
1029
1073
  /*
@@ -1046,11 +1090,16 @@ small {
1046
1090
  * --btn-focus-color
1047
1091
  * --btn-gap
1048
1092
  * --btn-hover-overlay
1093
+ * --btn-default-bg
1094
+ * --btn-default-fg
1095
+ * --btn-default-border
1096
+ * --btn-border-color
1049
1097
  *
1050
1098
  * Internal:
1051
1099
  * --btn-bg
1052
1100
  * --btn-fg
1053
1101
  * --btn-border
1102
+ * --btn-default-hover-bg
1054
1103
  * --btn-focus-ring-color
1055
1104
  * --btn-border-block-end-color
1056
1105
  * --btn-border-block-end-width
@@ -1075,6 +1124,7 @@ small {
1075
1124
  --btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
1076
1125
  --btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
1077
1126
  --btn-border: var(--ui-border, var(--btn-default-border, transparent));
1127
+ --btn-default-hover-bg: var(--btn-bg);
1078
1128
  --btn-radius: var(--radius);
1079
1129
  --btn-focus-color: var(--intent, var(--focus));
1080
1130
  --btn-hover-overlay: var(--hover-overlay, rgb(0 0 0 / 0.08));
@@ -1127,7 +1177,7 @@ small {
1127
1177
  fallbacks make .btn solid by default; .ghost/.link are .btn-only variants. */
1128
1178
  .btn.ghost {
1129
1179
  --ui-bg: transparent;
1130
- --ui-fg: var(--intent, var(--text));
1180
+ --ui-fg: var(--intent, currentColor);
1131
1181
  --ui-border: transparent;
1132
1182
  }
1133
1183
 
@@ -1145,12 +1195,12 @@ small {
1145
1195
  Declared outside :hover: the variables resolve only when the hover rule
1146
1196
  reads them, so the values are inert until interaction. */
1147
1197
  .btn:is(.outline, .ghost) {
1148
- --ui-hover-bg: var(--surface-subtle);
1198
+ --ui-hover-bg: transparent;
1149
1199
  }
1150
1200
 
1151
1201
  @supports (color: color-mix(in oklch, red, white)) {
1152
- .btn:not(:where(.soft, .outline, .ghost, .link)) {
1153
- --ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
1202
+ .btn {
1203
+ --btn-default-hover-bg: color-mix(in oklch, var(--btn-bg) 88%, black);
1154
1204
  }
1155
1205
 
1156
1206
  .btn.soft {
@@ -1161,19 +1211,16 @@ small {
1161
1211
  );
1162
1212
  }
1163
1213
 
1164
- .btn.outline {
1165
- --ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
1166
- }
1167
-
1214
+ .btn.outline,
1168
1215
  .btn.ghost {
1169
- --ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
1216
+ --ui-hover-bg: color-mix(in oklch, currentColor 10%, transparent);
1170
1217
  }
1171
1218
  }
1172
1219
 
1173
1220
  /* The .soft/.outline/.ghost hover recipe (--ui-hover-bg) is set locally above;
1174
1221
  the overlay darkening for solid/soft is .btn-specific. */
1175
1222
  .btn:not(:where(:disabled, [aria-disabled="true"])):hover {
1176
- background: var(--ui-hover-bg, var(--btn-bg));
1223
+ background: var(--ui-hover-bg, var(--btn-default-hover-bg));
1177
1224
  box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
1178
1225
  }
1179
1226
 
@@ -1183,7 +1230,7 @@ small {
1183
1230
 
1184
1231
  .btn.link {
1185
1232
  --ui-bg: transparent;
1186
- --ui-fg: var(--intent, var(--text));
1233
+ --ui-fg: var(--intent, currentColor);
1187
1234
  --ui-border: transparent;
1188
1235
  --btn-radius: var(--radius-sm);
1189
1236
  --btn-border-block-end-color: transparent;
@@ -1191,6 +1238,8 @@ small {
1191
1238
 
1192
1239
  min-block-size: auto;
1193
1240
  padding-inline: 0;
1241
+ inline-size: fit-content;
1242
+ max-inline-size: 100%;
1194
1243
  border-color: transparent;
1195
1244
  text-decoration: underline;
1196
1245
  text-underline-offset: 0.18em;
@@ -1201,7 +1250,6 @@ small {
1201
1250
 
1202
1251
  .btn.link:not(:where(:disabled, [aria-disabled="true"])):hover {
1203
1252
  background: transparent;
1204
- color: var(--intent, var(--text));
1205
1253
  text-decoration-thickness: 0.12em;
1206
1254
  }
1207
1255
 
@@ -1302,6 +1350,10 @@ a.btn[aria-disabled="true"] {
1302
1350
  * --badge-radius
1303
1351
  *
1304
1352
  * Internal:
1353
+ * --badge-default-bg
1354
+ * --badge-default-fg
1355
+ * --badge-default-border
1356
+ * --badge-default-hover-bg
1305
1357
  * --badge-bg
1306
1358
  * --badge-fg
1307
1359
  * --badge-border
@@ -1322,9 +1374,13 @@ a.btn[aria-disabled="true"] {
1322
1374
  --badge-pad-x: 0.55em;
1323
1375
  --badge-font-size: 0.8125rem;
1324
1376
  --badge-dot-size: 0.5rem;
1325
- --badge-bg: var(--ui-bg, var(--surface-subtle));
1326
- --badge-fg: var(--ui-fg, var(--intent, var(--text)));
1327
- --badge-border: var(--ui-border, var(--border));
1377
+ --badge-default-bg: var(--surface-subtle);
1378
+ --badge-default-fg: var(--intent, var(--text));
1379
+ --badge-default-border: var(--border);
1380
+ --badge-default-hover-bg: var(--badge-bg);
1381
+ --badge-bg: var(--ui-bg, var(--badge-default-bg));
1382
+ --badge-fg: var(--ui-fg, var(--badge-default-fg));
1383
+ --badge-border: var(--ui-border, var(--badge-default-border));
1328
1384
  --badge-radius: var(--radius-full);
1329
1385
 
1330
1386
  display: inline-flex;
@@ -1351,18 +1407,17 @@ a.btn[aria-disabled="true"] {
1351
1407
  box-shadow var(--duration);
1352
1408
  }
1353
1409
 
1354
- /* Soft by default — duplicated here with @sync markers for check-sync.js
1355
- to guard, the same way alert.css guards its own soft recipe. */
1410
+ /* Soft by default. Local defaults remain behind --ui-* in the resolution
1411
+ chain, so explicit shared treatments compose without selector exclusions. */
1356
1412
  @supports (color: color-mix(in oklch, red, white)) {
1357
- .badge:where(:not(.solid, .outline)) {
1413
+ .badge {
1358
1414
  /* @sync soft-recipe */
1359
- --badge-bg: color-mix(
1415
+ --badge-default-bg: color-mix(
1360
1416
  in oklch,
1361
1417
  var(--surface) var(--soft-bg-mix),
1362
1418
  var(--intent, var(--neutral))
1363
1419
  );
1364
- --badge-fg: var(--intent, var(--text));
1365
- --badge-border: color-mix(
1420
+ --badge-default-border: color-mix(
1366
1421
  in oklch,
1367
1422
  var(--surface) var(--soft-border-mix),
1368
1423
  var(--intent, var(--neutral))
@@ -1408,7 +1463,7 @@ a.btn[aria-disabled="true"] {
1408
1463
  /* Hover recipes — sets --ui-hover-bg consumed by the hover rule below.
1409
1464
  Declared outside :hover like button.css for the same reason. */
1410
1465
  :is(a.badge, button.badge).outline {
1411
- --ui-hover-bg: var(--surface-subtle);
1466
+ --ui-hover-bg: transparent;
1412
1467
  }
1413
1468
 
1414
1469
  @supports (color: color-mix(in oklch, red, white)) {
@@ -1417,7 +1472,7 @@ a.btn[aria-disabled="true"] {
1417
1472
  }
1418
1473
 
1419
1474
  :is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
1420
- --ui-hover-bg: color-mix(
1475
+ --badge-default-hover-bg: color-mix(
1421
1476
  in oklch,
1422
1477
  var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
1423
1478
  var(--intent, var(--neutral))
@@ -1425,12 +1480,12 @@ a.btn[aria-disabled="true"] {
1425
1480
  }
1426
1481
 
1427
1482
  :is(a.badge, button.badge).outline {
1428
- --ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
1483
+ --ui-hover-bg: color-mix(in oklch, currentColor 10%, transparent);
1429
1484
  }
1430
1485
  }
1431
1486
 
1432
1487
  :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"])):hover {
1433
- background: var(--ui-hover-bg, var(--badge-bg));
1488
+ background: var(--ui-hover-bg, var(--badge-default-hover-bg));
1434
1489
  }
1435
1490
 
1436
1491
  :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"], .outline)):hover {
@@ -1491,8 +1546,12 @@ a.badge[aria-disabled="true"] {
1491
1546
  * --alert-radius
1492
1547
  * --alert-icon-size
1493
1548
  * --alert-dismiss-size
1549
+ * --alert-border-inline-start-color
1550
+ * --alert-border-inline-start-width
1494
1551
  *
1495
1552
  * Internal:
1553
+ * --alert-default-bg
1554
+ * --alert-default-border
1496
1555
  * --alert-bg
1497
1556
  * --alert-fg
1498
1557
  * --alert-border
@@ -1513,9 +1572,11 @@ a.badge[aria-disabled="true"] {
1513
1572
  --alert-pad-inline: var(--space-40);
1514
1573
  --alert-font-size: var(--font-size-md);
1515
1574
  --alert-icon-size: 1.25rem;
1516
- --alert-bg: var(--ui-bg, var(--surface-subtle));
1575
+ --alert-default-bg: var(--surface-subtle);
1576
+ --alert-default-border: var(--border);
1577
+ --alert-bg: var(--ui-bg, var(--alert-default-bg));
1517
1578
  --alert-fg: var(--ui-fg, var(--intent, var(--text)));
1518
- --alert-border: var(--ui-border, var(--border));
1579
+ --alert-border: var(--ui-border, var(--alert-default-border));
1519
1580
 
1520
1581
  padding-block: var(--alert-pad-block);
1521
1582
  padding-inline: var(--alert-pad-inline);
@@ -1540,13 +1601,17 @@ a.badge[aria-disabled="true"] {
1540
1601
  margin-block-end: 0;
1541
1602
  }
1542
1603
 
1543
- /* Alerts are soft by default. .solid and .outline need no local rules — the
1544
- shared variant plumbing in variants.css sets --ui-* that alert consumes. */
1604
+ /* Alerts are soft by default. The recipe writes local defaults rather than
1605
+ --ui-* so an explicit shared treatment always wins without exclusions. */
1545
1606
  @supports (color: color-mix(in oklch, red, white)) {
1546
- .alert:where(:not(.solid, .outline, .callout)) {
1607
+ .alert {
1547
1608
  /* @sync soft-recipe */
1548
- --ui-bg: color-mix(in oklch, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral)));
1549
- --ui-border: color-mix(
1609
+ --alert-default-bg: color-mix(
1610
+ in oklch,
1611
+ var(--surface) var(--soft-bg-mix),
1612
+ var(--intent, var(--neutral))
1613
+ );
1614
+ --alert-default-border: color-mix(
1550
1615
  in oklch,
1551
1616
  var(--surface) var(--soft-border-mix),
1552
1617
  var(--intent, var(--neutral))
@@ -1769,6 +1834,8 @@ a.badge[aria-disabled="true"] {
1769
1834
  * edge children inherit the card corners. .bleed must be a direct child.
1770
1835
  *
1771
1836
  * Internal:
1837
+ * --card-bg
1838
+ * --card-fg
1772
1839
  * --heading
1773
1840
  * --busy-overlay-bg
1774
1841
  */
@@ -1784,11 +1851,17 @@ a.badge[aria-disabled="true"] {
1784
1851
  --card-pad: var(--space-40);
1785
1852
  --card-radius: var(--radius-lg);
1786
1853
  --card-max-inline-size: none;
1854
+ --card-bg: var(--ui-bg, var(--surface-raised));
1855
+ --card-fg: var(--ui-fg, var(--text));
1856
+
1857
+ /* Keep contextual descendants tied to the surface this card actually owns. */
1858
+ --heading: var(--card-fg);
1859
+ --busy-overlay-bg: var(--card-bg);
1787
1860
 
1788
1861
  max-inline-size: var(--card-max-inline-size);
1789
1862
  padding: var(--card-pad);
1790
- background: var(--ui-bg, var(--surface-raised));
1791
- color: var(--ui-fg, var(--text));
1863
+ background: var(--card-bg);
1864
+ color: var(--card-fg);
1792
1865
  border: var(--border-width) solid var(--ui-border, var(--border));
1793
1866
  border-radius: var(--card-radius);
1794
1867
  }
@@ -3491,6 +3564,9 @@ a.avatar[aria-disabled="true"] {
3491
3564
  * Base form is unstyled chrome that takes its surrounding color. Add .pill for
3492
3565
  * the bordered rounded shape, and combine with an intent class (.primary,
3493
3566
  * .success, etc.) to tint the text, border, and background.
3567
+ *
3568
+ * Public hooks:
3569
+ * --overline-radius
3494
3570
  */
3495
3571
  /* Intent boundary — inherited intents do not tint this component.
3496
3572
  Local intent classes still win because :where() has zero specificity. */
@@ -3499,7 +3575,6 @@ a.avatar[aria-disabled="true"] {
3499
3575
  }
3500
3576
 
3501
3577
  .overline {
3502
- align-self: flex-start;
3503
3578
  color: var(--text-muted);
3504
3579
  font-size: var(--font-size-sm);
3505
3580
  font-weight: var(--font-weight-medium);
@@ -3508,14 +3583,18 @@ a.avatar[aria-disabled="true"] {
3508
3583
  }
3509
3584
 
3510
3585
  .overline.pill {
3586
+ --overline-radius: var(--radius-full);
3587
+
3511
3588
  display: inline-flex;
3512
3589
  align-items: center;
3513
3590
  gap: 0.5em;
3514
3591
  padding-inline: 0.6em;
3515
3592
  padding-block: 0.25em;
3593
+ inline-size: fit-content;
3594
+ max-inline-size: 100%;
3516
3595
  color: var(--intent, var(--text-muted));
3517
3596
  border: var(--border-width) solid var(--border);
3518
- border-radius: var(--radius-full);
3597
+ border-radius: var(--overline-radius);
3519
3598
  background: var(--surface);
3520
3599
  }
3521
3600
 
@@ -3540,6 +3619,14 @@ a.avatar[aria-disabled="true"] {
3540
3619
  * Internal:
3541
3620
  * --gap (local value for the shared gap token)
3542
3621
  */
3622
+ /* Intent boundary — inherited intents do not tint this component.
3623
+ Local intent classes still win because :where() has zero specificity. */
3624
+ :where(.navbar) {
3625
+ --ui-bg: initial;
3626
+ --ui-fg: initial;
3627
+ --ui-border: initial;
3628
+ }
3629
+
3543
3630
  .navbar {
3544
3631
  display: flex;
3545
3632
  flex-wrap: wrap;
@@ -3548,17 +3635,17 @@ a.avatar[aria-disabled="true"] {
3548
3635
  gap: var(--gap);
3549
3636
  padding-block: var(--space-30);
3550
3637
  padding-inline: var(--space-40);
3551
- border: var(--border-width) solid var(--border);
3638
+ border: var(--border-width) solid var(--ui-border, var(--border));
3552
3639
  border-radius: var(--radius-lg);
3553
- background: var(--surface-raised);
3554
- color: var(--text);
3640
+ background: var(--ui-bg, var(--surface-raised));
3641
+ color: var(--ui-fg, var(--text));
3555
3642
  }
3556
3643
 
3557
3644
  .navbar-brand {
3558
3645
  display: inline-flex;
3559
3646
  align-items: center;
3560
3647
  gap: var(--space-20);
3561
- color: var(--text);
3648
+ color: var(--ui-fg, var(--text));
3562
3649
  font-weight: var(--font-weight-bold);
3563
3650
  text-decoration: none;
3564
3651
  }
@@ -3583,7 +3670,7 @@ a.avatar[aria-disabled="true"] {
3583
3670
  min-block-size: var(--control-size-sm);
3584
3671
  padding-inline: var(--space-20);
3585
3672
  border-radius: var(--radius);
3586
- color: var(--text-muted);
3673
+ color: var(--ui-fg, var(--text-muted));
3587
3674
  font-weight: var(--font-weight-medium);
3588
3675
  text-decoration: none;
3589
3676
  }
@@ -4052,6 +4139,8 @@ a.avatar[aria-disabled="true"] {
4052
4139
  * --control-bg
4053
4140
  * --control-fg
4054
4141
  * --control-cursor
4142
+ * --control-border-block-end-color
4143
+ * --control-border-block-end-width
4055
4144
  */
4056
4145
  .input,
4057
4146
  .textarea,