actual-css 0.3.1 → 0.4.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.
Files changed (203) hide show
  1. package/CHANGELOG.md +67 -3
  2. package/README.md +35 -30
  3. package/dist/actual.css +73 -4946
  4. package/dist/actual.full.css +7090 -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/cascade-layer.md +34 -13
  10. package/docs/design-notes/column-layout.md +677 -0
  11. package/docs/design-notes/forced-colors.md +3 -0
  12. package/docs/design-notes/grid.md +612 -0
  13. package/docs/design-notes/naming.md +15 -0
  14. package/docs/design-notes/widget-primitives.md +1 -0
  15. package/docs/navigation.json +5 -1
  16. package/docs/pages/components/alert.md +16 -14
  17. package/docs/pages/components/aura.md +1 -1
  18. package/docs/pages/components/avatar.md +6 -6
  19. package/docs/pages/components/badge.md +7 -7
  20. package/docs/pages/components/button.md +16 -16
  21. package/docs/pages/components/card.md +13 -9
  22. package/docs/pages/components/chat.md +8 -8
  23. package/docs/pages/components/dialog.md +7 -7
  24. package/docs/pages/components/drawer.md +3 -3
  25. package/docs/pages/components/fab.md +7 -7
  26. package/docs/pages/components/navbar.md +9 -8
  27. package/docs/pages/components/pagination.md +4 -3
  28. package/docs/pages/components/skeleton.md +4 -4
  29. package/docs/pages/components/spinner.md +5 -5
  30. package/docs/pages/components/table.md +30 -5
  31. package/docs/pages/enhancements/flyout.md +13 -13
  32. package/docs/pages/enhancements/overview.md +1 -1
  33. package/docs/pages/enhancements/scrollspy.md +6 -6
  34. package/docs/pages/enhancements/status-bar.md +5 -1
  35. package/docs/pages/enhancements/tabs.md +6 -6
  36. package/docs/pages/enhancements/tooltip.md +1 -1
  37. package/docs/pages/examples/overview.md +39 -22
  38. package/docs/pages/forms/choice-cards.md +11 -6
  39. package/docs/pages/forms/controls.md +1 -1
  40. package/docs/pages/forms/detached-actions.md +1 -1
  41. package/docs/pages/forms/floating-field.md +10 -5
  42. package/docs/pages/forms/form-actions.md +1 -1
  43. package/docs/pages/forms/input-policy.md +1 -1
  44. package/docs/pages/forms/joined-controls.md +4 -4
  45. package/docs/pages/forms/labels.md +6 -6
  46. package/docs/pages/forms/otp.md +1 -1
  47. package/docs/pages/forms/overview.md +19 -19
  48. package/docs/pages/forms/password.md +1 -1
  49. package/docs/pages/forms/range.md +1 -1
  50. package/docs/pages/forms/states-and-sizes.md +41 -45
  51. package/docs/pages/forms/sticky-actions.md +3 -3
  52. package/docs/pages/forms/switch.md +110 -2
  53. package/docs/pages/forms/validation.md +9 -9
  54. package/docs/pages/foundations/philosophy.md +1 -1
  55. package/docs/pages/foundations/theming.md +17 -2
  56. package/docs/pages/foundations/tokens.md +22 -10
  57. package/docs/pages/foundations/typography.md +50 -20
  58. package/docs/pages/guides/bootstrap.md +11 -11
  59. package/docs/pages/guides/integrating-actual-css.md +186 -0
  60. package/docs/pages/guides/introduction.md +23 -26
  61. package/docs/pages/guides/modular-import.md +173 -0
  62. package/docs/pages/guides/progressive-enhancement.md +81 -78
  63. package/docs/pages/guides/tailwind.md +6 -8
  64. package/docs/pages/layout/app-shell.md +1 -1
  65. package/docs/pages/layout/center.md +1 -1
  66. package/docs/pages/layout/choosing.md +39 -0
  67. package/docs/pages/layout/column-layout.md +206 -0
  68. package/docs/pages/layout/frame.md +6 -1
  69. package/docs/pages/layout/grid.md +100 -102
  70. package/docs/pages/layout/list.md +1 -1
  71. package/docs/pages/layout/media.md +1 -1
  72. package/docs/pages/layout/overview.md +15 -2
  73. package/docs/pages/layout/scroll-snap.md +2 -2
  74. package/docs/pages/layout/scroll-target.md +45 -0
  75. package/docs/pages/layout/sidebar-layout.md +20 -19
  76. package/docs/pages/layout/stack.md +53 -1
  77. package/docs/pages/layout/switcher.md +10 -26
  78. package/docs/pages/layout/topbar.md +2 -2
  79. package/docs/pages/patterns/actions.md +1 -1
  80. package/docs/pages/patterns/code-block.md +1 -1
  81. package/docs/pages/patterns/exclusive-accordion.md +1 -1
  82. package/docs/pages/patterns/hierarchical-nav.md +1 -1
  83. package/docs/pages/patterns/icon-input.md +1 -1
  84. package/docs/pages/patterns/intent-nesting.md +1 -1
  85. package/docs/pages/patterns/navigation-list.md +1 -1
  86. package/docs/pages/patterns/overview.md +1 -1
  87. package/docs/pages/patterns/structured-lists.md +1 -1
  88. package/docs/pages/utilities/accessibility.md +3 -3
  89. package/docs/pages/utilities/flex.md +15 -15
  90. package/docs/pages/utilities/links.md +4 -4
  91. package/docs/pages/utilities/list.md +8 -8
  92. package/docs/pages/utilities/overflow.md +8 -8
  93. package/docs/pages/utilities/overview.md +1 -1
  94. package/docs/pages/utilities/shape.md +3 -3
  95. package/docs/pages/utilities/sizing-wrapping.md +23 -16
  96. package/docs/pages/utilities/spacing.md +20 -20
  97. package/docs/pages/utilities/text.md +10 -10
  98. package/llms.txt +46 -4
  99. package/package.json +42 -13
  100. package/scripts/reserved-classes.json +261 -0
  101. package/src/cli/actual-css.js +133 -0
  102. package/src/css/actual.css +1 -31
  103. package/src/css/actual.full.css +11 -2
  104. package/src/css/components/alert.css +17 -4
  105. package/src/css/components/badge.css +18 -6
  106. package/src/css/components/breadcrumb.css +1 -1
  107. package/src/css/components/button.css +19 -1
  108. package/src/css/components/card.css +27 -2
  109. package/src/css/components/drawer.css +27 -2
  110. package/src/css/components/flyout.css +5 -0
  111. package/src/css/components/index.css +32 -27
  112. package/src/css/components/join.css +8 -1
  113. package/src/css/components/key.css +2 -0
  114. package/src/css/components/menu.css +11 -1
  115. package/src/css/components/meter.css +4 -1
  116. package/src/css/components/modal.css +5 -0
  117. package/src/css/components/pagination.css +11 -0
  118. package/src/css/components/spinner.css +5 -0
  119. package/src/css/components/status-bar.css +5 -0
  120. package/src/css/components/surface.css +5 -0
  121. package/src/css/components/table.css +6 -0
  122. package/src/css/components/tooltip.css +5 -0
  123. package/src/css/core/index.css +9 -0
  124. package/src/css/core/print.css +18 -0
  125. package/src/css/{theme.css → core/theme.css} +20 -10
  126. package/src/css/{tokens.css → core/tokens.css} +15 -2
  127. package/src/css/{variants.css → core/variants.css} +3 -2
  128. package/src/css/effects/index.css +2 -0
  129. package/src/css/forms/base.css +8 -0
  130. package/src/css/forms/choice-card.css +5 -4
  131. package/src/css/forms/choice.css +49 -37
  132. package/src/css/forms/control.css +4 -1
  133. package/src/css/forms/custom-select.css +2 -0
  134. package/src/css/{optional → forms}/floating-field.css +25 -7
  135. package/src/css/forms/index.css +10 -13
  136. package/src/css/forms/native.css +15 -2
  137. package/src/css/{optional → forms}/otp.css +20 -16
  138. package/src/css/forms/switch.css +31 -29
  139. package/src/css/layout/app-shell.css +6 -0
  140. package/src/css/layout/center.css +13 -0
  141. package/src/css/layout/cluster.css +21 -0
  142. package/src/css/layout/column-layout.css +144 -0
  143. package/src/css/layout/container-query.css +13 -0
  144. package/src/css/layout/frame.css +19 -0
  145. package/src/css/layout/grid.css +141 -0
  146. package/src/css/layout/index.css +42 -0
  147. package/src/css/layout/measure.css +9 -0
  148. package/src/css/layout/media.css +10 -0
  149. package/src/css/layout/scroll-target.css +15 -0
  150. package/src/css/layout/scroller.css +41 -0
  151. package/src/css/layout/sidebar-layout.css +31 -0
  152. package/src/css/layout/stack.css +21 -0
  153. package/src/css/layout/switcher.css +23 -0
  154. package/src/css/{optional/layout-extra.css → layout/topbar.css} +0 -20
  155. package/src/css/typography/index.css +5 -0
  156. package/src/css/typography/lead.css +7 -0
  157. package/src/css/{components → typography}/overline.css +2 -2
  158. package/src/css/{prose.css → typography/prose.css} +53 -38
  159. package/src/css/{utilities.css → utilities/base.css} +2 -0
  160. package/src/css/utilities/index.css +3 -0
  161. package/src/js/escape.js +36 -0
  162. package/src/js/flyout.js +22 -12
  163. package/src/js/full.js +26 -0
  164. package/src/js/index.js +5 -21
  165. package/src/js/surface.js +1 -31
  166. package/src/js/tooltip.js +46 -18
  167. package/src/js/validation.js +1 -1
  168. package/src/tooling/css-bundle.js +308 -0
  169. package/dist/actual-themes.min.css +0 -1
  170. package/dist/optional.css +0 -1048
  171. package/dist/optional.min.css +0 -1
  172. package/docs/pages/guides/optional-components.md +0 -33
  173. package/src/css/grid.css +0 -109
  174. package/src/css/layout.css +0 -123
  175. package/src/css/optional/index.css +0 -10
  176. package/src/css/optional/scroller.css +0 -63
  177. package/src/css/print.css +0 -47
  178. package/src/css/themes/corporate.css +0 -60
  179. package/src/css/themes/cyberpunk.css +0 -60
  180. package/src/css/themes/dim.css +0 -33
  181. package/src/css/themes/edge.css +0 -117
  182. package/src/css/themes/forest.css +0 -65
  183. package/src/css/themes/gradient.css +0 -97
  184. package/src/css/themes/index.css +0 -47
  185. package/src/css/themes/indigo.css +0 -33
  186. package/src/css/themes/lavender.css +0 -66
  187. package/src/css/themes/mono.css +0 -60
  188. package/src/css/themes/neon.css +0 -66
  189. package/src/css/themes/ocean.css +0 -60
  190. package/src/css/themes/petrol.css +0 -64
  191. package/src/css/themes/spruce.css +0 -64
  192. package/src/css/themes/square.css +0 -65
  193. package/src/css/themes/sunset.css +0 -66
  194. /package/src/css/{optional → components}/chat.css +0 -0
  195. /package/src/css/{optional → components}/fab.css +0 -0
  196. /package/src/css/{base.css → core/base.css} +0 -0
  197. /package/src/css/{focus.css → core/focus.css} +0 -0
  198. /package/src/css/{intents.css → core/intents.css} +0 -0
  199. /package/src/css/{reset.css → core/reset.css} +0 -0
  200. /package/src/css/{optional → effects}/aura.css +0 -0
  201. /package/src/css/{optional → layout}/scroll-snap.css +0 -0
  202. /package/src/css/{optional/typography-fluid.css → typography/fluid.css} +0 -0
  203. /package/src/css/{optional/utilities-extra.css → utilities/extra.css} +0 -0
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Media frame with a fixed ratio.
3
+ *
4
+ * Public hooks:
5
+ * --frame-ratio
6
+ *
7
+ * Child contract: direct media children (img/video/iframe/canvas/svg) are
8
+ * sized to fill the frame.
9
+ */
10
+ .frame {
11
+ overflow: hidden;
12
+ aspect-ratio: var(--frame-ratio, 16 / 9);
13
+ }
14
+
15
+ .frame > :where(img, video, iframe, canvas, svg) {
16
+ inline-size: 100%;
17
+ block-size: 100%;
18
+ object-fit: cover;
19
+ }
@@ -0,0 +1,141 @@
1
+ /*
2
+ * Responsive equal-width grids.
3
+ *
4
+ * .grid is space-driven: it fits as many tracks as --grid-min allows.
5
+ * .grid-N is count-driven: it declares a structural density of N and collapses
6
+ * through natural divisors of N (see the subdivision block below).
7
+ *
8
+ * Public hooks:
9
+ * --grid-min
10
+ * --grid-columns
11
+ *
12
+ * Internal:
13
+ * --grid-count
14
+ *
15
+ * Child contract: each direct child is a grid item placed in the tracks the
16
+ * recipe generates.
17
+ */
18
+
19
+ /* --grid-columns replaces the intrinsic auto-fit template: once set, the
20
+ grid no longer collapses on its own — the author owns narrow-container
21
+ collapse. Prefer .sidebar-layout for content + aside layouts; reserve
22
+ --grid-columns for exact custom track templates. */
23
+
24
+ /* Recipe:
25
+ - collection whose item width is the contract -> .grid, tune --grid-min
26
+ - collection whose column density is the contract -> .grid-2/3/4/6
27
+ - balanced subdivision of that density -> wrap it in .container-query
28
+ - peer regions that switch as a group -> .switcher
29
+ - content + aside -> .sidebar-layout
30
+ - exact custom track templates -> --grid-columns with an explicit collapse
31
+ rule of your own */
32
+ .grid {
33
+ display: grid;
34
+ gap: var(--gap);
35
+ grid-template-columns: var(
36
+ --grid-columns,
37
+ repeat(auto-fit, minmax(min(100%, var(--grid-min, 16rem)), 1fr))
38
+ );
39
+ }
40
+
41
+ /* Structural density presets. The baseline is an auto-fill grid bounded at N
42
+ tracks: responsive and overflow-safe on its own, so a preset used without a
43
+ query container is never a trap — it simply reaches its balanced states less
44
+ precisely. auto-fill rather than auto-fit keeps item width stable when a
45
+ partial final page holds fewer items than the grid's capacity.
46
+
47
+ --grid-min is deliberately NOT read here. The subdivision below runs on
48
+ @container thresholds, which cannot resolve a custom property, so a hook
49
+ honored in the baseline would fall silent the moment a query container is
50
+ added. Item width is .grid's contract; column density is .grid-N's. */
51
+ :where(.grid-2, .grid-3, .grid-4, .grid-6) {
52
+ display: grid;
53
+ gap: var(--gap);
54
+ grid-template-columns: repeat(
55
+ auto-fill,
56
+ minmax(
57
+ min(
58
+ 100%,
59
+ max(16rem, calc((100% - (var(--grid-count) - 1) * var(--gap)) / var(--grid-count)))
60
+ ),
61
+ 1fr
62
+ )
63
+ );
64
+ }
65
+
66
+ .grid-2 {
67
+ --grid-count: 2;
68
+ }
69
+ .grid-3 {
70
+ --grid-count: 3;
71
+ }
72
+ .grid-4 {
73
+ --grid-count: 4;
74
+ }
75
+ .grid-6 {
76
+ --grid-count: 6;
77
+ }
78
+
79
+ /* Grid items keep an automatic minimum inline size (min-content), which can
80
+ overflow a track sized minmax(0, 1fr). Layout primitives remove that
81
+ intrinsic floor so their overflow-safe track sizing holds; authors only
82
+ need .min-inline-size-0 outside primitives that do not already remove this
83
+ floor. */
84
+ :where(.grid, .grid-2, .grid-3, .grid-4, .grid-6) > * {
85
+ min-inline-size: 0;
86
+ }
87
+
88
+ /* Balanced subdivision, opted into by an ancestor query container.
89
+ *
90
+ * .grid-2 2 -> 1
91
+ * .grid-3 3 -> 1
92
+ * .grid-4 4 -> 2 -> 1
93
+ * .grid-6 6 -> 3 -> 2 -> 1
94
+ *
95
+ * Every state is a divisor of N, so all items keep the same width and no
96
+ * state produces a visibly uneven split such as 5 + 1 or 4 + 2. Intermediate
97
+ * counts are skipped on purpose: do not "complete" these chains with the
98
+ * missing integers, and do not replace them with an unbounded auto-fit.
99
+ *
100
+ * The query container must be an ancestor. An element cannot query its own
101
+ * size without taking inline-size containment, which destroys its intrinsic
102
+ * inline contribution and collapses it inside .cluster, .topbar, floats, and
103
+ * any shrink-to-fit box. See docs/design-notes/grid.md.
104
+ *
105
+ * Three shared thresholds, not one calibrated per preset: 28rem opens the
106
+ * first horizontal density, 48rem opens three columns, 64rem opens the dense
107
+ * 4 and 6. N already carries the author's statement about item size, so
108
+ * tuning every step to land the same item width would erase that signal —
109
+ * .grid-6 at 64rem gives ~161px per tile, which is the point of choosing it.
110
+ * Probed with the content each preset targets: large cards for .grid-2 down
111
+ * to stats, avatars, and thumbnails for .grid-6.
112
+ *
113
+ * Ascending min-width only: the last matching step wins by cascade order.
114
+ */
115
+ @supports (container-type: inline-size) {
116
+ .container-query :where(.grid-2, .grid-3, .grid-4, .grid-6) {
117
+ grid-template-columns: 1fr;
118
+ }
119
+
120
+ @container (min-width: 28rem) {
121
+ .container-query :where(.grid-2, .grid-4, .grid-6) {
122
+ grid-template-columns: repeat(2, minmax(0, 1fr));
123
+ }
124
+ }
125
+
126
+ @container (min-width: 48rem) {
127
+ .container-query :where(.grid-3, .grid-6) {
128
+ grid-template-columns: repeat(3, minmax(0, 1fr));
129
+ }
130
+ }
131
+
132
+ @container (min-width: 64rem) {
133
+ .container-query :where(.grid-4) {
134
+ grid-template-columns: repeat(4, minmax(0, 1fr));
135
+ }
136
+
137
+ .container-query :where(.grid-6) {
138
+ grid-template-columns: repeat(6, minmax(0, 1fr));
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,42 @@
1
+ /*
2
+ * Layout — composition primitives. Readable cascade order: container-query
3
+ * first (a local opt-in, unrelated to the primitives), then the primitives.
4
+ *
5
+ * Selection:
6
+ * .stack vertical flow
7
+ * .cluster wrapping inline group
8
+ * .center centered content column
9
+ * .frame fixed-ratio media frame
10
+ * .app-shell full-height application shell
11
+ * .media fixed media + flexible content
12
+ * .switcher peer regions: all horizontal or all stacked
13
+ * .sidebar-layout main content + secondary aside
14
+ * .grid responsive collection; item width is the contract
15
+ * .grid-N structural density of N; wrap in .container-query for
16
+ * balanced subdivision (in grid.css)
17
+ * .column-layout explicit 12-column region composition; the author owns
18
+ * narrow-container recomposition
19
+ * .scroller theme-aware scrollbar density and colour
20
+ * .scroll-snap snap rails
21
+ * .scroll-target anchor scroll offset for sticky headers
22
+ * .topbar sticky app-shell top bar
23
+ * .measure readable content measure cap
24
+ *
25
+ * Prefer these compositions over viewport breakpoint utilities.
26
+ */
27
+ @import "./container-query.css";
28
+ @import "./stack.css";
29
+ @import "./cluster.css";
30
+ @import "./center.css";
31
+ @import "./frame.css";
32
+ @import "./app-shell.css";
33
+ @import "./media.css";
34
+ @import "./switcher.css";
35
+ @import "./sidebar-layout.css";
36
+ @import "./grid.css";
37
+ @import "./column-layout.css";
38
+ @import "./scroller.css";
39
+ @import "./scroll-snap.css";
40
+ @import "./scroll-target.css";
41
+ @import "./topbar.css";
42
+ @import "./measure.css";
@@ -0,0 +1,9 @@
1
+ /* Readable content measure. Composes with .center for centering or with
2
+ margin-inline: auto for a one-off centered block.
3
+ *
4
+ * Public hooks:
5
+ * --measure
6
+ */
7
+ .measure {
8
+ max-inline-size: var(--measure, 70ch);
9
+ }
@@ -0,0 +1,10 @@
1
+ /* Media object: fixed-width leading element (avatar, icon, image) + flexible
2
+ trailing content. Replaces the repeated "auto minmax(0, 1fr)" pattern in
3
+ author cards, comments, meta rows, and similar compositions.
4
+ Child contract: exactly two direct children (leading media + content). */
5
+ .media {
6
+ display: grid;
7
+ grid-template-columns: auto minmax(0, 1fr);
8
+ gap: var(--gap);
9
+ align-items: start;
10
+ }
@@ -0,0 +1,15 @@
1
+ /* Anchor scroll offset. A #hash link or scrollIntoView() places the target
2
+ * flush at the scroll container's start edge, which hides it behind a sticky
3
+ * header (.topbar) or underlaps it; scroll-margin reserves space on the
4
+ * target itself so the browser stops early.
5
+ *
6
+ * Apply to the elements that are link targets: <section id="...">, headings,
7
+ * etc. The scrollspy enhancer only reads ids — this offset is what actually
8
+ * shapes where the page lands, and the two are independent.
9
+ *
10
+ * Public hooks:
11
+ * --scroll-target-offset
12
+ */
13
+ .scroll-target {
14
+ scroll-margin-block-start: var(--scroll-target-offset, var(--space-60));
15
+ }
@@ -0,0 +1,41 @@
1
+ /*
2
+ * Scroller — optional scrollbar treatment for overflow regions.
3
+ *
4
+ * Apply to any scroll container (.overflow-auto, sidebars, long tables). Only
5
+ * the standard properties are used: the engine keeps drawing the scrollbar,
6
+ * this hands it the theme's density and colour. No ::-webkit-scrollbar chrome —
7
+ * rebuilding a thumb by hand means re-implementing the hover, the corner and
8
+ * the light/dark adaptation that color-scheme already gives for free, and the
9
+ * two never match anyway.
10
+ *
11
+ * Above the capability floor: scrollbar-color lands in Chromium 121 and
12
+ * Safari 18.2, the floor is Chromium 99 / Safari 15.4. Older engines ignore
13
+ * both declarations and draw their native scrollbar — the intended fallback,
14
+ * not a broken state.
15
+ *
16
+ * Styles the scrollbar only; it does not create the overflow. Pair it with
17
+ * .overflow-auto or a component that already scrolls.
18
+ *
19
+ * Public hooks:
20
+ * --scroller-track
21
+ * --scroller-thumb
22
+ */
23
+ .scroller {
24
+ --scroller-track: transparent;
25
+ --scroller-thumb: var(--border);
26
+
27
+ scrollbar-color: var(--scroller-thumb) var(--scroller-track);
28
+ scrollbar-width: thin;
29
+ }
30
+
31
+ /* Opt-in gutter: reserves space so the layout does not shift when
32
+ the scrollbar appears. Use alongside a known-overflow container. */
33
+ .scroller.stable-gutter {
34
+ scrollbar-gutter: stable;
35
+ }
36
+
37
+ @supports (color: color-mix(in oklch, red, white)) {
38
+ .scroller {
39
+ --scroller-thumb: color-mix(in oklch, var(--text-muted), transparent 55%);
40
+ }
41
+ }
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Main content + secondary region.
3
+ *
4
+ * The children wrap according to the space allocated to this layout, without
5
+ * an ancestor opt-in or viewport threshold. The large growth ratio lets the
6
+ * main region reclaim the full row when the aside wraps instead of remaining
7
+ * at its minimum viable width.
8
+ *
9
+ * Public hooks:
10
+ * --sidebar-content-min
11
+ * --sidebar-layout-size
12
+ *
13
+ * Child contract: the first and last direct children carry the sizing roles.
14
+ */
15
+ .sidebar-layout {
16
+ display: flex;
17
+ flex-wrap: wrap;
18
+ gap: var(--gap);
19
+ align-items: flex-start;
20
+ }
21
+
22
+ .sidebar-layout > :first-child {
23
+ flex-basis: 0;
24
+ flex-grow: 999;
25
+ min-inline-size: min(100%, var(--sidebar-content-min, 30rem));
26
+ }
27
+
28
+ .sidebar-layout > :last-child {
29
+ flex-basis: var(--sidebar-layout-size, 18rem);
30
+ flex-grow: 1;
31
+ }
@@ -0,0 +1,21 @@
1
+ /* Vertical flow. .stack owns the block axis only: block margins reset, inline
2
+ margins stay free (self-centering children via margin-inline: auto keep
3
+ working).
4
+ Child contract: direct children become flex items; margins reset, spacing
5
+ comes from --gap.
6
+
7
+ --gap is inherited, so a container that composes other primitives sets its
8
+ own rhythm on the `gap` property, not on the token: setting --gap on a
9
+ region re-spaces every nested stack, cluster and grid at any depth. Setting
10
+ the token stays correct on the element that consumes it, and .sm/.lg set it
11
+ deliberately so a density context reaches nested layouts. See
12
+ docs/pages/layout/stack.md. */
13
+ .stack {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: var(--gap);
17
+ }
18
+
19
+ .stack > * {
20
+ margin-block: 0;
21
+ }
@@ -0,0 +1,23 @@
1
+ /*
2
+ * Peer regions that switch as a group.
3
+ *
4
+ * All children sit side by side when they all fit; otherwise every child
5
+ * stacks. Unlike .grid, this recipe cannot create a partial row such as two
6
+ * items plus one orphan.
7
+ *
8
+ * Public hooks:
9
+ * --switcher-threshold
10
+ *
11
+ * Child contract: every direct child is a peer; the flex-basis applies to all.
12
+ */
13
+ .switcher {
14
+ display: flex;
15
+ flex-wrap: wrap;
16
+ gap: var(--gap);
17
+ }
18
+
19
+ .switcher > * {
20
+ flex-basis: calc((var(--switcher-threshold, 40rem) - 100%) * 999);
21
+ flex-grow: 1;
22
+ min-inline-size: 0;
23
+ }
@@ -1,23 +1,3 @@
1
- /*
2
- * Additional layout primitives not included in the core bundle.
3
- *
4
- * Import this file explicitly when a project needs these patterns.
5
- *
6
- * Public hooks:
7
- * --switcher-threshold
8
- */
9
- .switcher {
10
- display: flex;
11
- flex-wrap: wrap;
12
- gap: var(--gap);
13
- }
14
-
15
- .switcher > * {
16
- flex-basis: calc((var(--switcher-threshold, 40rem) - 100%) * 999);
17
- flex-grow: 1;
18
- min-inline-size: 0;
19
- }
20
-
21
1
  /*
22
2
  * Sticky, frosted top bar shell for app/dashboard shells (persistent
23
3
  * sidebar + topbar layouts). Owns positioning, stacking, and the frosted
@@ -0,0 +1,5 @@
1
+ /* Typography — prose scope and typographic treatments. */
2
+ @import "./prose.css";
3
+ @import "./lead.css";
4
+ @import "./overline.css";
5
+ @import "./fluid.css";
@@ -0,0 +1,7 @@
1
+ /* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
2
+ Non-fluid and always available — use .text-lead from the optional fluid
3
+ module for marketing/display surfaces. */
4
+ .lead {
5
+ font-size: 1.125em;
6
+ line-height: var(--line-height-relaxed);
7
+ }
@@ -42,7 +42,7 @@
42
42
  keeps a readable bordered pill everywhere else. */
43
43
  @supports (color: color-mix(in oklch, red, white)) {
44
44
  .overline.pill {
45
- border-color: color-mix(in oklch, var(--intent, var(--border)) 35%, var(--border));
46
- background: color-mix(in oklch, var(--intent, transparent) 6%, var(--surface));
45
+ border-color: color-mix(in oklab, var(--intent, var(--border)) 35%, var(--border));
46
+ background: color-mix(in oklab, var(--intent, transparent) 6%, var(--surface));
47
47
  }
48
48
  }
@@ -14,6 +14,7 @@
14
14
  * --prose-h2
15
15
  * --prose-h3
16
16
  * --prose-h4
17
+ * --link
17
18
  */
18
19
 
19
20
  .prose {
@@ -29,19 +30,23 @@
29
30
  --prose-h4: 1.125rem;
30
31
 
31
32
  max-inline-size: var(--prose-measure);
32
- color: var(--text);
33
33
  font-size: var(--prose-size);
34
34
  line-height: var(--prose-line-height);
35
35
  overflow-wrap: break-word;
36
36
  }
37
37
 
38
- .prose :where(p, ul, ol, dl, blockquote, pre, table, figure) {
39
- margin-block: var(--prose-flow) 0;
38
+ /* Vertical rhythm belongs to the direct block that participates in the
39
+ document, so it must not be injected into the internals of a nested
40
+ component (e.g. a .table-wrap around a .table would trap the margin). */
41
+ .prose > * + * {
42
+ margin-block-start: var(--prose-flow);
40
43
  }
41
44
 
45
+ /* Heading hierarchy remains low-specificity and contextual: a surface may
46
+ remap --heading without prose repainting it with the root text color. */
42
47
  .prose :where(h1, h2, h3, h4, h5, h6) {
43
48
  margin-block: 1.5em 0;
44
- color: var(--text);
49
+ color: var(--heading, var(--text));
45
50
  font-weight: var(--font-weight-strong);
46
51
  line-height: var(--prose-heading-line-height);
47
52
  }
@@ -54,48 +59,71 @@
54
59
  margin-block-start: 0;
55
60
  }
56
61
 
57
- .prose h1 {
62
+ /* Element recipes are wrapped in :where() so every .prose rule is 0-1-0.
63
+ An explicit component/util class can therefore override prose defaults
64
+ regardless of the underlying element — a .btn or .alert link must not be
65
+ reshaped just because it lives inside authored content. */
66
+ .prose :where(h1) {
58
67
  font-size: var(--prose-h1);
59
68
  }
60
69
 
61
- .prose h2 {
70
+ .prose :where(h2) {
62
71
  font-size: var(--prose-h2);
63
72
  }
64
73
 
65
- .prose h3 {
74
+ .prose :where(h3) {
66
75
  font-size: var(--prose-h3);
67
76
  }
68
77
 
69
- .prose h4 {
78
+ .prose :where(h4) {
70
79
  font-size: var(--prose-h4);
71
80
  }
72
81
 
73
- .prose a {
74
- color: var(--primary);
82
+ /* Link ink defaults to --primary. --link is an optional escape hatch: a
83
+ theme whose primary is too light or too vivid to read as inline text sets
84
+ it without having to restate the rule. */
85
+ .prose :where(a) {
86
+ color: var(--link, var(--primary));
75
87
  text-decoration-line: underline;
76
88
  text-decoration-thickness: 0.08em;
77
89
  text-underline-offset: 0.18em;
78
90
  }
79
91
 
80
- .prose a:hover {
92
+ .prose :where(a:hover) {
81
93
  text-decoration-thickness: 0.12em;
82
94
  }
83
95
 
84
96
  @supports (color: color-mix(in oklch, red, white)) {
85
- .prose a:hover {
86
- color: color-mix(in oklch, var(--primary), var(--text) 15%);
97
+ .prose :where(a:hover) {
98
+ color: color-mix(in oklab, var(--link, var(--primary)), var(--text) 15%);
87
99
  }
88
100
  }
89
101
 
102
+ /* Rich-text descendants are intentional here. Components and application UI
103
+ belong outside the prose boundary. */
90
104
  .prose :where(ul, ol) {
91
105
  padding-inline-start: 1.5em;
92
106
  }
93
107
 
94
- .prose li + li {
108
+ .prose :where(li + li) {
95
109
  margin-block-start: 0.35em;
96
110
  }
97
111
 
98
- .prose code {
112
+ /* Description lists keep document flow: a valid <dl> may pair several <dt>
113
+ with one <dd> or the reverse, so no grid layout is imposed here. */
114
+ .prose :where(dt) {
115
+ font-weight: var(--font-weight-strong);
116
+ }
117
+
118
+ .prose :where(dd) {
119
+ margin-inline-start: 1.5em;
120
+ }
121
+
122
+ .prose :where(dd + dt) {
123
+ margin-block-start: 0.75em;
124
+ }
125
+
126
+ .prose :where(code) {
99
127
  padding: 0.15em 0.3em;
100
128
  border-radius: var(--radius-sm);
101
129
  background: var(--surface-subtle);
@@ -104,7 +132,8 @@
104
132
  }
105
133
 
106
134
  /* Visual twin of .key (components/key.css) — keep the two recipes in sync. */
107
- .prose kbd {
135
+ /* @sync key-recipe */
136
+ .prose :where(kbd) {
108
137
  display: inline-flex;
109
138
  align-items: center;
110
139
  justify-content: center;
@@ -123,8 +152,9 @@
123
152
  white-space: nowrap;
124
153
  vertical-align: 0.08em;
125
154
  }
155
+ /* @sync key-recipe */
126
156
 
127
- .prose pre {
157
+ .prose :where(pre) {
128
158
  overflow-x: auto;
129
159
  padding: 1em;
130
160
  border: var(--border-width) solid var(--border);
@@ -132,53 +162,38 @@
132
162
  background: var(--surface-subtle);
133
163
  }
134
164
 
135
- .prose pre code {
165
+ .prose :where(pre) :where(code) {
136
166
  padding: 0;
137
167
  background: transparent;
138
168
  }
139
169
 
140
- .prose blockquote {
170
+ .prose :where(blockquote) {
141
171
  padding-inline-start: 1em;
142
172
  border-inline-start: var(--border-width) solid var(--border);
143
173
  color: var(--text-muted);
144
174
  }
145
175
 
146
- .prose figure {
176
+ .prose :where(figure) {
147
177
  margin-inline: 0;
148
178
  }
149
179
 
150
- .prose figcaption {
180
+ .prose :where(figcaption) {
151
181
  margin-block-start: 0.5em;
152
182
  color: var(--text-muted);
153
183
  font-size: 0.875em;
154
184
  }
155
185
 
156
- .prose table {
186
+ .prose :where(table) {
157
187
  inline-size: 100%;
158
188
  border-collapse: collapse;
159
189
  }
160
190
 
161
- .prose th,
162
- .prose td {
191
+ .prose :where(th, td) {
163
192
  padding: 0.5em;
164
193
  border-block-end: var(--border-width) solid var(--border);
165
194
  text-align: start;
166
195
  }
167
196
 
168
- /* Lead paragraph: a slightly larger, more relaxed reading-introduction text.
169
- Non-fluid and always available — use .text-lead from the optional fluid
170
- module for marketing/display surfaces. */
171
- .lead {
172
- font-size: 1.125em;
173
- line-height: var(--line-height-relaxed);
174
- }
175
-
176
- /* Readable content measure. Composes with .center for centering or with
177
- margin-inline: auto for a one-off centered block. */
178
- .measure {
179
- max-inline-size: var(--prose-measure, 70ch);
180
- }
181
-
182
197
  .prose.sm {
183
198
  --prose-size: 0.9375rem;
184
199
  --prose-h1: 1.75rem;
@@ -12,6 +12,8 @@
12
12
  margin: -1px;
13
13
  overflow: hidden;
14
14
  clip: rect(0, 0, 0, 0);
15
+ /* clip alone is legacy-only; clip-path is the modern hiding mechanism. */
16
+ clip-path: inset(50%);
15
17
  white-space: nowrap;
16
18
  border: 0;
17
19
  }
@@ -0,0 +1,3 @@
1
+ /* Utilities — small, single-purpose escape hatches. */
2
+ @import "./base.css";
3
+ @import "./extra.css";
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Per-document LIFO dismissal for transient UI that closes on Escape.
3
+ * Consumers register only while visible, so the topmost dismissable UI owns
4
+ * one key press and pinned or hidden UI never consumes it.
5
+ */
6
+
7
+ const dismissableStacks = new WeakMap();
8
+
9
+ export function registerEscapeDismissal(element, dismiss) {
10
+ const doc = element.ownerDocument;
11
+ let stack = dismissableStacks.get(doc);
12
+ if (!stack) {
13
+ stack = [];
14
+ dismissableStacks.set(doc, stack);
15
+ doc.addEventListener("keydown", onDocumentEscape);
16
+ }
17
+
18
+ const entry = { element, dismiss };
19
+ stack.push(entry);
20
+
21
+ return () => {
22
+ const index = stack.indexOf(entry);
23
+ if (index >= 0) stack.splice(index, 1);
24
+ };
25
+ }
26
+
27
+ function onDocumentEscape(event) {
28
+ if (event.key !== "Escape" || event.ctrlKey || event.altKey || event.shiftKey) return;
29
+
30
+ const stack = dismissableStacks.get(event.currentTarget);
31
+ const entry = stack.at(-1);
32
+ if (!entry) return;
33
+
34
+ event.preventDefault();
35
+ entry.dismiss({ restoreFocus: true });
36
+ }