actual-css 0.3.1 → 0.4.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.
Files changed (223) hide show
  1. package/CHANGELOG.md +109 -3
  2. package/README.md +35 -30
  3. package/dist/actual.css +66 -4958
  4. package/dist/actual.full.css +7548 -0
  5. package/dist/actual.full.js +1 -0
  6. package/dist/actual.full.min.css +1 -0
  7. package/dist/actual.js +1 -1
  8. package/dist/actual.min.css +1 -1
  9. package/docs/design-notes/app-ui.md +33 -0
  10. package/docs/design-notes/cascade-layer.md +34 -13
  11. package/docs/design-notes/column-layout.md +677 -0
  12. package/docs/design-notes/enhancement-contract.md +25 -0
  13. package/docs/design-notes/forced-colors.md +3 -0
  14. package/docs/design-notes/grid.md +785 -0
  15. package/docs/design-notes/naming.md +15 -0
  16. package/docs/design-notes/references.md +1 -1
  17. package/docs/design-notes/widget-primitives.md +1 -0
  18. package/docs/navigation.json +12 -2
  19. package/docs/pages/components/alert.md +21 -14
  20. package/docs/pages/components/app-navigation.md +34 -0
  21. package/docs/pages/components/aura.md +1 -1
  22. package/docs/pages/components/avatar.md +16 -6
  23. package/docs/pages/components/badge.md +9 -7
  24. package/docs/pages/components/button.md +46 -16
  25. package/docs/pages/components/card.md +17 -9
  26. package/docs/pages/components/chat.md +8 -8
  27. package/docs/pages/components/dialog.md +7 -7
  28. package/docs/pages/components/drawer.md +3 -3
  29. package/docs/pages/components/fab.md +9 -7
  30. package/docs/pages/components/indicator.md +52 -0
  31. package/docs/pages/components/list.md +62 -0
  32. package/docs/pages/components/navbar.md +15 -8
  33. package/docs/pages/components/pagination.md +4 -3
  34. package/docs/pages/components/rating.md +68 -0
  35. package/docs/pages/components/skeleton.md +4 -4
  36. package/docs/pages/components/spinner.md +5 -5
  37. package/docs/pages/components/steps.md +57 -0
  38. package/docs/pages/components/table.md +30 -5
  39. package/docs/pages/enhancements/flyout.md +15 -15
  40. package/docs/pages/enhancements/overview.md +1 -1
  41. package/docs/pages/enhancements/scrollspy.md +6 -6
  42. package/docs/pages/enhancements/status-bar.md +5 -1
  43. package/docs/pages/enhancements/tabs.md +7 -8
  44. package/docs/pages/enhancements/tooltip.md +1 -1
  45. package/docs/pages/examples/overview.md +47 -22
  46. package/docs/pages/forms/choice-cards.md +11 -6
  47. package/docs/pages/forms/controls.md +1 -1
  48. package/docs/pages/forms/detached-actions.md +1 -1
  49. package/docs/pages/forms/floating-field.md +10 -5
  50. package/docs/pages/forms/form-actions.md +1 -1
  51. package/docs/pages/forms/input-policy.md +1 -1
  52. package/docs/pages/forms/joined-controls.md +4 -4
  53. package/docs/pages/forms/labels.md +6 -6
  54. package/docs/pages/forms/otp.md +33 -1
  55. package/docs/pages/forms/overview.md +19 -19
  56. package/docs/pages/forms/password.md +1 -1
  57. package/docs/pages/forms/range.md +1 -1
  58. package/docs/pages/forms/states-and-sizes.md +44 -48
  59. package/docs/pages/forms/sticky-actions.md +3 -3
  60. package/docs/pages/forms/switch.md +110 -2
  61. package/docs/pages/forms/validation.md +9 -9
  62. package/docs/pages/foundations/philosophy.md +1 -1
  63. package/docs/pages/foundations/theming.md +17 -2
  64. package/docs/pages/foundations/tokens.md +22 -10
  65. package/docs/pages/foundations/typography.md +50 -20
  66. package/docs/pages/guides/bootstrap.md +11 -11
  67. package/docs/pages/guides/integrating-actual-css.md +192 -0
  68. package/docs/pages/guides/introduction.md +23 -26
  69. package/docs/pages/guides/modular-import.md +173 -0
  70. package/docs/pages/guides/progressive-enhancement.md +81 -78
  71. package/docs/pages/guides/tailwind.md +6 -8
  72. package/docs/pages/layout/app-layout.md +57 -0
  73. package/docs/pages/layout/app-shell.md +28 -7
  74. package/docs/pages/layout/center.md +12 -1
  75. package/docs/pages/layout/choosing.md +54 -0
  76. package/docs/pages/layout/column-layout.md +206 -0
  77. package/docs/pages/layout/frame.md +6 -1
  78. package/docs/pages/layout/grid.md +133 -102
  79. package/docs/pages/layout/media.md +1 -1
  80. package/docs/pages/layout/overview.md +15 -2
  81. package/docs/pages/layout/scroll-snap.md +2 -2
  82. package/docs/pages/layout/scroll-target.md +45 -0
  83. package/docs/pages/layout/sidebar-layout.md +38 -18
  84. package/docs/pages/layout/stack.md +70 -1
  85. package/docs/pages/layout/switcher.md +10 -26
  86. package/docs/pages/layout/topbar.md +2 -2
  87. package/docs/pages/patterns/actions.md +1 -1
  88. package/docs/pages/patterns/code-block.md +1 -1
  89. package/docs/pages/patterns/exclusive-accordion.md +1 -1
  90. package/docs/pages/patterns/hierarchical-nav.md +1 -1
  91. package/docs/pages/patterns/icon-input.md +1 -1
  92. package/docs/pages/patterns/icon-slot.md +61 -0
  93. package/docs/pages/patterns/intent-nesting.md +1 -1
  94. package/docs/pages/patterns/navigation-list.md +1 -1
  95. package/docs/pages/patterns/overview.md +1 -1
  96. package/docs/pages/patterns/structured-lists.md +38 -40
  97. package/docs/pages/utilities/accessibility.md +3 -3
  98. package/docs/pages/utilities/flex.md +15 -15
  99. package/docs/pages/utilities/links.md +4 -4
  100. package/docs/pages/utilities/list.md +8 -8
  101. package/docs/pages/utilities/overflow.md +8 -8
  102. package/docs/pages/utilities/overview.md +1 -1
  103. package/docs/pages/utilities/shape.md +3 -3
  104. package/docs/pages/utilities/sizing-wrapping.md +23 -16
  105. package/docs/pages/utilities/spacing.md +20 -20
  106. package/docs/pages/utilities/text.md +10 -10
  107. package/llms.txt +46 -4
  108. package/package.json +43 -13
  109. package/scripts/reserved-classes.json +278 -0
  110. package/src/cli/actual-css.js +133 -0
  111. package/src/css/actual.css +1 -31
  112. package/src/css/actual.full.css +11 -2
  113. package/src/css/components/alert.css +17 -4
  114. package/src/css/components/app-nav.css +76 -0
  115. package/src/css/components/avatar.css +4 -0
  116. package/src/css/components/badge.css +19 -19
  117. package/src/css/components/breadcrumb.css +1 -1
  118. package/src/css/components/button.css +20 -32
  119. package/src/css/components/card.css +34 -9
  120. package/src/css/{optional → components}/chat.css +0 -6
  121. package/src/css/components/drawer.css +24 -5
  122. package/src/css/{optional → components}/fab.css +1 -7
  123. package/src/css/components/flyout.css +5 -0
  124. package/src/css/components/index.css +37 -27
  125. package/src/css/components/indicator.css +47 -0
  126. package/src/css/components/join.css +8 -1
  127. package/src/css/components/key.css +2 -0
  128. package/src/css/components/list.css +83 -0
  129. package/src/css/components/menu.css +11 -8
  130. package/src/css/components/meter.css +4 -1
  131. package/src/css/components/modal.css +5 -17
  132. package/src/css/components/navbar.css +6 -15
  133. package/src/css/components/pagination.css +11 -0
  134. package/src/css/components/rating.css +101 -0
  135. package/src/css/components/spinner.css +5 -0
  136. package/src/css/components/status-bar.css +5 -0
  137. package/src/css/components/steps.css +122 -0
  138. package/src/css/components/surface.css +5 -0
  139. package/src/css/components/tab.css +20 -22
  140. package/src/css/components/table.css +6 -0
  141. package/src/css/components/tooltip.css +3 -7
  142. package/src/css/{focus.css → core/focus.css} +0 -8
  143. package/src/css/core/index.css +9 -0
  144. package/src/css/core/print.css +18 -0
  145. package/src/css/{theme.css → core/theme.css} +19 -19
  146. package/src/css/{tokens.css → core/tokens.css} +16 -4
  147. package/src/css/{variants.css → core/variants.css} +3 -2
  148. package/src/css/{optional → effects}/aura.css +1 -10
  149. package/src/css/effects/index.css +2 -0
  150. package/src/css/forms/base.css +8 -0
  151. package/src/css/forms/choice-card.css +7 -15
  152. package/src/css/forms/choice.css +49 -37
  153. package/src/css/forms/control.css +4 -1
  154. package/src/css/forms/custom-select.css +2 -0
  155. package/src/css/{optional → forms}/floating-field.css +25 -7
  156. package/src/css/forms/index.css +10 -13
  157. package/src/css/forms/native.css +15 -2
  158. package/src/css/{optional → forms}/otp.css +25 -16
  159. package/src/css/forms/range.css +14 -4
  160. package/src/css/forms/switch.css +31 -29
  161. package/src/css/layout/app-layout.css +102 -0
  162. package/src/css/layout/app-shell.css +6 -0
  163. package/src/css/layout/center.css +14 -0
  164. package/src/css/layout/cluster.css +21 -0
  165. package/src/css/layout/column-layout.css +144 -0
  166. package/src/css/layout/container-query.css +15 -0
  167. package/src/css/layout/frame.css +19 -0
  168. package/src/css/layout/grid.css +155 -0
  169. package/src/css/layout/index.css +44 -0
  170. package/src/css/layout/measure.css +9 -0
  171. package/src/css/layout/media.css +10 -0
  172. package/src/css/layout/scroll-target.css +15 -0
  173. package/src/css/layout/scroller.css +41 -0
  174. package/src/css/layout/sidebar-layout.css +52 -0
  175. package/src/css/layout/stack.css +21 -0
  176. package/src/css/layout/switcher.css +23 -0
  177. package/src/css/{optional/layout-extra.css → layout/topbar.css} +0 -20
  178. package/src/css/typography/index.css +5 -0
  179. package/src/css/typography/lead.css +7 -0
  180. package/src/css/{components → typography}/overline.css +2 -2
  181. package/src/css/{prose.css → typography/prose.css} +57 -38
  182. package/src/css/{utilities.css → utilities/base.css} +2 -0
  183. package/src/css/utilities/index.css +3 -0
  184. package/src/js/escape.js +36 -0
  185. package/src/js/flyout.js +22 -12
  186. package/src/js/full.js +26 -0
  187. package/src/js/index.js +5 -21
  188. package/src/js/surface.js +1 -31
  189. package/src/js/tooltip.js +48 -19
  190. package/src/js/validation.js +1 -1
  191. package/src/tooling/css-bundle.js +308 -0
  192. package/dist/actual-themes.min.css +0 -1
  193. package/dist/optional.css +0 -1048
  194. package/dist/optional.min.css +0 -1
  195. package/docs/pages/guides/optional-components.md +0 -33
  196. package/docs/pages/layout/list.md +0 -41
  197. package/src/css/grid.css +0 -109
  198. package/src/css/layout.css +0 -123
  199. package/src/css/optional/index.css +0 -10
  200. package/src/css/optional/scroller.css +0 -63
  201. package/src/css/print.css +0 -47
  202. package/src/css/themes/corporate.css +0 -60
  203. package/src/css/themes/cyberpunk.css +0 -60
  204. package/src/css/themes/dim.css +0 -33
  205. package/src/css/themes/edge.css +0 -117
  206. package/src/css/themes/forest.css +0 -65
  207. package/src/css/themes/gradient.css +0 -97
  208. package/src/css/themes/index.css +0 -47
  209. package/src/css/themes/indigo.css +0 -33
  210. package/src/css/themes/lavender.css +0 -66
  211. package/src/css/themes/mono.css +0 -60
  212. package/src/css/themes/neon.css +0 -66
  213. package/src/css/themes/ocean.css +0 -60
  214. package/src/css/themes/petrol.css +0 -64
  215. package/src/css/themes/spruce.css +0 -64
  216. package/src/css/themes/square.css +0 -65
  217. package/src/css/themes/sunset.css +0 -66
  218. /package/src/css/{base.css → core/base.css} +0 -0
  219. /package/src/css/{intents.css → core/intents.css} +0 -0
  220. /package/src/css/{reset.css → core/reset.css} +0 -0
  221. /package/src/css/{optional → layout}/scroll-snap.css +0 -0
  222. /package/src/css/{optional/typography-fluid.css → typography/fluid.css} +0 -0
  223. /package/src/css/{optional/utilities-extra.css → utilities/extra.css} +0 -0
