actual-css 0.9.1 → 0.10.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 (107) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/README.md +67 -45
  3. package/components.json +33 -13
  4. package/dist/actual.full.css +607 -502
  5. package/dist/actual.full.js +1 -1
  6. package/dist/actual.full.min.css +1 -1
  7. package/docs/design-notes/app-ui.md +1 -1
  8. package/docs/design-notes/column-layout.md +103 -520
  9. package/docs/design-notes/forced-colors.md +12 -1
  10. package/docs/design-notes/naming.md +1 -1
  11. package/docs/design-notes/surface-reparenting.md +8 -9
  12. package/docs/navigation.json +2 -0
  13. package/docs/pages/components/accordion.md +55 -1
  14. package/docs/pages/components/alert.md +9 -15
  15. package/docs/pages/components/badge.md +6 -7
  16. package/docs/pages/components/bleed.md +42 -0
  17. package/docs/pages/components/button.md +30 -4
  18. package/docs/pages/components/card.md +21 -86
  19. package/docs/pages/components/close.md +48 -0
  20. package/docs/pages/components/data-list.md +26 -1
  21. package/docs/pages/components/dialog.md +16 -16
  22. package/docs/pages/components/drawer.md +9 -9
  23. package/docs/pages/components/navbar.md +5 -5
  24. package/docs/pages/components/spinner.md +3 -3
  25. package/docs/pages/components/table.md +3 -2
  26. package/docs/pages/enhancements/overview.md +5 -0
  27. package/docs/pages/enhancements/tooltip.md +3 -1
  28. package/docs/pages/examples/overview.md +3 -1
  29. package/docs/pages/forms/choice-cards.md +39 -0
  30. package/docs/pages/forms/combobox.md +23 -11
  31. package/docs/pages/forms/input-policy.md +59 -0
  32. package/docs/pages/forms/joined-controls.md +2 -5
  33. package/docs/pages/forms/overview.md +6 -0
  34. package/docs/pages/forms/validation.md +37 -1
  35. package/docs/pages/foundations/theming.md +64 -18
  36. package/docs/pages/foundations/tokens.md +50 -13
  37. package/docs/pages/foundations/typography.md +10 -0
  38. package/docs/pages/guides/introduction.md +17 -24
  39. package/docs/pages/guides/modular-import.md +53 -6
  40. package/docs/pages/guides/progressive-enhancement.md +9 -5
  41. package/docs/pages/guides/tailwind.md +17 -5
  42. package/docs/pages/layout/column-layout.md +92 -54
  43. package/docs/pages/layout/media.md +1 -1
  44. package/docs/pages/layout/overview.md +1 -1
  45. package/docs/pages/utilities/overview.md +1 -1
  46. package/docs/pages/utilities/sizing-wrapping.md +5 -3
  47. package/llms.txt +24 -4
  48. package/package.json +7 -6
  49. package/reserved-classes.json +5 -30
  50. package/skills/actual-css/PROJECT.md.example +65 -0
  51. package/skills/actual-css/README.md +64 -0
  52. package/skills/actual-css/SKILL.md +234 -0
  53. package/skills/actual-css/references/accessibility.md +44 -0
  54. package/skills/actual-css/references/api-and-vocabulary.md +94 -0
  55. package/skills/actual-css/references/enhancements.md +59 -0
  56. package/skills/actual-css/references/forms.md +49 -0
  57. package/skills/actual-css/references/layout-and-theming.md +82 -0
  58. package/skills/actual-css/references/review-checklist.md +51 -0
  59. package/skills/actual-css/scripts/inspect-actual-css.mjs +171 -0
  60. package/src/css/components/accordion.css +44 -7
  61. package/src/css/components/alert.css +23 -43
  62. package/src/css/components/app-nav.css +9 -0
  63. package/src/css/components/badge.css +11 -39
  64. package/src/css/components/bleed.css +79 -0
  65. package/src/css/components/button.css +6 -17
  66. package/src/css/components/card.css +7 -40
  67. package/src/css/components/close.css +91 -0
  68. package/src/css/components/data-list.css +18 -0
  69. package/src/css/components/drawer.css +15 -56
  70. package/src/css/components/fab.css +10 -0
  71. package/src/css/components/flyout.css +1 -0
  72. package/src/css/components/index.css +2 -0
  73. package/src/css/components/join.css +36 -20
  74. package/src/css/components/menu.css +12 -0
  75. package/src/css/components/modal.css +10 -57
  76. package/src/css/components/navbar.css +27 -0
  77. package/src/css/core/base.css +2 -0
  78. package/src/css/core/focus.css +9 -3
  79. package/src/css/core/theme.css +25 -43
  80. package/src/css/core/tokens.css +3 -1
  81. package/src/css/core/variants.css +15 -36
  82. package/src/css/forms/choice-card.css +74 -4
  83. package/src/css/forms/choice.css +2 -3
  84. package/src/css/forms/control.css +15 -8
  85. package/src/css/forms/custom-select.css +4 -4
  86. package/src/css/forms/form.css +6 -0
  87. package/src/css/forms/native.css +4 -4
  88. package/src/css/forms/range.css +23 -8
  89. package/src/css/forms/switch.css +2 -3
  90. package/src/css/layout/app-layout.css +1 -1
  91. package/src/css/layout/column-layout.css +26 -111
  92. package/src/css/utilities/extra.css +7 -6
  93. package/src/js/dialog.js +95 -24
  94. package/src/js/enhance.js +7 -3
  95. package/src/js/escape.js +1 -0
  96. package/src/js/flyout.js +10 -1
  97. package/src/js/focus-group.js +2 -1
  98. package/src/js/keys.js +14 -0
  99. package/src/js/menu.js +2 -1
  100. package/src/js/status.js +19 -0
  101. package/src/js/surface.js +12 -8
  102. package/src/js/tab.js +2 -1
  103. package/src/js/tooltip.js +18 -1
  104. package/src/js/validation.js +36 -6
  105. package/dist/actual.css +0 -863
  106. package/dist/actual.js +0 -1
  107. package/dist/actual.min.css +0 -1
