actual-css 0.6.0 → 0.8.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 (143) hide show
  1. package/CHANGELOG.md +214 -1
  2. package/README.md +13 -11
  3. package/components.json +668 -0
  4. package/dist/actual.css +166 -37
  5. package/dist/actual.full.css +1005 -550
  6. package/dist/actual.full.js +1 -1
  7. package/dist/actual.full.min.css +1 -1
  8. package/dist/actual.js +1 -1
  9. package/dist/actual.min.css +1 -1
  10. package/docs/design-notes/accessibility-decisions.md +33 -0
  11. package/docs/design-notes/browser-support.md +82 -25
  12. package/docs/design-notes/column-layout.md +3 -3
  13. package/docs/design-notes/css-anchor.md +75 -21
  14. package/docs/design-notes/enhancement-contract.md +14 -6
  15. package/docs/design-notes/grid.md +4 -4
  16. package/docs/design-notes/naming.md +6 -1
  17. package/docs/design-notes/platform-alignment.md +99 -2
  18. package/docs/design-notes/popover-manual-poc.md +328 -0
  19. package/docs/design-notes/surface-reparenting.md +131 -0
  20. package/docs/design-notes/tooltip-coordinate-space.md +132 -0
  21. package/docs/design-notes/widget-primitives.md +9 -6
  22. package/docs/pages/components/accordion.md +41 -2
  23. package/docs/pages/components/alert.md +80 -30
  24. package/docs/pages/components/app-navigation.md +6 -0
  25. package/docs/pages/components/avatar.md +6 -6
  26. package/docs/pages/components/badge.md +101 -16
  27. package/docs/pages/components/button.md +16 -8
  28. package/docs/pages/components/card.md +10 -10
  29. package/docs/pages/components/dialog.md +16 -6
  30. package/docs/pages/components/fab.md +4 -3
  31. package/docs/pages/components/list.md +42 -4
  32. package/docs/pages/components/navbar.md +17 -1
  33. package/docs/pages/components/pagination.md +2 -2
  34. package/docs/pages/components/steps.md +19 -0
  35. package/docs/pages/components/table.md +3 -2
  36. package/docs/pages/enhancements/flyout.md +144 -24
  37. package/docs/pages/enhancements/overview.md +25 -0
  38. package/docs/pages/enhancements/status-bar.md +20 -0
  39. package/docs/pages/enhancements/tabs.md +45 -37
  40. package/docs/pages/enhancements/tooltip.md +40 -2
  41. package/docs/pages/examples/overview.md +38 -5
  42. package/docs/pages/forms/choice-cards.md +6 -2
  43. package/docs/pages/forms/combobox.md +1 -1
  44. package/docs/pages/forms/floating-field.md +4 -3
  45. package/docs/pages/forms/joined-controls.md +22 -0
  46. package/docs/pages/forms/otp.md +2 -1
  47. package/docs/pages/forms/overview.md +1 -1
  48. package/docs/pages/forms/states-and-sizes.md +16 -18
  49. package/docs/pages/forms/sticky-actions.md +2 -2
  50. package/docs/pages/forms/switch.md +1 -1
  51. package/docs/pages/foundations/philosophy.md +67 -22
  52. package/docs/pages/foundations/theming.md +10 -2
  53. package/docs/pages/foundations/tokens.md +118 -21
  54. package/docs/pages/foundations/typography.md +35 -14
  55. package/docs/pages/guides/introduction.md +11 -9
  56. package/docs/pages/guides/progressive-enhancement.md +136 -49
  57. package/docs/pages/guides/tailwind.md +3 -2
  58. package/docs/pages/layout/media.md +1 -1
  59. package/docs/pages/layout/overview.md +11 -1
  60. package/docs/pages/layout/scroll-snap.md +3 -3
  61. package/docs/pages/layout/stack.md +17 -9
  62. package/docs/pages/patterns/icon-input.md +4 -2
  63. package/docs/pages/patterns/icon-slot.md +2 -0
  64. package/docs/pages/patterns/intent-nesting.md +1 -1
  65. package/docs/pages/utilities/overview.md +23 -2
  66. package/docs/pages/utilities/sizing-wrapping.md +1 -1
  67. package/docs/pages/utilities/spacing.md +10 -8
  68. package/llms.txt +2 -1
  69. package/package.json +19 -8
  70. package/{scripts/reserved-classes.json → reserved-classes.json} +4 -6
  71. package/src/css/components/accordion.css +42 -3
  72. package/src/css/components/alert.css +25 -13
  73. package/src/css/components/app-nav.css +18 -8
  74. package/src/css/components/avatar.css +13 -1
  75. package/src/css/components/badge.css +78 -12
  76. package/src/css/components/breadcrumb.css +2 -3
  77. package/src/css/components/button.css +9 -16
  78. package/src/css/components/card.css +9 -2
  79. package/src/css/components/chat.css +1 -0
  80. package/src/css/components/data-list.css +1 -1
  81. package/src/css/components/drawer.css +79 -25
  82. package/src/css/components/fab.css +0 -6
  83. package/src/css/components/flyout.css +46 -63
  84. package/src/css/components/index.css +0 -1
  85. package/src/css/components/indicator.css +21 -0
  86. package/src/css/components/join.css +7 -0
  87. package/src/css/components/key.css +10 -0
  88. package/src/css/components/list.css +40 -4
  89. package/src/css/components/menu.css +18 -11
  90. package/src/css/components/modal.css +48 -33
  91. package/src/css/components/navbar.css +37 -4
  92. package/src/css/components/pagination.css +1 -0
  93. package/src/css/components/spinner.css +6 -6
  94. package/src/css/components/status-bar.css +0 -5
  95. package/src/css/components/steps.css +45 -59
  96. package/src/css/components/tab.css +29 -9
  97. package/src/css/components/table.css +5 -5
  98. package/src/css/components/tooltip.css +45 -13
  99. package/src/css/core/base.css +23 -1
  100. package/src/css/core/intents.css +17 -6
  101. package/src/css/core/theme.css +27 -0
  102. package/src/css/core/tokens.css +35 -15
  103. package/src/css/core/variants.css +64 -15
  104. package/src/css/forms/choice-card.css +4 -4
  105. package/src/css/forms/choice.css +6 -3
  106. package/src/css/forms/control.css +2 -1
  107. package/src/css/forms/floating-field.css +3 -3
  108. package/src/css/forms/form.css +4 -3
  109. package/src/css/forms/input-icon.css +20 -14
  110. package/src/css/forms/native.css +6 -1
  111. package/src/css/forms/otp.css +9 -1
  112. package/src/css/forms/range.css +2 -0
  113. package/src/css/forms/select.css +29 -9
  114. package/src/css/forms/switch.css +19 -5
  115. package/src/css/layout/app-layout.css +1 -1
  116. package/src/css/layout/grid.css +50 -76
  117. package/src/css/layout/scroll-snap.css +11 -7
  118. package/src/css/layout/scroller.css +3 -4
  119. package/src/css/layout/stack.css +2 -2
  120. package/src/css/typography/fluid.css +4 -3
  121. package/src/css/typography/prose.css +6 -5
  122. package/src/css/utilities/base.css +2 -1
  123. package/src/css/utilities/extra.css +20 -17
  124. package/src/js/context-menu.js +27 -26
  125. package/src/js/dialog.js +2 -1
  126. package/src/js/dismiss.js +5 -0
  127. package/src/js/enhance.js +34 -2
  128. package/src/js/enhancement-loader.js +36 -43
  129. package/src/js/events.js +0 -3
  130. package/src/js/filter.js +0 -1
  131. package/src/js/floating-space.js +42 -0
  132. package/src/js/flyout.js +9 -44
  133. package/src/js/focus-group.js +7 -0
  134. package/src/js/focus.js +6 -0
  135. package/src/js/menu.js +10 -1
  136. package/src/js/selectors.js +5 -9
  137. package/src/js/status.js +44 -7
  138. package/src/js/surface.js +106 -132
  139. package/src/js/tab.js +6 -1
  140. package/src/js/tooltip.js +152 -53
  141. package/src/js/validation.js +1 -1
  142. package/src/css/components/surface.css +0 -49
  143. package/src/js/floating.js +0 -432