@@ -1,31 +1 @@
1
- @import "./reset.css";
2
- @import "./tokens.css";
3
- @import "./theme.css";
4
- @import "./base.css";
5
- @import "./intents.css";
6
- /* Prose before layout: layout primitives (.stack, .cluster) reset child
7
- margins and must win over .prose's flow margins at equal specificity —
8
- source order is the tie-breaker, so prose loads first. */
9
- @import "./prose.css";
10
- @import "./layout.css";
11
- @import "./grid.css";
12
- @import "./variants.css";
13
- @import "./components/index.css";
14
- @import "./forms/index.css";
15
- @import "./utilities.css";
16
- /* Joined controls are a component, but must load after the controls it groups. */
17
- @import "./components/join.css";
18
- @import "./focus.css";
19
- @import "./print.css";
20
-
21
- /*
22
- * NOTE: The 'optional/' directory contains layout and utility extensions
23
- * (such as optional grids, split, visual effects, and extra utility classes
24
- * like .gap-sm, .flex-nowrap, .shadow-sm, etc.). These are intentionally not
25
- * bundled here. Two entry points ship them:
26
- *
27
- * actual-css/css/optional/index optional layer only
28
- * actual-css/css/actual.full core + optional
29
- *
30
- * Import one of these paths in your project when you need them.
31
- */
1
+ @import "./core/index.css";
@@ -1,2 +1,11 @@
1
- @import "actual.css";
2
- @import "optional/index.css";
1
+ /* Core, then every shipped functional family in cascade order. Typography
2
+ precedes Layout because direct-child layout resets intentionally win over
3
+ equal-specificity prose flow margins. Forms precede Components because of
4
+ join. Do not alphabetize these top-level imports. */
5
+ @import "./actual.css";
6
+ @import "./typography/index.css";
7
+ @import "./layout/index.css";
8
+ @import "./forms/index.css";
9
+ @import "./components/index.css";
10
+ @import "./effects/index.css";
11
+ @import "./utilities/index.css";
@@ -20,6 +20,7 @@
20
20
  *