@@ -17,6 +17,24 @@ Component custom properties fall into three header categories in the source: **P
17
17
 
18
18
  As a convention, component-prefixed properties owned by the component's base rule are usually author hooks. Properties owned by states/variants, derived from other hooks, or written by JavaScript are not. This convention is indicative, not algorithmic: runtime-written, derived, and state-relay properties stay internal even when their form resembles a hook.
19
19
 
20
+ ### Where a token is declared
21
+
22
+ A hook is only defined inside the scope that declares it. Reading one outside that scope is not an error a browser reports: `var()` resolves to nothing, the whole declaration becomes invalid at computed-value time, and the property falls back to its initial value — `padding: var(--card-pad)` outside a card computes to `0`, silently.
23
+
24
+ | Scope | Declared on | Examples |
25
+ | --------- | --------------------------- | -------------------------------------- |
26
+ | Global | `:root`, every theme island | `--space-40`, `--radius`, `--primary` |
27
+ | Component | the component's base rule | `--card-pad`, `--drawer-size` |
28
+ | Relay | a variant, state, or script | `--ui-bg`, `--intent`, `--surface-pad` |
29
+
30
+ Global tokens are the ones to reach for in application CSS. Write a relay only to override what a variant sets on one element (Button · Variants); read one only where its owner documents it (`--bleed-pad`). When you do want a component token outside its component — matching a bare block to the card rhythm, say — give `var()` the default explicitly, so the declaration survives where the hook is absent:
31
+
32
+ ```css
33
+ .panel {
34
+ padding: var(--card-pad, var(--space-40));
35
+ }
36
+ ```
37
+
20
38
  ### Color
21
39
 
22
40
  Intent tokens are used by `.primary`, `.secondary`, `.success`, `.warning`, `.danger`, and `.neutral`. They are curated color/foreground pairs. Actual CSS does not compute foregrounds automatically in the core theme contract.