@@ -76,6 +76,18 @@ Focus styling is outline-first. `--focus` names the theme's focus color, and a r
76
76
  }
77
77
  ```
78
78
 
79
+ ### Palette coherence
80
+
81
+ A coherent palette treats surfaces, text, borders, intents, and shadows as related roles rather than isolated colors.
82
+
83
+ Coherence does not mean that every token must be chromatic. Pure or near-pure whites and blacks remain legitimate where their role calls for them; what matters is that the surrounding neutral ramp and derived treatments belong to the same visual family.
84
+
85
+ Neutral roles may lean toward the palette's ambient hue — by default the primary's — while lightness and chroma stay curated per role. Chroma runs strongest through the ramp's mid-lightness rungs, often around `--border` and `--text-muted` depending on the color scheme, and tapers toward both ends. A palette whose accent is deliberately warmer or cooler than its paper picks an ambient hue of its own.
86
+
87
+ Semantic intents are curated colors, not mechanical derivatives of `--primary`. Danger, warning, and success stay recognizable for their function, but they are chosen as members of the theme palette rather than copied independently from a generic scale. Neither intents nor neutrals are derived from `--primary` at runtime: the relationship is a design decision recorded in the theme's own values.
88
+
89
+ Color should reinforce structure and meaning, not carry them alone. Information hierarchy and semantic states should remain understandable when hue is removed or unavailable. Desaturating a composition is a useful review heuristic, not a conformance test.
90
+
79
91
  ### Default theme philosophy
80
92
 
81
93
  The default palette is **Ink & Terra**: a near-neutral aubergine `primary`, terracotta `secondary`, and pigment-toned statuses (sage, ochre, brick) tuned to equal perceived weight. The neutral ramp (text, borders, subtle surfaces) is tinted toward the primary hue so the whole page quietly carries the identity. Optional themes can re-introduce vivid intent palettes on top of this foundation.
@@ -91,8 +103,62 @@ Soft surfaces (`.btn.soft`, `.badge.soft`, and the default `.alert`) are generat
91
103
  }
92
104
  ```