21
21
  * Internal:
22
22
  * --alert-default-bg
23
+ * --alert-default-fg
23
24
  * --alert-default-border
24
25
  * --alert-bg
25
26
  * --alert-fg
@@ -42,9 +43,10 @@
42
43
  --alert-font-size: var(--font-size-md);
43
44
  --alert-icon-size: 1.25rem;
44
45
  --alert-default-bg: var(--surface-subtle);
46
+ --alert-default-fg: var(--intent, var(--text));
45
47
  --alert-default-border: var(--border);
46
48
  --alert-bg: var(--ui-bg, var(--alert-default-bg));
47
- --alert-fg: var(--ui-fg, var(--intent, var(--text)));
49
+ --alert-fg: var(--ui-fg, var(--alert-default-fg));
48
50
  --alert-border: var(--ui-border, var(--alert-default-border));
49
51
 
50
52
  padding-block: var(--alert-pad-block);
@@ -76,15 +78,20 @@
76
78
  .alert {
77
79
  /* @sync soft-recipe */
78
80
  --alert-default-bg: color-mix(
79
- in oklch,
81
+ in oklab,
80
82
  var(--surface) var(--soft-bg-mix),
81
83
  var(--intent, var(--neutral))
82
84
  );
83
85
  --alert-default-border: color-mix(
84
- in oklch,
86
+ in oklab,
85
87
  var(--surface) var(--soft-border-mix),
86
88
  var(--intent, var(--neutral))
87
89
  );
90
+ --alert-default-fg: color-mix(
91
+ in oklab,
92
+ var(--text),
93
+ var(--intent, var(--text)) var(--soft-fg-mix)
94
+ );
88
95
  /* @sync soft-recipe */
89
96
  }