@@ -62,17 +80,16 @@ The surface levels are: `surface` (canvas), `surface-raised` (cards, raised surf
62
80
 
63
81
  Use `*-fg` pairs only for solid backgrounds where the component controls both foreground and background. Do not require a foreground token for every surface token.
64
82
 
65
- Focus styling is outline-first. `--focus` names the theme's focus color, and a ring is derived from it with `color-mix()`. A global focus baseline applies `--focus-outline` and `--focus-outline-offset`: older browsers show it on `:focus`, while modern browsers limit it to `:focus-visible`. Components add `--focus-ring-shadow` only where a softer halo helps; `--focus-ring-width` sets its thickness. Under `forced-colors: active`, the outline maps to `Highlight` and the shadow is disabled.
83
+ Focus styling is outline-first. A global focus baseline applies `--focus-outline` and `--focus-outline-offset`: older browsers show it on `:focus`, while modern browsers limit it to `:focus-visible`. Components draw a solid `--focus-ring-width` line in `--focus`: inside the border box on text fields (`.input`, `.textarea`, `.select`), outside at `--focus-outline-offset` on actions (`.btn`, `.close`, choices, switches). The color never follows an intent or a local surface, so `--focus` must hold 3:1 against both `--surface` and `--surface-solid` — the page, fields and a solid band all rely on it. Under `forced-colors: active`, outlines map to the system focus color.
66
84
 
67
85
  ```css
68
86
  :root {
69
- --focus: var(--neutral);
87
+ --focus: hsl(268 30% 55%);
70
88
 
71
89
  --focus-outline-color: currentColor;
72
90
  --focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color);
73
91
  --focus-outline-offset: 2px;
74
- --focus-ring-width: 3px;
75
- --focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
92
+ --focus-ring-width: 2px;
76
93
  }
77
94
  ```
78
95
 
@@ -211,6 +228,8 @@ Typography tokens cover the document baseline and common component needs.
211
228
  :root {
212
229
  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
213
230
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
231
+ --font-width: 100%;
232
+ --font-width-dense: 95%;
214
233
 
215
234
  --line-height: 1.5;
216
235
  --font-weight-normal: 400;
@@ -264,24 +283,41 @@ Disabled controls and disabled-prone components read `--disabled-opacity` for th
264
283
 
265
284
  ### Density
266
285
 
267
- Density contexts swap shared spacing and geometry:
286
+ Density contexts swap shared spacing and geometry, plus a slight typographic
287
+ width bonus:
268
288
 
269
289
  ```css
290
+ :root {
291
+ --font-width: 100%;
292
+ --font-width-dense: 95%;
293
+ }
294
+
270
295
  .compact {
271
296
  --gap: var(--space-20);
272
297
  --density-space: var(--space-20);
273
298
  --control-size: var(--control-size-sm);
299
+ font-stretch: var(--font-width-dense);
274
300
  }
275
301
 
276
302
  .spacious {
277
303
  --gap: var(--space-50);
278
304
  --density-space: var(--space-50);
279
305
  --control-size: var(--control-size-lg);
306
+ font-stretch: var(--font-width);
280
307
  }
281
308
  ```
282
309
 
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`
310
+ Density never changes font size or icon size — it may only use the denser font
311
+ width. `font-stretch` uses a variable `wdth` axis when available, or
312
+ participates in normal width-face matching for families that expose multiple
313
+ widths. Layout must never depend on the narrowing being available. Compact
314
+ density may slightly narrow variable typefaces when they support a width axis.
315
+ This is a density enhancement, not an overflow strategy: never condense a
316
+ component just because its label overflows at some viewport — that hides a
317
+ composition problem. A theme whose typeface has no useful width axis opts out
318
+ with `--font-width-dense: 100%`.
319
+
320
+ Participation is opt-in and selective: controls consume `--control-size`
285
321
  for geometry; spacing helpers and rhythms consume `--density-space` and `--gap`;
286
322
  components whose size is intrinsic (badge, avatar, spinner, rating, key, prose,
287
323
  inline choice/switch) keep their own scales. A local component size overrides
@@ -426,9 +462,7 @@ Text controls (`.input`/`.textarea`/`.select`) expose the same lower-edge extens
426
462
 
427
463
  Alerts expose the same idea on the leading edge instead of the lower one: `--alert-border-inline-start-color` and `--alert-border-inline-start-width` are unset by default and fall back to the regular `--ui-border`, so a theme can turn an alert into a callout with a colored flag on the inline-start side, matching the classic notice pattern, without a per-variant override.
428
464
 
429
- A theme is not limited to the tokens above; it can restate any component-local token under a state selector to change the interaction recipe itself, not just the palette. The `edge` theme does this for focus: the base input recipe (forms/control.css) flips `--control-border` to `--focus` on top of the shared outline + ring, which would stack with a lower-edge accent. Edge instead restores `--control-border` under `:focus-visible` and thickens the accent edge so the edge, not a second ring, carries the focus.
430
-
431
- Buttons don't consume `--focus-outline` — button.css computes its own ring inline so the color can follow `--btn-focus-color` (intent-aware, unlike the plain `--focus`). It reuses the shared `--focus-ring-width` for the ring thickness and derives the color through `--btn-focus-ring-color` (a `color-mix()` of `--btn-focus-color` when supported, falling back to the shared `--focus-ring`).
465
+ A theme is not limited to the tokens above; it can restate any component-local token or rule under a state selector to change the interaction recipe itself, not just the palette. The `gradient` theme does this for field focus: it restores a `--focus` border plus an outer translucent halo in place of the core inset outline.
432
466
 
433
467
  Rules:
434
468
 
@@ -445,9 +479,11 @@ Rules:
445
479
 
446
480
  Themes override tokens, not selectors. The themes in `src/css/themes/` are repository-only demo examples, not included in the default stylesheet or the npm package; they exist to show valid ways to use this contract, such as `ocean`, `square`, `cyberpunk`, and `brutalist`.
447
481
 
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.
482
+ A minimal recolor theme overrides the intent pairs, surfaces, text colors, border, focus, hover overlay, and shadow color. Pick `--focus` as a mid-tone that holds 3:1 against both `--surface` and `--surface-solid` in every scheme the theme declares; a pastel dark-scheme primary usually fails on the light solid surface. `bun run report:theme-contrast` measures both pairs.
483
+
484
+ When overriding an intent color, review its paired `--*-fg` and `--*-soft-fg`. A theme that declares no hook falls back to the `--soft-fg-mix` derivation: one global percentage applied to every role, so it carries no contrast guarantee for a palette it was not tuned against. `bun run report:theme-contrast` prints the resting and hovered soft pair for every intent of every island, which is how a theme finds the roles that need a hook or a lower `--soft-fg-mix`.
449
485
 
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.
486
+ The default palette's hooks apply to `:root` and to the `light`/`dark` boundaries only. Any other `data-theme` value resets them to the derivation, whether the attribute sits on `<html>` or on a nested island.
451
487
 
452
488
  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.
453
489
 
@@ -488,6 +524,7 @@ A minimal recolor theme (illustrative, not a shipped theme):
488
524
  --border: hsl(260 24% 84%);
489
525
  --focus: var(--primary);
490
526
  --hover-overlay: hsl(260 30% 10% / 0.04);
527
+ --shadow-color: hsl(260 30% 10%);
491
528
  --soft-bg-mix: 84%;
492
529
  --soft-border-mix: 58%;
493
530
 
@@ -497,7 +534,7 @@ A minimal recolor theme (illustrative, not a shipped theme):
497
534
  }
498
535
  ```
499
536
 
500
- Several tokens are theme-derived aliases that reference other tokens — `--state-selected`/`--state-selected-fg`/`--state-disabled`, `--indicator-ring`, `--shadow`/`--shadow-popout`, `--heading`, `--selection-bg`/`--selection-fg`, and `--focus-ring-shadow`. They are declared on `:root, [data-theme]` so they recompute on every theme boundary: a custom property resolves its `var()` references at computed-value time on the element that declares it, so an alias declared only on `:root` would be inherited as an already-resolved value and would not follow a `[data-theme]` island's overridden tokens. A theme that wants a distinct alias (e.g. a `--selection-bg` of its own) overrides it explicitly afterwards, which wins by cascade order.
537
+ Several tokens are theme-derived aliases that reference other tokens — `--state-selected`/`--state-selected-fg`/`--state-disabled`, `--indicator-ring`, `--shadow`/`--shadow-popout`, `--heading`, and `--selection-bg`/`--selection-fg`. They are declared on `:root, [data-theme]` so they recompute on every theme boundary: a custom property resolves its `var()` references at computed-value time on the element that declares it, so an alias declared only on `:root` would be inherited as an already-resolved value and would not follow a `[data-theme]` island's overridden tokens. A theme that wants a distinct alias (e.g. a `--selection-bg` of its own) overrides it explicitly afterwards, which wins by cascade order.
501
538
 
502
539
  Without `data-theme`, the default theme advertises `color-scheme: light dark` and follows the user's OS preference in browsers that support `light-dark()`. Dark themes should set `color-scheme: dark`. Light themes should set `color-scheme: light`. Browsers without `light-dark()` receive the light fallback.
503
540
 
@@ -113,6 +113,16 @@ the document) and `--font-mono` (for `code` / `kbd` / `samp` / `pre`). There is
113
113
  theme-level token in your own theme when a project actually needs one; the
114
114
  baseline stays sans so app UI inherits predictably.
115
115
 
116
+ ### Font width
117
+
118
+ Two public hooks carry the typographic side of density: `--font-width` (normal
119
+ width) and `--font-width-dense` (compact width). The document reads
120
+ `--font-width` through `font-stretch`; `.compact` applies
121
+ `--font-width-dense` and `.spacious` restores `--font-width`. A theme whose
122
+ typeface has no useful width axis sets `--font-width-dense: 100%`. Width is a
123
+ density token, not a responsive escape hatch — never condense text to fix an
124
+ overflowing label.
125
+
116
126
  ## Font weights
117
127
 
118
128
  The framework exposes a small set of weights:
@@ -30,37 +30,29 @@ ARIA and native HTML, and per-widget configuration uses self-describing
30
30
  npm install actual-css
31
31
  ```
32
32
 
33
- Import the full framework in your CSS:
33
+ Import what you use. The simplest start is the package's full entry, copied
34
+ into your project and pruned — see
35
+ [Start from the full entry](modular-import.md#start-from-the-full-entry):
34
36
 
35
37
  ```css
36
- @import "actual-css/full";
37
- ```
38
-
39
- `actual-css/full` ships every functional family. The bare `actual-css` entrypoint
40
- is the minimal core (reset, tokens, theme, base, intents, variants, focus,
41
- print).
42
-
43
- Or compose only the pieces you use:
44
-
45
- ```css
46
- @import "actual-css/css";
38
+ @import "actual-css/css"; /* core */
47
39
  @import "actual-css/css/layout";
40
+ @import "actual-css/css/forms/all";
48
41
  @import "actual-css/css/components/button";
49
42
  @import "actual-css/css/components/card";
50
- @import "actual-css/css/forms";
51
- @import "actual-css/css/components/flyout";
52
43
  @import "actual-css/css/utilities";
53
44
  ```
54
45
 
55
- `actual-css/css` is the minimal core. Family manifests (`css/layout`,
56
- `css/forms`, `css/components`, `css/typography`, `css/effects`,
57
- `css/utilities`) and their modules map one-to-one to `src/css/`. See the
58
- [modular import guide](modular-import.md) for the full map.
46
+ `actual-css/css` is the core (reset, tokens, theme, base, intents, variants,
47
+ focus, print). Each family and module is its own import; the
48
+ [modular import guide](modular-import.md) has the full map.
59
49
 
60
- You can also use the compiled full bundle directly:
50
+ For zero-config use — prototypes, CDN, or when bundle size is not worth
51
+ optimizing — the bare `actual-css` entrypoint and `actual-css/full` both resolve
52
+ to the complete compiled framework:
61
53
 
62
54
  ```html
63
- <link rel="stylesheet" href="actual.full.min.css">
55
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/actual-css@0.10/dist/actual.full.min.css">
64
56
  ```
65
57
 
66
58
  The package does not maintain separate partial bundles. Modular entrypoints map
@@ -76,11 +68,12 @@ Actual CSS uses a small unprefixed class grammar:
76
68
 
77
69
  Intents are `.primary`, `.secondary`, `.success`, `.warning`, and `.danger`, and
78
70
  work across components. Variants such as `.solid`, `.soft`, `.outline`, and `.surface` are
79
- shared by buttons, badges, alerts, and cards; `.inverted` is a shared surface
80
- modifier that paints any block with the inverse surface. `.ghost` and `.link`
81
- are button-only. Size variants `.sm` and `.lg` scale typography and
71
+ shared by buttons, badges, alerts, and cards; a subtree that needs another
72
+ palette is a `data-theme` island, not a class. `.ghost` and `.link` are
73
+ button-only. Size variants `.sm` and `.lg` scale typography and
82
74
  participating component geometry consistently. Density contexts `.compact` and
83
- `.spacious` change geometry and spacing without changing typography or icons.
75
+ `.spacious` change geometry and spacing without changing font size or icons;
76
+ `.compact` may slightly narrow typefaces that support a width axis.
84
77
  Undocumented `is-*` classes are runtime internals.
85
78
 
86
79
  ```html
@@ -1,19 +1,26 @@
1
1
  # Modular imports
2
2
 
3
- Actual CSS ships one minimal core (`actual-css`) and one all-in entrypoint (`actual-css/full`). Projects can also compose the framework from family manifests or individual modules.
3
+ Actual CSS has two kinds of entrypoint. Without `/css`, you get a compiled artifact ready to serve. With `/css`, you compose the sources.
4
4
 
5
5
  ## Entrypoints
6
6
 
7
- ```css
8
- @import "actual-css"; /* minimal core */
9
- @import "actual-css/full"; /* complete framework */
7
+ ```text
8
+ Composable source CSS (recommended)
9
+ actual-css/css core
10
+ actual-css/css/<family> one family, e.g. css/layout, css/forms/all
11
+ actual-css/css/<family>/* one module, e.g. css/components/button
12
+ actual-css/css/full every family, as source
13
+
14
+ Zero-config distribution
15
+ actual-css complete compiled framework
16
+ actual-css/full the same bundle, named explicitly
10
17
  ```
11
18
 
12
- `actual-css` contains the shared baseline: reset, tokens, theme, document defaults, intents, universal variants, focus, and print.
19
+ `actual-css/css` contains the shared baseline: reset, tokens, theme, document defaults, intents, universal variants, focus, and print.
13
20
 
14
21
  It contains no typography, layout, forms, components, effects, or utilities.
15
22
 
16
- `actual-css/full` starts from the core and adds every family in cascade order:
23
+ The full framework starts from the core and adds every family in cascade order:
17
24
 
18
25
  ```text
19
26
  core → typography → layout → forms → components → effects → utilities
@@ -27,6 +34,46 @@ Components may enhance or replace that treatment in interactive states, but thei
27
34
 
28
35
  When an interactive state replaces the outline, it must preserve a visible focus indicator in forced-colors, either with an outline-based fallback or by keeping the replacement out of forced-colors.
29
36
 
37
+ ## Start from the full entry
38
+
39
+ Deleting is easier than adding, and it keeps the cascade order right. Copy the
40
+ package's full entry into your project, rewrite its relative imports to
41
+ package paths, then delete what you do not need:
42
+
43
+ - CSS: `src/css/actual.full.css`. Prefix each import with `actual-css/css/`
44
+ and drop `.css` and `/index`; `./actual.css` is `actual-css/css`. To go below
45
+ family level, replace a family line with the imports of its `index.css`,
46
+ rewritten the same way.
47
+ - JS: `src/js/full.js`. Prefix each import with `actual-css/js/` and drop
48
+ `.js`; `./index.js` is `actual-css/js`.
49
+
50
+ ```css
51
+ /* app/index.css — actual.full.css, rewritten, components expanded and pruned */
52
+ @import "actual-css/css";
53
+ @import "actual-css/css/typography";
54
+ @import "actual-css/css/layout";
55
+ @import "actual-css/css/forms/all";
56
+ @import "actual-css/css/components/button";
57
+ @import "actual-css/css/components/close";
58
+ @import "actual-css/css/components/card";
59
+ @import "actual-css/css/components/modal";
60
+ @import "actual-css/css/utilities";
61
+ ```
62
+
63
+ ```js
64
+ // app/index.js — full.js, rewritten and pruned
65
+ import "actual-css/js/flyout";
66
+ import "actual-css/js/dialog";
67
+ import "actual-css/js/dismiss";
68
+ import "actual-css/js/tooltip";
69
+ import "actual-css/js";
70
+ ```
71
+
72
+ JS modules import what they depend on. CSS modules do not, and three are
73
+ shared: keep `components/close` with modal, drawer, alert, or a removable
74
+ badge; `components/bleed` with a `.bleed` child of card, drawer, or accordion;
75
+ `components/spinner` with `components/busy`.
76
+
30
77
  ## Family manifests
31
78
 
32
79
  Each family is one import. The contents are enumerations rather than table
@@ -49,7 +49,10 @@ never tracked.
49
49
 
50
50
  `enhance()` observes DOM insertions and removals, not attribute changes. If an
51
51
  already-connected element receives a behavior attribute later, call the returned
52
- `refresh(node)` handle. Behavior attributes are setup-time contracts: removing
52
+ `refresh(node)` handle of your own `enhance()`, or `applyEnhancement()` for a
53
+ `data-enhance` token. The built-in `data-*` behaviors (`data-mask`,
54
+ `data-filter`, …) expose no handle: insert the element with the attribute
55
+ already set. Behavior attributes are setup-time contracts: removing
53
56
  one does not disconnect an active behavior. If an application needs a live
54
57
  enable/disable switch, keep that state inside its enhancer and clean it up when
55
58
  the element leaves the DOM.
@@ -229,8 +232,8 @@ Three non-obvious requirements:
229
232
 
230
233
  Lifecycle: `prepareSurface` → `openSurface` → `closeSurface` →
231
234
  `disconnectSurface`. `retainSurface(panel)` reference-counts a surface shared by
232
- several triggers and returns a release function; `isSurfaceOpen` and
233
- `getSurfaceAutoClose` read current state. The separate `escape` primitive adds
235
+ several triggers and returns a release function; `isSurfaceOpen` reads the
236
+ current state. The separate `escape` primitive adds
234
237
  visible dismissable UI to the shared per-document Escape stack.
235
238
 
236
239
  Actual-managed surfaces are mutually exclusive within a document. Opening a
@@ -785,8 +788,9 @@ document.addEventListener("actual:invalid", (event) => {
785
788
 
786
789
  Custom rules go in `data-validation-rules` and resolve through
787
790
  `FormValidator.registerRule`. Built-in rules are `same`, `number`, `digits`,
788
- `alnum`, and `date`. The library never performs network validation; server
789
- errors flow back through `FormValidator.setErrors(form, { name: message })` and
791
+ `alnum`, `starts-with`, `ends-with`, `date`, and `tel-prefix`. The library never
792
+ performs network validation; server errors flow back through
793
+ `FormValidator.setErrors(form, { name: message })` and
790
794
  `FormValidator.clearFieldError(field)`.
791
795
 
792
796
  ```js
@@ -25,17 +25,29 @@ The layered import is the cleanest way to keep both stylesheets during the
25
25
  migration:
26
26
 
27
27
  ```css
28
+ /* Tailwind v4 emits its own layers (theme, base, components, utilities),
29
+ so declare the order explicitly: Actual sits above Tailwind components
30
+ but below Tailwind utilities during the migration. */
31
+ @layer theme, base, components, actual, utilities;
32
+
28
33
  @import "tailwind.css"; /* your compiled Tailwind output */
29
- @layer actual;
30
34
  @import "actual-css/full" layer(actual);
35
+
31
36
  /* Unlayered project overrides stay on top */
32
37
  @import "app.css";
33
38
  ```
34
39
 
35
- The full import places every Actual rule in a single `actual` layer, so
36
- unlayered Tailwind output and project CSS keep precedence while the migration
37
- runs. See [Cascade layer strategy](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/cascade-layer.md) for the
38
- limitations of the approach.
40
+ The full import places every Actual rule in a single `actual` layer. The
41
+ explicit `@layer` order is what keeps Tailwind utilities above Actual while
42
+ the migration runs — not the unlayered status of either output. See
43
+ [Cascade layer strategy](https://github.com/lekoala/actual-css/blob/master/docs/design-notes/cascade-layer.md)
44
+ for the limitations of the approach.
45
+
46
+ Layers do not scope custom properties: both frameworks define
47
+ `--font-sans`, `--font-mono`, `--font-weight-*`, and `--radius-sm` /
48
+ `--radius-lg`. With `actual` after `theme`, `font-sans` takes Actual's font
49
+ stack and `rounded-lg` resolves to Actual's `--radius-lg`. Expect these
50
+ token collisions on migrated screens until Tailwind is removed.
39
51
 
40
52
  Do not assume that loading both complete stylesheets is automatically safe.
41
53