93
105
 
106
+ `--soft-border-mix` is the same lever for the rim. The `65%` default keeps a
107
+ visibly tinted border around every soft surface; pointing it at `--soft-bg-mix`
108
+ resolves the rim to the fill, and soft treatments become a tint carrying text
109
+ with no contour — the flatter language of a modern component set, from one
110
+ declaration:
111
+
112
+ ```css
113
+ [data-theme="flat"] {
114
+ --soft-border-mix: var(--soft-bg-mix);
115
+ }
116
+ ```
117
+
118
+ It reaches the default `.badge` and `.alert` plus every explicit `.soft`
119
+ (`.btn`, `.card`, `.chat`, `.navbar`, `.app-nav`), and nothing else: a component
120
+ on its own surface keeps its `--border` rim. The dial is also
121
+ rendering-only — the baseline path below `color-mix()` keeps `var(--border)`, so
122
+ borderless soft is a look, not a contract the Degraded tier upholds.
123
+
94
124
  `--soft-fg-mix` is the share of raw intent in soft *text*. At its `100%` default soft ink is the intent color itself, which is what a palette of dark, muted intents wants. A vivid or light palette cannot afford that: a soft badge then paints intent-tinted ink on an intent-tinted surface, and the two converge. Lowering the mix rebates the ink toward `--text`, which is the right direction in both schemes because `--text` is dark on a light theme and light on a dark one. Around `45%` a fully saturated palette recovers AA while the ink still reads as its intent.
95
125
 
126
+ Soft ink can also be corrected per role. The intent classes relay a per-role
127
+ hook, and the soft recipe reads it before falling back to the global mix:
128
+
129
+ - `--primary-soft-fg`, `--secondary-soft-fg`, `--success-soft-fg`,
130
+ `--warning-soft-fg`, `--danger-soft-fg`, `--neutral-soft-fg` — **theme hooks
131
+ per role**; the ones whose palette needs a softer ink than the others.
132
+ - `--intent-soft-fg` — the **current relay** (framework plumbing), set by each
133
+ intent class to its own `--*-soft-fg`.
134
+ - `--soft-fg-mix` — the **global fallback derivation**, unchanged.
135
+
136
+ **Soft foregrounds are calibrated against the darkest or closest interactive
137
+ surface they are expected to appear on.** For interactive soft treatments the
138
+ fill moves toward the intent on hover (`--soft-hover-alpha`), so the binding
139
+ surface is the *hovered* one and the resting fill simply carries more margin;
140
+ the default palette ships its own `--primary`/`--secondary`/… `-soft-fg` hooks
141
+ to keep that pair ≥ 4.5:1 in both color schemes, enforced by
142
+ `tests/browser/soft-recipe.test.js`. The hard line stops at the default theme:
143
+ presets and custom `[data-theme]` islands keep the `--soft-fg-mix` derivation
144
+ unless they declare the hooks themselves (the framework resets them outside
145
+ the default boundary). A theme that overrides the soft palette is responsible
146
+ for preserving sufficient text contrast; `bun run report:theme-contrast`
147
+ reports every preset's resting and hovered pairs without failing the build.
148
+
149
+ A theme whose primary should read more muted than its statuses sets only
150
+ `--primary-soft-fg`, and every soft `.primary` foreground follows it. Without a
151
+ `--*-soft-fg` the recipe resolves byte-identically to the old `--soft-fg-mix`
152
+ derivation. A custom intent class provides the relay directly:
153
+
154
+ ```css
155
+ .tertiary {
156
+ --intent: var(--tertiary);
157
+ --intent-fg: var(--tertiary-fg);
158
+ --intent-soft-fg: var(--tertiary-soft-fg);
159
+ }
160
+ ```
161
+
96
162
  **When an intent is tinted against a theme-controlled surface or border, the mix interpolates in `oklab`.** That covers the soft recipe, the checked `.choice-card` tint, and the `.overline.pill` border.