90
97
  }
@@ -230,7 +237,7 @@
230
237
  @supports (color: color-mix(in oklch, red, white)) {
231
238
  .alert.admonition > .alert-title {
232
239
  background: color-mix(
233
- in oklch,
240
+ in oklab,
234
241
  var(--surface) var(--soft-bg-mix),
235
242
  var(--intent, var(--neutral))
236
243
  );
@@ -258,3 +265,9 @@
258
265
  .alert.admonition > .alert-body > :last-child {
259
266
  margin-block-end: 0;
260
267
  }
268
+ @media print {
269
+ .alert {
270
+ border: var(--border-width) solid CanvasText;
271
+ break-inside: avoid;
272
+ }
273
+ }
@@ -0,0 +1,76 @@
1
+ /*
2
+ * Primary application navigation. The component owns the navigation surface
3
+ * and item states; app-layout owns whether that surface is a bottom bar or
4
+ * labelled side navigation.
5
+ *
6
+ * Public hooks:
7
+ * --app-nav-size
8
+ * --app-nav-gap
9
+ */
10
+ :where(.app-nav) {
11
+ --ui-bg: initial;
12
+ --ui-fg: initial;
13
+ --ui-border: initial;
14
+ }
15
+
16
+ .app-nav {
17
+ --app-nav-size: 4rem;
18
+ --app-nav-gap: var(--space-10);
19
+
20
+ position: sticky;
21
+ inset-block-end: 0;
22
+ z-index: var(--z-sticky);
23
+ display: grid;
24
+ grid-auto-flow: column;
25
+ grid-auto-columns: minmax(0, 1fr);
26
+ gap: var(--app-nav-gap);
27
+ min-block-size: calc(var(--app-nav-size) + env(safe-area-inset-bottom));
28
+ padding-block: var(--space-10) max(var(--space-10), env(safe-area-inset-bottom));
29
+ padding-inline: var(--space-10);
30
+ border-block-start: var(--border-width) solid var(--ui-border, var(--border));
31
+ background: var(--ui-bg, var(--surface));
32
+ color: var(--ui-fg, var(--text-muted));
33
+ }
34
+
35
+ .app-nav > a {
36
+ display: grid;
37
+ grid-template-rows: auto auto;
38
+ place-content: center;
39
+ justify-items: center;
40
+ gap: var(--space-10);
41
+ min-inline-size: 0;
42
+ min-block-size: var(--control-size-lg);
43
+ padding-inline: var(--space-10);
44
+ border-radius: var(--radius);
45
+ color: inherit;
46
+ font-size: var(--font-size-xs);
47
+ font-weight: var(--font-weight-medium);
48
+ line-height: var(--line-height-tight);
49
+ text-align: center;
50
+ text-decoration: none;
51
+ overflow-wrap: anywhere;
52
+ }
53
+
54
+ .app-nav > a > :where(svg, img, [aria-hidden="true"]) {
55
+ inline-size: 1.5rem;
56
+ block-size: 1.5rem;
57
+ font-size: 1.5rem;
58
+ line-height: 1;
59
+ }
60
+
61
+ .app-nav > a:hover {
62
+ background: var(--surface-subtle);
63
+ color: var(--text);
64
+ }
65
+
66
+ .app-nav > a:where([aria-current]:not([aria-current="false"])) {
67
+ background: var(--state-selected);
68
+ color: var(--state-selected-fg);
69
+ font-weight: var(--font-weight-bold);
70
+ }
71
+
72
+ @media print {
73
+ .app-nav {
74
+ display: none;
75
+ }
76
+ }
@@ -69,6 +69,9 @@
69
69
  }
70
70
  }