97
163
 
98
164
  Not because rectangular interpolation is more faithful to the intent hue — across the shipped presets it is a few degrees *less* faithful. A browser treats hue as powerless below a small chroma epsilon, so for the low-chroma surfaces and borders the presets actually use, a polar mix snaps cleanly onto the intent and wins.
@@ -147,8 +213,10 @@ Typography tokens cover the document baseline and common component needs.
147
213
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
148
214
 
149
215
  --line-height: 1.5;
150
- --font-weight: 400;
151
- --font-weight-strong: 650;
216
+ --font-weight-normal: 400;
217
+ --font-weight-medium: 500;
218
+ --font-weight-semibold: 600;
219
+ --font-weight-bold: 700;
152
220
 
153
221
  --font-size-xs: 0.8125rem;
154
222
  --font-size-sm: 0.875rem;
@@ -161,7 +229,9 @@ Components can use local font variables for their own mapping, but should inheri
161
229
 
162
230
  ### Controls
163
231
 
164
- Controls share sizing so buttons, inputs, selects, and compact app UI always align. `.sm`/`.lg` set shared density tokens for controls, compact labels, and spacing helpers.
232
+ Controls share geometry so buttons, inputs, selects, and compact app UI align.
233
+ Local `.sm`/`.lg` sizes map `--control-size`, `--control-font-size`, and
234
+ `--control-pad-x`.
165
235
 
166
236
  ```css
167
237
  :root {
@@ -169,41 +239,53 @@ Controls share sizing so buttons, inputs, selects, and compact app UI always ali
169
239
  --control-size-md: 2.375rem; /* 38px → default */
170
240
  --control-size-lg: 2.75rem; /* 44px → .lg */
171
241
  --control-size: var(--control-size-md);
172
- --control-pad-x: 1em;
242
+ --control-pad-x: var(--font-size-md);
173
243
  --control-font-size: var(--font-size-md);
174
244
  --disabled-opacity: 0.65;
175
245
  }
176
246
  ```
177
247
 
248
+ `--control-pad-x` is a resolved geometry length, stated in the typographic
249
+ tokens rather than an `em`. Every part of one control must see the same
250
+ horizontal distance; an `em` would change value on a descendant with its own
251
+ font size, which is exactly what `--input-icon-size` does to the icon glyph. It
252
+ mirrors `--control-font-size` and is rebound beside it at the `.sm`/`.lg`
253
+ boundaries.
254
+
178
255
  - `.lg` — 44px control height
179
256
  - default — 38px control height
180
257
  - `.sm` — 32px control height
181
258
 
182
- The font size stays at the baseline (`--control-font-size` = `--font-size-md`) in every density — density changes how much space controls consume, not how large their text is.
259
+ The default font size is `--font-size-md`; `.sm` maps it to `--font-size-sm` and
260
+ `.lg` to `--font-size-lg`. Density contexts change `--control-size` without
261
+ changing the font size.
183
262
 
184
263
  Disabled controls and disabled-prone components read `--disabled-opacity` for their dimmed state — buttons, tabs, menu items, badges, avatars, choice cards, native inputs, and range sliders.
185
264
 
186
265
  ### Density
187
266
 
188
- Density modifiers swap the shared control geometry:
267
+ Density contexts swap shared spacing and geometry:
189
268
 
190
269
  ```css
191
- .sm {
270
+ .compact {
192
271
  --gap: var(--space-20);
193
272
  --density-space: var(--space-20);
194
273
  --control-size: var(--control-size-sm);
195
- --density-compact-size: 1.25rem;
196
274
  }
197
275
 
198
- .lg {
276
+ .spacious {
199
277
  --gap: var(--space-50);
200
278
  --density-space: var(--space-50);
201
279
  --control-size: var(--control-size-lg);
202
- --density-compact-size: 1.875rem;
203
280
  }
204
281
  ```
205
282
 
206
- Density covers spacing and geometry only — it never touches typography or icon size. Compact labels such as `.badge` consume `--density-compact-size`; display elements with special geometry, such as avatar and spinner, keep their own size scales. Content blocks like alert, card, dialog, table, and accordion do not participate.
283
+ Density covers spacing and geometry only — it never touches typography or icon
284
+ size. Participation is opt-in and selective: controls consume `--control-size`
285
+ for geometry; spacing helpers and rhythms consume `--density-space` and `--gap`;
286
+ components whose size is intrinsic (badge, avatar, spinner, rating, key, prose,
287
+ inline choice/switch) keep their own scales. A local component size overrides
288
+ inherited density for the dimensions they share.
207
289
 
208
290
  ### Elevation
209
291
 
@@ -258,7 +340,7 @@ A single icon token can serve both `background-image` (native select) and `mask-
258
340
  A few cross-component helpers:
259
341
 
260
342
  - `--indicator-offset` / `--indicator-ring` — positioning and contrast ring for a status dot attached to `.avatar > .badge:empty`.
261
- - `--backdrop-color` / `--backdrop-opacity` / `--backdrop-fill` — scrim color and opacity shared by `dialog.modal::backdrop`, `dialog.drawer::backdrop`, and `.surface-backdrop`. Override `--backdrop-opacity` per theme for a denser or lighter scrim.
343
+ - `--backdrop-color` / `--backdrop-opacity` / `--backdrop-fill` — scrim color and opacity shared by `dialog.modal::backdrop` and `dialog.drawer::backdrop`. Override `--backdrop-opacity` per theme for a denser or lighter scrim.
262
344
 
263
345
  ```css