71
71
 
72
+ /* The initials element (a span or an accessible <abbr title="…">) fills the box.
73
+ text-decoration: none neutralizes the UA dotted underline on <abbr title="…">
74
+ inside the circular avatar. */
72
75
  .avatar > :first-child:not(:where(img, picture, .badge)) {
73
76
  display: inline-grid;
74
77
  place-items: center;
@@ -76,6 +79,7 @@
76
79
  block-size: 100%;
77
80
  overflow: hidden;
78
81
  border-radius: inherit;
82
+ text-decoration: none;
79
83
  }
80
84
 
81
85
  .avatar > img,
@@ -35,7 +35,7 @@
35
35
  .badge {
36
36
  --badge-size: var(--density-compact-size);
37
37
  --badge-pad-x: 0.55em;
38
- --badge-font-size: 0.8125rem;
38
+ --badge-font-size: var(--font-size-xs);
39
39
  --badge-dot-size: 0.5rem;
40
40
  --badge-default-bg: var(--surface-subtle);
41
41
  --badge-default-fg: var(--intent, var(--text));
@@ -76,15 +76,20 @@
76
76
  .badge {
77
77
  /* @sync soft-recipe */
78
78
  --badge-default-bg: color-mix(
79
- in oklch,
79
+ in oklab,
80
80
  var(--surface) var(--soft-bg-mix),
81
81
  var(--intent, var(--neutral))
82
82
  );
83
83
  --badge-default-border: color-mix(
84
- in oklch,
84
+ in oklab,
85
85
  var(--surface) var(--soft-border-mix),
86
86
  var(--intent, var(--neutral))
87
87
  );
88
+ --badge-default-fg: color-mix(
89
+ in oklab,
90
+ var(--text),
91
+ var(--intent, var(--text)) var(--soft-fg-mix)
92
+ );
88
93
  /* @sync soft-recipe */
89
94
  }
90
95
  }
@@ -104,8 +109,8 @@
104
109
  display: inline-grid;
105
110
  place-items: center;
106
111
  flex: 0 0 auto;
107
- min-inline-size: 1.5rem;
108
- min-block-size: 1.5rem;
112
+ min-inline-size: var(--density-compact-size);
113
+ min-block-size: var(--density-compact-size);
109
114
  margin-block: -0.25rem;
110
115
  margin-inline-start: -0.15em;
111
116
  margin-inline-end: -0.45em;
@@ -136,7 +141,7 @@
136
141
 
137
142
  :is(a.badge, button.badge):where(:not(.solid, .outline, :empty)) {
138
143
  --badge-default-hover-bg: color-mix(
139
- in oklch,
144
+ in oklab,
140
145
  var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
141
146
  var(--intent, var(--neutral))
142
147
  );
@@ -152,6 +157,7 @@
152
157
  }
153
158
 
154
159
  :where(a.badge, button.badge):not(:where(:disabled, [aria-disabled="true"], .outline)):hover {
160
+ /* Huge inset spread paints the overlay over the pill radius (see button.css). */
155
161
  box-shadow: inset 0 0 0 999px var(--hover-overlay);
156
162
  }
157
163
 
@@ -174,20 +180,14 @@ a.badge[aria-disabled="true"] {
174
180
  background: ButtonText;
175
181
  }
176
182
 
177
- :where(a.badge, button.badge):hover:not(:where(:disabled, [aria-disabled="true"])) {
178
- outline: var(--border-width) solid ButtonText;
179
- outline-offset: 2px;
180
- }
181
-
182
- .badge > button:not(:disabled):hover {
183
- outline: var(--border-width) solid currentColor;
184
- outline-offset: 1px;
185
- }
186
-
187
- button.badge:disabled,
188
183
  .badge[aria-disabled="true"] {
189
- border-color: GrayText;
190
- background: Canvas;
191
184
  color: GrayText;
185
+ border-color: GrayText;
186
+ }
187
+ }
188
+ @media print {
189
+ .badge {
190
+ border: var(--border-width) solid CanvasText;
191
+ break-inside: avoid;
192
192
  }
193
193
  }