264
346
  :root {
@@ -274,18 +356,30 @@ Under `forced-colors: active`, `--backdrop-fill` maps to `Canvas`.
274
356
 
275
357
  ### Layering
276
358
 
277
- A small z-index scale orders non-dialog overlays. Native `<dialog>` elements (modal, drawer) render on the platform top-layer, which has no numeric z-index and always paints above the page — they never compete with this scale.
359
+ Two systems order overlays, and only one of them uses numbers.
360
+
361
+ **The platform top layer** holds native `<dialog>` (modal, drawer) and every element currently showing as a popover — which, since the runtime drives flyouts, context menus and tooltips with `popover="manual"`, is all of Actual's anchored overlays. Top-layer ordering does not use `z-index`. Such an element always paints above the document, and its order among the others is its top-layer promotion order — the most recently promoted on top. A `z-index` declaration on it still computes; it just does not decide where it lands.
362
+
363
+ **The document layer** is everything else, and the scale below is what orders it. Nothing in it can reach the other system: `z-index: 999999` on a document-layer element still paints under any open dialog or popover.
278
364
 
279
- | Token | Value | Used by |
280
- | ------------- | ----- | ------------------------------- |
281
- | `--z-sticky` | 10 | `form-actions.sticky`, `topbar` |
282
- | `--z-menu` | 20 | Floating menus (`flyout`) |
283
- | `--z-tooltip` | 50 | Tooltips |
284
- | `--z-status` | 60 | `status-bar` |
365
+ | Token | Value | Used by |
366
+ | ------------ | ----- | ------------------------------- |
367
+ | `--z-sticky` | 10 | `form-actions.sticky`, `topbar` |
368
+ | `--z-menu` | 20 | `fab`, static `.flyout` |
369
+ | `--z-status` | 60 | `status-bar` |
285
370
 
286
- `.surface-backdrop` sits just under the menus, at `calc(var(--z-menu) - 1)`.
371
+ `.flyout` keeps the `--z-menu` declaration for a panel the runtime does not
372
+ manage — one the platform has not promoted still stacks by number.
287
373
 
288
- Stacking-context traps: a flyout or tooltip that is a DOM descendant of a dialog, or of a container with `transform`, `filter`, `contain`, `isolation`, or `will-change`, is confined to that ancestor's stacking context. Its z-index token only orders it against siblings within that context. When a dialog's top-layer clips a descendant flyout, move the flyout markup outside the dialog or use a portal/popover to escape the stacking context.
374
+ `status-bar` is at the top of the document layer: above sticky bars and the FAB,
375
+ and below any open dialog, menu or tooltip. That is the boundary between the
376
+ two systems rather than a gap in the scale. Status messages are transient,
377
+ non-critical feedback and their real contract is the live-region announcement
378
+ — which is why `status.js` moves the singleton into an open modal dialog's
379
+ subtree, since a modal inerts the rest of the document and promotion would not
380
+ undo that.
381
+
382
+ Stacking-context traps: these apply to overlays painted in the page, not to promoted ones. An element that is a DOM descendant of a container with `transform`, `filter`, `contain`, `isolation`, or `will-change` is confined to that ancestor's stacking context, and its z-index token only orders it against siblings within that context. Promotion escapes all of it — but do not move the markup to chase the effect: a panel or tooltip used from inside a modal dialog must be authored *inside* that dialog, because the top layer does not lift an element out of a modal's inertness.
289
383
 
290
384
  Scrollbar gutter: modal open/close can shift page layout when the classic scrollbar disappears. When a measured scrollbar is present, `modal.css` applies `scrollbar-gutter: stable` to the viewport so the page doesn't jump.
291
385
 
@@ -342,6 +436,7 @@ Rules:
342
436
  - Prefix shared variant plumbing with `--ui-*`.
343
437
  - The shared relays `--ui-*`, `--intent*`, and `--density-*` are framework plumbing: they are classified once at framework level, not restated in every component file.
344
438
  - Keep component-specific variants in component code when they do not generalize.
439
+ - `flush` is a shared modifier: it removes the component's outer chrome so the component sits directly inside an existing surface (`.accordion.flush`). Reuse the term one component at a time when the same integration need appears elsewhere; `separated` stays component-local until a second real consumer exists.
345
440
  - Do not require users to override internal tokens for ordinary theming.
346
441
  - Promote an internal token to public only when there is a repeated, reasonable customization need.
347
442
  - A custom property used only in a fallback position (`var(--x, default)`) and never declared is an unset extension point and must carry a classification — public, framework plumbing, or internal. `check:css-api` fails on any unclassified fallback-only property; it does not force an artificial declaration to satisfy the audit.
@@ -352,6 +447,8 @@ Themes override tokens, not selectors. The themes in `src/css/themes/` are repos
352
447
 
353
448
  A minimal recolor theme overrides the intent pairs, surfaces, text colors, border, focus, and hover overlay. In browsers with `color-mix()` support, the core derives `--focus-ring` from the island's `--focus`; override the ring only for a deliberate visual treatment or when a matching pre-`color-mix()` fallback is required.
354
449
 
450
+ When overriding an intent color, review its paired `--*-fg` and `--*-soft-fg`. The generic soft fallback (`--soft-fg-mix` derivation, above) may already be sufficient.
451
+
355
452
  Shape, shadow, motion, typography, and soft-variant mix tokens are optional knobs. Override them only when the theme actually changes that part of the system.
356
453
 
357
454
  A full theme can override:
@@ -38,7 +38,7 @@ It applies everywhere — cards, dialogs, heroes, app shells — not only in pro
38
38
 
39
39
  It does not own visual hierarchy. No global `font-size` on headings, no global `font-weight` on headings, no global margins on headings, no global `text-decoration` on links. These choices would shape app UI in ways the framework cannot predict.
40
40
 
41
- The mildly opinionated defaults at the baseline are few: line-height on headings (tight, so multi-line headings stay readable), `text-wrap: balance` on headings where supported, the `hr` rule, the `::selection` rule, the `code` / `kbd` / `samp` / `pre` font family, `small` font size, and a global link underline offset that prose and components reuse. Everything else stays neutral so components and `.prose` can take over without fighting the baseline.
41
+ The mildly opinionated defaults at the baseline are few: line-height on headings (tight, so multi-line headings stay readable), `text-wrap: balance` on headings where supported, the `hr` rule, the `::selection` rule, the `code` / `kbd` / `samp` / `pre` font family, `pre` overflow containment, `small` font size, and a global link underline offset that prose and components reuse. Everything else stays neutral so components and `.prose` can take over without fighting the baseline.
42
42
 
43
43
  Global links inherit color. The visual affordance of a link is the responsibility of `.prose` (for authored content) or of a component (for nav links, button-like links, tabs, breadcrumbs, etc.). Underlining every `a` globally would collide with all of those.
44
44
 
@@ -54,10 +54,11 @@ The reason for that boundary: a dialog title, a card heading, or a sidebar secti
54
54
 
55
55
  ### Fluid module
56
56
 
57
- `src/css/typography/fluid.css` is a small module that is not imported by
58
- `actual.css`. Projects that need display, title, and lead sizes that scale
59
- with the viewport (landing pages, marketing pages, hero sections,
60
- documentation homepages) import it manually:
57
+ `src/css/typography/fluid.css` ships with the typography family
58
+ (`actual-css/css/typography`) and `actual-css/full`, but not with the minimal
59
+ core. A core-only entry point that needs display, title, and lead sizes that
60
+ scale with the viewport (landing pages, marketing pages, hero sections,
61
+ documentation homepages) imports it explicitly:
61
62
 
62
63
  ```css
63
64
  @import "actual-css/css/typography/fluid";
@@ -116,14 +117,14 @@ baseline stays sans so app UI inherits predictably.
116
117
 
117
118
  The framework exposes a small set of weights:
118
119
 
119
- - `--font-weight` (400) — body default
120
- - `--font-weight-medium` (500) — slightly heavier than body, for nav links
121
- - `--font-weight-strong` (650) — emphasis, prose headings, badges, tabs, table headers
122
- - `--font-weight-bold` (700) — the strongest weight exposed, used by the navbar brand and the accordion summary
120
+ - `--font-weight-normal` (400) — body default
121
+ - `--font-weight-medium` (500) — slightly heavier than body, for nav links and accordion summaries
122
+ - `--font-weight-semibold` (600) — emphasis, prose headings, badges, tabs, table headers
123
+ - `--font-weight-bold` (700) — the strongest weight exposed, used by the navbar brand
123
124
 
124
125
  No `--font-weight-light`. Light weights depend on the typeface, are rarely a good default, and add surface area without a recurring need.
125
126
 
126
- These weights are also the values used by components. Components do not use literal `font-weight: 600` or `font-weight: 750`; they reference the tokens. Theme authors can re-map them in one place.
127
+ These weights are also the values used by components. Components reference the tokens instead of literal weights, so theme authors can re-map the scale in one place.
127
128
 
128
129
  ## Line heights
129
130
 
@@ -159,7 +160,7 @@ Headings are split between the global baseline and `.prose`.
159
160
 
160
161
  Globally, headings get a tight line-height, a color from `--heading` (or inherited), and progressive `text-wrap: balance`. They do not get a `font-size`, a `font-weight`, or margins. The reason is that a card heading, a dialog title, or a sidebar section should not be auto-magically enlarged by selector.
161
162
 
162
- Inside `.prose`, headings get a `font-weight` from `--font-weight-strong`, a tighter line-height from `--prose-heading-line-height`, margin rhythm, and keep the global `text-wrap: balance`. Their color follows the contextual `--heading` alias, so prose also composes correctly with a contextual surface. The `h1` to `h4` font sizes are scoped to `.prose`, remain low-specificity, and are not exposed as a public scale.
163
+ Inside `.prose`, headings get a `font-weight` from `--font-weight-semibold`, a tighter line-height from `--prose-heading-line-height`, margin rhythm, and keep the global `text-wrap: balance`. Their color follows the contextual `--heading` alias, so prose also composes correctly with a contextual surface. The `h1` to `h4` font sizes are scoped to `.prose`, remain low-specificity, and are not exposed as a public scale.
163
164
 
164
165
  The `hgroup` element gets only structural styling: a grid layout, a gap, and a reset of internal margins. No font-size, no color, no weight. Its visual identity is the responsibility of the headings and paragraphs it contains.
165
166
 
@@ -175,7 +176,27 @@ Three logical alignment utilities exist (`.text-start`, `.text-center`, `.text-e
175
176
 
176
177
  `small` gets a smaller font size globally (`0.875em`) because it is a semantic element, not a utility. There is no `.small` class competing with the element.
177
178
 
178
- `code`, `kbd`, `samp`, and `pre` get the mono font family globally. Visual treatment (background, padding, radius) is left to `.prose` or to code-block components.
179
+ `code`, `kbd`, `samp`, and `pre` get the mono font family globally. `pre` also gets `overflow-x: auto`, because one unbreakable line widens the whole page and nothing in the markup warns you. That is containment, not decoration: background, padding, and radius are still left to `.prose` or to a code-block component.
180
+
181
+ For a standalone snippet outside authored content, `.prose` is the ready-made box — opt out of the reading measure so the code can use the full width:
182
+
183
+ ```html
184
+ <div class="prose" style="--prose-measure: none">
185
+ <pre><code>bun add actual-css</code></pre>
186
+ </div>
187
+ ```
188
+
189
+ `hr` resets the UA's `margin-inline: auto`. In document flow that changes nothing — auto inline margins on an auto-width block resolve to zero — but as a flex or grid item they replace the cross-axis stretch, which made an `<hr>` inside a `.stack` compute to zero width and disappear. `.stack` deliberately leaves the inline axis free (self-centering children keep working), so the reset belongs to the element.
190
+
191
+ Its block margin is tunable with `--hr-space`. The `--space-60` default suits a rule closing a page section; a rule separating two rows inside a panel wants far less.
192
+
193
+ ```css
194
+ .settings-panel hr {
195
+ --hr-space: var(--space-10);
196
+ }
197
+ ```
198
+
199
+ The hook applies to an `hr` in document flow. Inside `.stack` or `.prose` the parent owns the distance between siblings and overrides the element's own margin — express the rhythm there instead, with `--gap` or `--prose-flow`.
179
200
 
180
201
  ## Tables
181
202
 
@@ -198,14 +219,14 @@ To enable fluid display type on a marketing or documentation surface, import the
198
219
 
199
220
  The module does not change anything in the core: it adds three size tokens and three classes. Body text, app UI, and the default prose scale are not affected.
200
221
 
201
- Projects that do not need fluid display type do nothing — the module is not part of the default bundle.
222
+ Projects on `actual-css/css/typography` or `actual-css/full` already have the module. A minimal-core project that does not need fluid display type does nothing — the module is not in the core.
202
223
 
203
224
  ## What not to reintroduce
204
225
 
205
226
  These are decisions that the framework has already made. Re-introducing them silently would regress the system.
206
227
 
207
228
  - Do not add `font-size` on global `h1` to `h6`. Headings inherit size from context (component or `.prose`).
208
- - Do not add `font-weight` on global `h1` to `h6`. Strong weight is scoped to `.prose` and to components that need it.
229
+ - Do not add `font-weight` on global `h1` to `h6`. Heavier weights are scoped to `.prose` and to components that need them.
209
230
  - Do not add global `text-decoration: underline` on `a`. Links get their visual affordance from `.prose` or from their component.
210
231
  - Do not add `transition: all`. Transitions target specific properties.
211
232
  - Do not add a `--font-weight-light`. It is rarely a good default and depends on the typeface.
@@ -78,7 +78,9 @@ Intents are `.primary`, `.secondary`, `.success`, `.warning`, and `.danger`, and
78
78
  work across components. Variants such as `.solid`, `.soft`, `.outline`, and `.surface` are
79
79
  shared by buttons, badges, alerts, and cards; `.inverted` is a shared surface
80
80
  modifier that paints any block with the inverse surface. `.ghost` and `.link`
81
- are button-only. Size variants `.sm` and `.lg` scale controls consistently.
81
+ are button-only. Size variants `.sm` and `.lg` scale typography and
82
+ participating component geometry consistently. Density contexts `.compact` and
83
+ `.spacious` change geometry and spacing without changing typography or icons.
82
84
  Undocumented `is-*` classes are runtime internals.
83
85
 
84
86
  ```html
@@ -139,16 +141,16 @@ internal design documents for contributors.
139
141
 
140
142
  Actual CSS is built around progressive enhancement.
141
143
 
142
- | Tier | Firefox | Safari | Chromium |
143
- | ------------ | ------- | --------- | -------- |
144
- | Degraded | 78+ | 14+ | 88+ |
145
- | **Minimal** | **98+** | **15.4+** | **99+** |
146
- | Intermediate | 121+ | 16+ | 106+ |
147
- | Recommended | 129+ | 17.5+ | 123+ |
144
+ | Tier | Firefox | Safari | Chromium |
145
+ | ----------- | -------- | ------- | -------- |
146
+ | Degraded | 78+ | 14+ | 88+ |
147
+ | **Minimal** | **125+** | **17+** | **116+** |
148
+ | Recommended | 129+ | 17.5+ | 123+ |
148
149
 
149
150
  **Degraded** — semantic HTML and core CSS remain usable. JavaScript
150
151
  enhancements are outside the supported contract.
151
152
 
152
153
  **Minimal** — full Actual support, including the JavaScript runtime. The
153
- runtime assumes modern browser APIs and ships no legacy compatibility layers or
154
- polyfills.
154
+ runtime assumes modern browser APIs — including the native manual Popover
155
+ transport used by interactive surfaces — and ships no legacy compatibility
156
+ layers or polyfills.