@@ -42,7 +42,7 @@
42
42
  border-radius: var(--radius-sm);
43
43
  }
44
44
 
45
- .breadcrumb :where(a, span):where([aria-current]:not([aria-current="false"])) {
45
+ .breadcrumb :where(li, a, span):where([aria-current]:not([aria-current="false"])) {
46
46
  color: var(--text);
47
47
  font-weight: var(--font-weight-medium);
48
48
  }
@@ -92,7 +92,8 @@
92
92
 
93
93
  .btn.icon-only {
94
94
  inline-size: var(--btn-min-size);
95
- padding-inline: 0;
95
+ block-size: var(--btn-min-size);
96
+ padding: 0;
96
97
  }
97
98
 
98
99
  @supports (color: color-mix(in oklch, red, white)) {
@@ -133,7 +134,7 @@
133
134
 
134
135
  .btn.soft {
135
136
  --ui-hover-bg: color-mix(
136
- in oklch,
137
+ in oklab,
137
138
  var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
138
139
  var(--intent, var(--neutral))
139
140
  );
@@ -149,6 +150,9 @@
149
150
  the overlay darkening for solid/soft is .btn-specific. */
150
151
  .btn:not(:where(:disabled, [aria-disabled="true"])):hover {
151
152
  background: var(--ui-hover-bg, var(--btn-default-hover-bg));
153
+ /* Huge inset spread paints the overlay over any border-radius without a
154
+ pseudo-element; a plain background would replace the fill instead of
155
+ darkening it. */
152
156
  box-shadow: inset 0 0 0 999px var(--btn-hover-overlay);
153
157
  }
154
158
 
@@ -161,14 +165,15 @@
161
165
  --ui-fg: var(--intent, currentColor);
162
166
  --ui-border: transparent;
163
167
  --btn-radius: var(--radius-sm);
164
- --btn-border-block-end-color: transparent;
165
- --btn-border-block-end-width: var(--border-width);
166
168
 
167
169
  min-block-size: auto;
168
170
  padding-inline: 0;
169
171
  inline-size: fit-content;
170
172
  max-inline-size: 100%;
171
- border-color: transparent;
173
+ /* No border at all: a link reads through its underline, and the inherited
174
+ .btn border made transparent would turn into a visible box in forced
175
+ colors. :focus-visible keeps its own outline. */
176
+ border: 0;
172
177
  text-decoration: underline;
173
178
  text-underline-offset: 0.18em;
174
179
  }
@@ -229,36 +234,19 @@ a.btn[aria-disabled="true"] {
229
234
  }
230
235
 
231
236
  @media (forced-colors: active) {
232
- .btn {
233
- border-color: ButtonText;
234
- background: ButtonFace;
235
- color: ButtonText;
236
- box-shadow: none;
237
- }
238
-
239
- .btn.link {
240
- border-color: transparent;
241
- background: Canvas;
242
- color: LinkText;
243
- }
244
-
245
- .btn:is([aria-pressed="true"], [aria-current="page"]):not(.link) {
246
- border-color: Highlight;
247
- background: Highlight;
248
- color: HighlightText;
249
- forced-color-adjust: none;
237
+ a.btn[aria-disabled="true"] {
238
+ color: GrayText;
239
+ border-color: GrayText;
250
240
  }
241
+ }
251
242
 
252
- .btn:hover:not(:where(:disabled, [aria-disabled="true"], .link)) {
253
- outline: var(--border-width) solid ButtonText;
254
- outline-offset: 2px;
243
+ @media print {
244
+ :is(button, input[type="button"], input[type="submit"], input[type="reset"]).btn {
245
+ display: none !important;
255
246
  }
256
247
 
257
- .btn:disabled,
258
- a.btn[aria-disabled="true"] {
259
- border-color: GrayText;
260
- background: Canvas;
261
- color: GrayText;
262
- forced-color-adjust: auto;
248
+ a.btn[href^="http"]::after {
249
+ content: " (" attr(href) ")";
250
+ font-size: 0.875em;
263
251
  }
264
252
  }
@@ -2,11 +2,16 @@
2
2
  * Cards provide a neutral raised surface for grouped content.
3
3
  *
4
4
  * Surface, border, radius, padding, density, and elevation stay local here.
5
- * Direct header/footer children are structural slots. Page layout remains the
6
- * job of markup and layout primitives outside the card.
5
+ * Direct header/footer children are structural slots; a direct footer is
6
+ * anchored to the card's bottom edge when extra block space is available (so
7
+ * price/action rows align across equal-height cards in a grid). A bare card
8
+ * owns its direct-child rhythm; a layout primitive on the same element takes
9
+ * over that layout. Page layout remains the job of markup and layout
10
+ * primitives outside the card.
7
11
  *
8
12
  * Public hooks:
9
13
  * --card-pad
14
+ * --card-gap
10
15
  * --card-radius
11
16
  * --card-max-inline-size
12
17
  *
@@ -30,6 +35,7 @@
30
35
 
31
36
  .card {
32
37
  --card-pad: var(--space-40);
38
+ --card-gap: var(--space-30);
33
39
  --card-radius: var(--radius-lg);
34
40
  --card-max-inline-size: none;
35
41
  --card-bg: var(--ui-bg, var(--surface-raised));
@@ -47,8 +53,23 @@
47
53
  border-radius: var(--card-radius);
48
54
  }
49
55
 
50
- .card > * {
56
+ /* The box lives at zero specificity so a layout primitive applied to the same
57
+ element owns it. components/ is imported after layout/, so a plain .card
58
+ declaration would beat .media (and any other non-column primitive) on source
59
+ order alone and silently flatten `class="card media"` into a column. The
60
+ column is still the default a bare .card gets, and > footer's margin-auto
61
+ bottom-anchoring depends on it. */
62
+ :where(.card) {
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: var(--card-gap);
66
+ }
67
+
68
+ /* Native block margins would add to flex gap because flex-item margins never
69
+ collapse. Zero specificity keeps intentional child utilities available. */
70
+ :where(.card) > * {
51
71
  min-inline-size: 0;
72
+ margin-block: 0;
52
73
  }
53
74
 
54
75
  .card > :where(header, footer) {
@@ -69,12 +90,10 @@
69
90
  align-items: var(--items-align, flex-start);
70
91
  }
71
92
 
72
- .card > :first-child {
73
- margin-block-start: 0;
74
- }
75
-
76
- .card > :last-child {
77
- margin-block-end: 0;
93
+ /* A direct footer is pinned to the bottom when the card has free space.
94
+ Specificity beats .stack > * so a .card.stack keeps this contract. */
95
+ .card > footer {
96
+ margin-block-start: auto;
78
97
  }
79
98
 
80
99
  .card.raised {
@@ -126,3 +145,9 @@
126
145
  .card > :where(header, footer, figure).bleed {
127
146
  padding: var(--card-pad);
128
147
  }
148
+ @media print {
149
+ .card {
150
+ border: var(--border-width) solid CanvasText;
151
+ break-inside: avoid;
152
+ }
153
+ }
@@ -97,9 +97,3 @@
97
97
  column-gap: 0;
98
98
  }
99
99
  }
100
-
101
- @media (forced-colors: active) {
102
- .chat-bubble {
103
- border-color: CanvasText;
104
- }
105
- }
@@ -108,8 +108,8 @@ dialog.drawer nav {
108
108
  }
109
109
  }
110
110
 
111
- /* RTL: swap closed transform directions so the drawer slides from the correct
112
- inline side. */
111
+ /* Safari before 16.4 does not support :dir(); keep the ancestor-attribute
112
+ path as the Minimal-tier fallback. */
113
113
  [dir="rtl"] dialog.drawer {
114
114
  --drawer-closed-translate: 100%;
115
115
  }
@@ -118,9 +118,23 @@ dialog.drawer nav {
118
118
  --drawer-closed-translate: -100%;
119
119
  }
120
120
 
121
- @media (forced-colors: active) {
122
- dialog.drawer::backdrop {
123
- background: Canvas;
121
+ /* :dir() follows inherited HTML directionality and handles nested direction
122
+ changes. Explicit ltr rules neutralize an outer RTL fallback selector. */
123
+ @supports selector(:dir(rtl)) {
124
+ dialog.drawer:dir(ltr) {
125
+ --drawer-closed-translate: -100%;
126
+ }
127
+
128
+ dialog.drawer[data-side="end"]:dir(ltr) {
129
+ --drawer-closed-translate: 100%;
130
+ }
131
+
132
+ dialog.drawer:dir(rtl) {
133
+ --drawer-closed-translate: 100%;
134
+ }
135
+
136
+ dialog.drawer[data-side="end"]:dir(rtl) {
137
+ --drawer-closed-translate: -100%;
124
138
  }
125
139
  }
126
140
 
@@ -137,3 +151,8 @@ dialog.drawer > header,
137
151
  dialog.drawer > form > header {
138
152
  padding-inline-end: calc(var(--control-size) + var(--space-30));
139
153
  }
154
+ @media print {
155
+ dialog.drawer[open] {
156
+ display: none !important;
157
+ }
158
+ }
@@ -68,6 +68,7 @@
68
68
  max-inline-size: 16rem;
69
69
  padding: var(--space-10) var(--space-20);
70
70
  border-radius: var(--radius);
71
+ border: var(--border-width) solid transparent;
71
72
  background: var(--surface);
72
73
  color: var(--text);
73
74
  box-shadow: var(--shadow);
@@ -90,13 +91,6 @@
90
91
  transform: rotate(45deg);
91
92
  }
92
93
 
93
- @media (forced-colors: active) {
94
- .fab-label {
95
- border: var(--border-width) solid CanvasText;
96
- box-shadow: none;
97
- }
98
- }
99
-
100
94
  @media print {
101
95
  .fab {
102
96
  display: none;
@@ -170,3 +170,8 @@
170
170
  /* Vertical dividers — .menu-separator (menu.css) is the canonical way to
171
171
  separate menu items. Flyout panels that are not menus use auto grid gap
172
172
  declared on .flyout itself. */
173
+ @media print {
174
+ .flyout {
175
+ display: none !important;
176
+ }
177
+ }
@@ -1,42 +1,52 @@
1
1
  /* Controls */
2
- @import "button.css";
3
- @import "badge.css";
4
- @import "alert.css";
5
- @import "actions.css";
2
+ @import "./button.css";
3
+ @import "./badge.css";
4
+ @import "./alert.css";
5
+ @import "./actions.css";
6
6
 
7
7
  /* Surfaces */
8
- @import "card.css";
9
- @import "surface.css";
10
- @import "modal.css";
11
- @import "drawer.css";
8
+ @import "./card.css";
9
+ @import "./surface.css";
10
+ @import "./modal.css";
11
+ @import "./drawer.css";
12
12
 
13
13
  /* Overlays */
14
- @import "flyout.css";
15
- @import "menu.css";
16
- @import "tooltip.css";
14
+ @import "./flyout.css";
15
+ @import "./menu.css";
16
+ @import "./tooltip.css";
17
17
 
18
18
  /* Disclosure */
19
- @import "accordion.css";
19
+ @import "./accordion.css";
20
20
 
21
21
  /* Feedback */
22
- @import "spinner.css";
23
- @import "skeleton.css";
24
- @import "busy.css";
25
- @import "progress.css";
26
- @import "status-bar.css";
22
+ @import "./spinner.css";
23
+ @import "./skeleton.css";
24
+ @import "./busy.css";
25
+ @import "./progress.css";
26
+ @import "./status-bar.css";
27
27
 
28
28
  /* Display */
29
- @import "avatar.css";
30
- @import "key.css";
31
- @import "overline.css";
29
+ @import "./avatar.css";
30
+ @import "./indicator.css";
31
+ @import "./key.css";
32
+ @import "./chat.css";
33
+ @import "./fab.css";
34
+ @import "./rating.css";
35
+ @import "./steps.css";
32
36
 
33
37
  /* Navigation */
34
- @import "navbar.css";
35
- @import "breadcrumb.css";
36
- @import "pagination.css";
37
- @import "tab.css";
38
- @import "scrollspy.css";
38
+ @import "./app-nav.css";
39
+ @import "./navbar.css";
40
+ @import "./breadcrumb.css";
41
+ @import "./pagination.css";
42
+ @import "./tab.css";
43
+ @import "./scrollspy.css";
39
44
 
40
45
  /* Data */
41
- @import "table.css";
42
- @import "meter.css";
46
+ @import "./list.css";
47
+ @import "./table.css";
48
+ @import "./meter.css";
49
+
50
+ /* Composed controls — imported last so equal-specificity corner rules remain
51
+ later than the controls they group. */
52
+ @import "./join.css";
@@ -0,0 +1,47 @@
1
+ /*
2
+ * Indicator — generic corner positioning for overlaid component content.
3
+ *
4
+ * The container only establishes the containing block. The item owns no
5
+ * paint, size, or ring so badges and other components keep their own visual
6
+ * contract.
7
+ *
8
+ * Internal:
9
+ * --indicator-translate-x
10
+ * --indicator-translate-y
11
+ */
12
+ .indicator {
13
+ position: relative;
14
+ }
15
+
16
+ .indicator-item {
17
+ --indicator-translate-x: 50%;
18
+ --indicator-translate-y: -50%;
19
+
20
+ position: absolute;
21
+ inset-block-start: 0;
22
+ inset-inline-end: 0;
23
+ z-index: 1;
24
+ transform: translate(var(--indicator-translate-x), var(--indicator-translate-y));
25
+ }
26
+
27
+ .indicator-item.start {
28
+ --indicator-translate-x: -50%;
29
+
30
+ inset-inline-start: 0;
31
+ inset-inline-end: auto;
32
+ }
33
+
34
+ .indicator-item.bottom {
35
+ --indicator-translate-y: 50%;
36
+
37
+ inset-block-start: auto;
38
+ inset-block-end: 0;
39
+ }
40
+
41
+ .indicator-item:dir(rtl) {
42
+ --indicator-translate-x: -50%;
43
+ }
44
+
45
+ .indicator-item.start:dir(rtl) {
46
+ --indicator-translate-x: 50%;
47
+ }