actual-css 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/README.md +24 -9
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1532 -1114
  5. package/dist/actual.js +1 -3
  6. package/dist/actual.min.css +1 -1
  7. package/dist/optional.css +1048 -0
  8. package/dist/optional.min.css +1 -0
  9. package/docs/design-notes/browser-support.md +94 -14
  10. package/docs/design-notes/cascade-layer.md +7 -6
  11. package/docs/design-notes/enhancement-contract.md +11 -14
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +2 -1
  14. package/docs/design-notes/platform-alignment.md +492 -0
  15. package/docs/design-notes/references.md +8 -0
  16. package/docs/design-notes/widget-primitives.md +37 -1
  17. package/docs/navigation.json +135 -0
  18. package/docs/pages/components/accordion.md +80 -0
  19. package/docs/pages/components/alert.md +203 -0
  20. package/docs/pages/components/aura.md +35 -0
  21. package/docs/pages/components/avatar.md +95 -0
  22. package/docs/pages/components/badge.md +102 -0
  23. package/docs/pages/components/breadcrumb.md +19 -0
  24. package/docs/pages/components/button.md +210 -0
  25. package/docs/pages/components/card.md +195 -0
  26. package/docs/pages/components/chat.md +50 -0
  27. package/docs/pages/components/dialog.md +367 -0
  28. package/docs/pages/components/drawer.md +113 -0
  29. package/docs/pages/components/fab.md +118 -0
  30. package/docs/pages/components/key.md +33 -0
  31. package/docs/pages/components/meter.md +23 -0
  32. package/docs/pages/components/navbar.md +92 -0
  33. package/docs/pages/components/pagination.md +55 -0
  34. package/docs/pages/components/progress.md +29 -0
  35. package/docs/pages/components/skeleton.md +38 -0
  36. package/docs/pages/components/spinner.md +93 -0
  37. package/docs/pages/components/table.md +102 -0
  38. package/docs/pages/enhancements/flyout.md +391 -0
  39. package/docs/pages/enhancements/overview.md +36 -0
  40. package/docs/pages/enhancements/scrollspy.md +71 -0
  41. package/docs/pages/enhancements/status-bar.md +72 -0
  42. package/docs/pages/enhancements/tabs.md +80 -0
  43. package/docs/pages/enhancements/tooltip.md +130 -0
  44. package/docs/pages/examples/overview.md +43 -0
  45. package/docs/pages/forms/choice-cards.md +102 -0
  46. package/docs/pages/forms/controls.md +32 -0
  47. package/docs/pages/forms/detached-actions.md +59 -0
  48. package/docs/pages/forms/floating-field.md +88 -0
  49. package/docs/pages/forms/form-actions.md +56 -0
  50. package/docs/pages/forms/input-policy.md +91 -0
  51. package/docs/pages/forms/joined-controls.md +37 -0
  52. package/docs/pages/forms/labels.md +42 -0
  53. package/docs/pages/forms/otp.md +50 -0
  54. package/docs/pages/forms/overview.md +188 -0
  55. package/docs/pages/forms/password.md +40 -0
  56. package/docs/pages/forms/range.md +17 -0
  57. package/docs/pages/forms/states-and-sizes.md +321 -0
  58. package/docs/pages/forms/sticky-actions.md +187 -0
  59. package/docs/pages/forms/switch.md +31 -0
  60. package/docs/pages/forms/validation.md +241 -0
  61. package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
  62. package/docs/pages/foundations/theming.md +99 -0
  63. package/docs/pages/foundations/tokens.md +421 -0
  64. package/docs/{typography.md → pages/foundations/typography.md} +27 -9
  65. package/docs/pages/guides/bootstrap.md +438 -0
  66. package/docs/pages/guides/introduction.md +157 -0
  67. package/docs/pages/guides/optional-components.md +33 -0
  68. package/docs/pages/guides/progressive-enhancement.md +686 -0
  69. package/docs/pages/guides/tailwind.md +455 -0
  70. package/docs/pages/layout/app-shell.md +25 -0
  71. package/docs/pages/layout/center.md +29 -0
  72. package/docs/pages/layout/cluster.md +59 -0
  73. package/docs/pages/layout/frame.md +30 -0
  74. package/docs/pages/layout/grid.md +142 -0
  75. package/docs/pages/layout/list.md +41 -0
  76. package/docs/pages/layout/media.md +28 -0
  77. package/docs/pages/layout/overview.md +37 -0
  78. package/docs/pages/layout/scroll-snap.md +54 -0
  79. package/docs/pages/layout/sidebar-layout.md +27 -0
  80. package/docs/pages/layout/stack.md +55 -0
  81. package/docs/pages/layout/switcher.md +36 -0
  82. package/docs/pages/layout/topbar.md +23 -0
  83. package/docs/pages/patterns/actions.md +52 -0
  84. package/docs/pages/patterns/code-block.md +36 -0
  85. package/docs/pages/patterns/exclusive-accordion.md +16 -0
  86. package/docs/pages/patterns/hierarchical-nav.md +26 -0
  87. package/docs/pages/patterns/icon-input.md +25 -0
  88. package/docs/pages/patterns/intent-nesting.md +3 -0
  89. package/docs/pages/patterns/navigation-list.md +235 -0
  90. package/docs/pages/patterns/overview.md +58 -0
  91. package/docs/pages/patterns/structured-lists.md +47 -0
  92. package/docs/pages/utilities/accessibility.md +34 -0
  93. package/docs/pages/utilities/flex.md +105 -0
  94. package/docs/pages/utilities/links.md +18 -0
  95. package/docs/pages/utilities/list.md +32 -0
  96. package/docs/pages/utilities/overflow.md +43 -0
  97. package/docs/pages/utilities/overview.md +18 -0
  98. package/docs/pages/utilities/shape.md +25 -0
  99. package/docs/pages/utilities/sizing-wrapping.md +115 -0
  100. package/docs/pages/utilities/spacing.md +79 -0
  101. package/docs/pages/utilities/text.md +43 -0
  102. package/llms.txt +4 -4
  103. package/package.json +21 -12
  104. package/src/css/actual.css +16 -2
  105. package/src/css/base.css +3 -5
  106. package/src/css/components/accordion.css +18 -30
  107. package/src/css/components/actions.css +4 -1
  108. package/src/css/components/alert.css +139 -102
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +69 -42
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +6 -0
  113. package/src/css/components/button.css +84 -34
  114. package/src/css/components/card.css +26 -38
  115. package/src/css/components/drawer.css +30 -13
  116. package/src/css/components/flyout.css +39 -109
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -2
  119. package/src/css/components/key.css +2 -0
  120. package/src/css/components/menu.css +125 -0
  121. package/src/css/components/meter.css +0 -23
  122. package/src/css/components/modal.css +162 -7
  123. package/src/css/components/navbar.css +30 -23
  124. package/src/css/components/overline.css +8 -9
  125. package/src/css/components/pagination.css +1 -1
  126. package/src/css/components/progress.css +8 -26
  127. package/src/css/components/skeleton.css +7 -0
  128. package/src/css/components/spinner.css +6 -7
  129. package/src/css/components/status-bar.css +15 -24
  130. package/src/css/components/surface.css +2 -8
  131. package/src/css/components/tab.css +19 -48
  132. package/src/css/components/table.css +7 -5
  133. package/src/css/components/tooltip.css +14 -7
  134. package/src/css/focus.css +14 -38
  135. package/src/css/forms/choice-card.css +13 -14
  136. package/src/css/forms/choice.css +21 -27
  137. package/src/css/forms/control.css +18 -17
  138. package/src/css/forms/custom-select.css +9 -11
  139. package/src/css/forms/form-actions.css +14 -3
  140. package/src/css/forms/form.css +7 -4
  141. package/src/css/forms/index.css +4 -4
  142. package/src/css/forms/input-icon.css +30 -12
  143. package/src/css/forms/native.css +50 -11
  144. package/src/css/forms/range.css +17 -9
  145. package/src/css/forms/select.css +8 -8
  146. package/src/css/forms/switch.css +14 -12
  147. package/src/css/forms/validation.css +32 -5
  148. package/src/css/grid.css +32 -18
  149. package/src/css/intents.css +2 -2
  150. package/src/css/layout.css +45 -2
  151. package/src/css/optional/aura.css +93 -0
  152. package/src/css/optional/chat.css +105 -0
  153. package/src/css/optional/fab.css +104 -0
  154. package/src/css/optional/floating-field.css +87 -0
  155. package/src/css/optional/index.css +5 -0
  156. package/src/css/optional/layout-extra.css +7 -4
  157. package/src/css/optional/otp.css +115 -0
  158. package/src/css/optional/scroll-snap.css +6 -0
  159. package/src/css/optional/scroller.css +13 -7
  160. package/src/css/optional/typography-fluid.css +5 -0
  161. package/src/css/optional/utilities-extra.css +17 -2
  162. package/src/css/prose.css +18 -8
  163. package/src/css/reset.css +0 -10
  164. package/src/css/theme.css +21 -5
  165. package/src/css/themes/cyberpunk.css +1 -1
  166. package/src/css/themes/dim.css +3 -3
  167. package/src/css/themes/edge.css +8 -10
  168. package/src/css/themes/index.css +8 -4
  169. package/src/css/themes/{dark.css → indigo.css} +5 -4
  170. package/src/css/themes/neon.css +5 -5
  171. package/src/css/themes/square.css +0 -3
  172. package/src/css/tokens.css +74 -20
  173. package/src/css/utilities.css +8 -10
  174. package/src/css/variants.css +51 -66
  175. package/src/js/command.js +1 -3
  176. package/src/js/context-menu.js +67 -35
  177. package/src/js/dialog.js +68 -79
  178. package/src/js/dismiss.js +15 -0
  179. package/src/js/enhance.js +54 -23
  180. package/src/js/enhancement-loader.js +301 -0
  181. package/src/js/events.js +3 -0
  182. package/src/js/filter.js +3 -3
  183. package/src/js/floating.js +101 -131
  184. package/src/js/flyout.js +104 -87
  185. package/src/js/focus-group.js +116 -0
  186. package/src/js/focus.js +6 -1
  187. package/src/js/index.js +4 -2
  188. package/src/js/input.js +1 -1
  189. package/src/js/keys.js +27 -0
  190. package/src/js/mask.js +7 -6
  191. package/src/js/menu.js +87 -34
  192. package/src/js/parse-config.js +47 -0
  193. package/src/js/password.js +9 -1
  194. package/src/js/scrollspy.js +12 -4
  195. package/src/js/selectors.js +0 -7
  196. package/src/js/status.js +3 -1
  197. package/src/js/surface.js +187 -136
  198. package/src/js/tab.js +36 -53
  199. package/src/js/tooltip.js +70 -35
  200. package/src/js/validation.js +34 -8
  201. package/dist/actual.js.map +0 -31
  202. package/docs/README.md +0 -28
  203. package/docs/components.md +0 -860
  204. package/docs/design-notes/previous-work.md +0 -5
  205. package/docs/forms.md +0 -1265
  206. package/docs/javascript.md +0 -496
  207. package/docs/layout.md +0 -480
  208. package/docs/patterns.md +0 -504
  209. package/docs/reserved.md +0 -7
  210. package/docs/tokens.md +0 -455
  211. package/docs/ui.md +0 -929
  212. package/docs/utilities.md +0 -384
  213. package/src/css/dialog-fallback.css +0 -56
  214. package/src/js/dialog-fallback.js +0 -163
package/src/css/reset.css CHANGED
@@ -24,16 +24,6 @@ select {
24
24
  display: none !important;
25
25
  }
26
26
 
27
- /* Legacy dialog fallback only. Modern dialogs keep the browser's native closed
28
- state so display/overlay transitions are not flattened by the reset.
29
- The presentation for the dialog-fallback.js shim lives in the separate
30
- dialog-fallback.css, so it can be dropped from custom bundles. */
31
- @supports not (selector(dialog:modal)) {
32
- dialog:not([open]) {
33
- display: none;
34
- }
35
- }
36
-
37
27
  :where(img, svg, video) {
38
28
  max-inline-size: 100%;
39
29
  block-size: auto;
package/src/css/theme.css CHANGED
@@ -1,3 +1,14 @@
1
+ /*
2
+ * Theme tokens — the palette, surfaces, text, borders, radius and shadow
3
+ * color. Named themes (themes/*.css) override this file; authors can set the
4
+ * same tokens on :root or on [data-theme="…"] islands.
5
+ *
6
+ * Public: Intent colors, Surfaces, Text, Lines and focus, Radius scale,
7
+ * Shadow color, Focus ring derivation.
8
+ * Derived: "Theme-derived aliases", Tone subsystem, Backdrop fill — reference
9
+ * other tokens; hard-coded overrides on a custom theme will break the intent.
10
+ * Internal: --hgroup-gap.
11
+ */
1
12
  :root {
2
13
  color-scheme: light dark;
3
14
  }
@@ -65,7 +76,6 @@
65
76
  /* Pre-color-mix fallback only — superseded below by a derivation from
66
77
  --focus so overriding --neutral/--focus never leaves a stale ring. */
67
78
  --focus-ring: hsl(258 7% 43% / 0.3);
68
- --focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
69
79
  --hover-overlay: hsl(260 20% 10% / 0.04);
70
80
 
71
81
  /* Radius scale — themes may override the whole scale (e.g. [data-theme="square"]) */
@@ -76,13 +86,19 @@
76
86
  /* Shadow color — consumed by --shadow / --shadow-popout in tokens.css */
77
87
  --shadow-color: hsl(260 25% 10%);
78
88
 
79
- /* Heading and selection — derived from theme color tokens */
89
+ /* hgroup internal gap */
90
+ --hgroup-gap: 0.25em;
91
+ }
92
+
93
+ /* Theme-derived aliases — re-declared on every theme boundary (see tokens.css
94
+ for the custom-property resolution rationale): islands recompute these from
95
+ their own tokens instead of inheriting :root's resolved values. */
96
+ :root,
97
+ [data-theme] {
80
98
  --heading: var(--text);
81
99
  --selection-bg: var(--primary);
82
100
  --selection-fg: var(--primary-fg);
83
-
84
- /* hgroup internal gap */
85
- --hgroup-gap: 0.25em;
101
+ --focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
86
102
  }
87
103
 
88
104
  /* Tone subsystem — fixed-backdrop foreground for components placed over media
@@ -1,5 +1,5 @@
1
1
  /* Cyberpunk — light-only stress-test theme (single block, color-scheme: light,
2
- * same shape as dark.css). Illustrative rather than production: it exists to
2
+ * same shape as indigo.css). Illustrative rather than production: it exists to
3
3
  * prove the token contract survives an extreme spec — a saturated yellow
4
4
  * canvas, near-black text and borders, dark-on-bright intent pairs, zero
5
5
  * radius, 2px strokes, and hard offset shadows — without touching a single
@@ -1,7 +1,7 @@
1
1
  /* Dim — a dark theme variant that lifts the surface tones up a few percentage
2
- * points. Same shape as dark.css (dark-only, single block) so authors can drop
3
- * it in when the canonical dark is too dense. Demonstrates how a theme can
4
- * recolor the same token set without changing structure. */
2
+ * points. Same shape as indigo.css (dark-only, single block) so authors can
3
+ * drop it in when the canonical dark is too dense. Demonstrates how a theme
4
+ * can recolor the same token set without changing structure. */
5
5
  [data-theme="dim"] {
6
6
  color-scheme: dark;
7
7
 
@@ -3,9 +3,9 @@
3
3
  * intent-colored lower edge (.solid remains available when an action needs a
4
4
  * filled treatment), text controls (input/textarea/select) pick up the same
5
5
  * lower-edge accent via --control-border-block-end-*, alerts get an
6
- * intent-colored leading flag via --alert-border-inline-start-*, and text
7
- * controls and buttons both get a thinner, tighter focus outline than the
8
- * shared recipe. */
6
+ * intent-colored leading flag via --alert-border-inline-start-*. The focus
7
+ * ring keeps the shared width — edge only drops the border-color flip so the
8
+ * intent-colored lower edge stays dominant. */
9
9
  [data-theme="edge"] {
10
10
  color-scheme: light dark;
11
11
 
@@ -61,13 +61,11 @@
61
61
  --ui-hover-bg: var(--surface-raised);
62
62
  }
63
63
 
64
- @media (hover: hover) {
65
- [data-theme="edge"]
66
- .btn:not(
67
- :where(:disabled, [aria-disabled="true"], .solid, .soft, .outline, .ghost, .link)
68
- ):hover {
69
- box-shadow: none;
70
- }
64
+ [data-theme="edge"]
65
+ .btn:not(
66
+ :where(:disabled, [aria-disabled="true"], .solid, .soft, .outline, .ghost, .link)
67
+ ):hover {
68
+ box-shadow: none;
71
69
  }
72
70
 
73
71
  /* Intent-dependent alert token, same reasoning as the button rule above:
@@ -1,11 +1,15 @@
1
- /* Opt-in theme bundle. Importing this file pulls in every theme below, each
1
+ /*
2
+ * Reference theme presets. Shipped for inspection and copying; not public
3
+ * package entry points (package.json blocks ./css/themes exports).
4
+ *
5
+ * Opt-in theme bundle. Importing this file pulls in every theme below, each
2
6
  * gated by [data-theme="…"]. The light baseline is :root in src/css/theme.css —
3
7
  * there is no light.css here.
4
8
  *
5
9
  * The themes are illustrative: each one demonstrates a different set of knobs
6
10
  * the token contract exposes, beyond recoloring.
7
11
  *
8
- * dark — canonical dark (single block, color-scheme: dark only)
12
+ * indigo — deep blue/violet dark palette (single block)
9
13
  * dim — dark variant with lifted surface tones
10
14
  * corporate — reference paired light/dark recolor via light-dark()
11
15
  * forest — paired recolor + scaled-up radius tokens
@@ -24,9 +28,9 @@
24
28
  * gradient — paired recolor + Bootstrap 4 style gradient on solid buttons
25
29
  * via component-local button tokens
26
30
  *
27
- * Single-block themes (dark, dim, cyberpunk, neon) pin one color-scheme and
31
+ * Single-block themes (indigo, dim, cyberpunk, neon) pin one color-scheme and
28
32
  * cannot adapt to prefers-color-scheme; the paired ones follow the OS. */
29
- @import "./dark.css";
33
+ @import "./indigo.css";
30
34
  @import "./dim.css";
31
35
  @import "./corporate.css";
32
36
  @import "./forest.css";
@@ -1,7 +1,8 @@
1
- /* Canonical dark theme. Single-block shape (color-scheme: dark only) — it
2
- * does not adapt to prefers-color-scheme; opt in via [data-theme="dark"].
3
- * dim.css is a darker variant with lifted surface tones. */
4
- [data-theme="dark"] {
1
+ /* Indigo — deep blue/violet palette, dark-only. Single-block shape
2
+ * (color-scheme: dark only) — it does not adapt to prefers-color-scheme;
3
+ * opt in via [data-theme="indigo"]. dim.css is a darker variant with
4
+ * lifted surface tones. */
5
+ [data-theme="indigo"] {
5
6
  color-scheme: dark;
6
7
 
7
8
  --primary: hsl(220 90% 66%);
@@ -1,9 +1,9 @@
1
1
  /* Neon — dark-only theme (single block, color-scheme: dark, same shape as
2
- * dark.css) that demonstrates wide-gamut progressive enhancement. The baseline
3
- * block uses sRGB-safe hsl() and works everywhere dark.css works; on P3
4
- * displays with oklch() support the intents are re-declared with chroma beyond
5
- * sRGB. This is the pattern for shipping colors sRGB cannot express without
6
- * breaking older screens or browsers. Also demonstrates the soft-variant
2
+ * indigo.css) that demonstrates wide-gamut progressive enhancement. The
3
+ * baseline block uses sRGB-safe hsl() and works everywhere indigo.css works;
4
+ * on P3 displays with oklch() support the intents are re-declared with chroma
5
+ * beyond sRGB. This is the pattern for shipping colors sRGB cannot express
6
+ * without breaking older screens or browsers. Also demonstrates the soft-variant
7
7
  * tuning knobs and glow shadows on popovers. */
8
8
  [data-theme="neon"] {
9
9
  color-scheme: dark;
@@ -34,9 +34,6 @@
34
34
  }
35
35
 
36
36
  @supports (color: light-dark(red, blue)) {
37
- /* Square — paired light/dark theme that demonstrates overriding the full
38
- * --radius scale to 0 for sharp-cornered applications. The recolor is close to
39
- * the default blue palette; the radius override is the point. */
40
37
  [data-theme="square"] {
41
38
  --primary: light-dark(hsl(220 90% 56%), hsl(220 75% 80%));
42
39
  --primary-fg: light-dark(white, hsl(220 30% 12%));
@@ -1,4 +1,21 @@
1
1
  :root {
2
+ /*
3
+ * Global framework tokens.
4
+ *
5
+ * Public: properties in the named groups below are public API (typography,
6
+ * spacing, shape, motion, focus, controls, viewport, z-index, icons) unless
7
+ * a group is explicitly marked Internal or Derived.
8
+ *
9
+ * Derived: "Theme-derived aliases" and "State tokens" reference intent and
10
+ * surface tokens and follow the theme; do not hard-code values into them.
11
+ * Internal: indicator offsets, --bar-height, --disabled-opacity and the
12
+ * like are consumed by components, not authored.
13
+ *
14
+ * Component-local hooks and internal state relays live with their
15
+ * components. See docs/pages/foundations/tokens.md for the public/internal
16
+ * token contract.
17
+ */
18
+
2
19
  /* Typography */
3
20
  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
4
21
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
@@ -16,20 +33,22 @@
16
33
  --font-size-md: 1rem;
17
34
  --font-size-lg: 1.125rem;
18
35
 
19
- /* Spacing */
36
+ /* Spacing — official steps are named in tens so the scale stays open for
37
+ application themes to intercalate intermediate values (--space-15,
38
+ --space-45…) without colliding with future framework steps. */
20
39
  --gap: 0.75rem;
21
- --variant-space: var(--space-4);
22
- --space-1: 0.25rem;
23
- --space-2: 0.5rem;
24
- --space-3: 0.75rem;
25
- --space-4: 1rem;
26
- --space-5: 1.5rem;
27
- --space-6: 2rem;
40
+ --density-space: var(--space-40);
41
+ --density-compact-size: 1.5rem;
42
+ --space-10: 0.25rem;
43
+ --space-20: 0.5rem;
44
+ --space-30: 0.75rem;
45
+ --space-40: 1rem;
46
+ --space-50: 1.5rem;
47
+ --space-60: 2rem;
28
48
 
29
49
  /* Shape */
30
50
  --border-width: 1px;
31
51
  --radius-full: 9999px;
32
- --shadow-xs: 0 1px 2px rgb(0 0 0 / 0.04);
33
52
  --shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04);
34
53
  --shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15);
35
54
 
@@ -53,7 +72,6 @@
53
72
  the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
54
73
  dot with the parent surface color. */
55
74
  --indicator-offset: 14.65%;
56
- --indicator-ring: var(--surface);
57
75
 
58
76
  /* Shared backdrop — consumed by modal, drawer, and mobile flyout sheets. */
59
77
  --backdrop-color: rgb(0 0 0);
@@ -64,7 +82,8 @@
64
82
  --bar-height: 0.65rem;
65
83
 
66
84
  /* Controls — inputs and buttons share --control-size so heights always match.
67
- .sm/.lg (variants.css) switch --control-size and --control-font-size. */
85
+ .sm/.lg (variants.css) switch --control-size; --control-font-size stays
86
+ at its baseline because density does not change typography. */
68
87
  --control-size-sm: 2rem;
69
88
  --control-size-md: 2.375rem;
70
89
  --control-size-lg: 2.75rem;
@@ -72,19 +91,38 @@
72
91
  --control-pad-x: 1em;
73
92
  --control-font-size: var(--font-size-md);
74
93
  --disabled-opacity: 0.65;
94
+ }
95
+
96
+ /* Theme-derived aliases. These reference intent/surface tokens, so they must
97
+ be re-declared on every theme boundary: a custom property resolves its
98
+ var() references at computed-value time on the element that declares it,
99
+ so an alias declared only on :root is inherited as an already-resolved
100
+ value and would not follow a [data-theme] island's overridden tokens.
101
+ Themes that want a distinct alias override it explicitly afterwards. */
102
+ :root,
103
+ [data-theme] {
104
+ /* State tokens — semantic colors for selected/pressed/current and disabled.
105
+ Components consume these instead of hard-wiring --primary/--text-subtle
106
+ so forced-colors can override them in one place. */
107
+ --state-selected: var(--primary);
108
+ --state-selected-fg: var(--primary-fg);
109
+ --state-disabled: var(--text-subtle);
110
+
111
+ /* Status dot ring — follows the island's surface. */
112
+ --indicator-ring: var(--surface);
113
+ }
75
114
 
115
+ :root {
76
116
  /* Variant tuning — themes may adjust generated soft surfaces without
77
117
  replacing the component rules. */
78
118
  --soft-bg-mix: 88%;
79
119
  --soft-border-mix: 65%;
80
120
  --soft-hover-alpha: 12%;
81
121
 
82
- /* Shared icons */
83
- --icon-chevron-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
84
- --icon-check-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
85
- /* Native select uses background-image, which cannot inherit currentColor.
86
- The stroke matches the default theme's text-subtle (hue 258 neutral). */
87
- --icon-chevron-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
122
+ /* Shared icon assets. Override these URLs when a strict CSP disallows data:. */
123
+ --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
124
+ --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
125
+ --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E");
88
126
 
89
127
  /* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
90
128
  Consumed by full-viewport components (app-shell, drawer, modal, flyout).
@@ -93,13 +131,24 @@
93
131
  --viewport-block: 100vh;
94
132
  --viewport-inline: 100vw;
95
133
 
96
- /* Layering */
134
+ /* Z-index scale — used ONLY by non-dialog overlays (see the layering
135
+ note below for the full model). Actual CSS reserves multiples of 10 for
136
+ framework layers; applications may use intermediate values for local
137
+ layering. */
97
138
  --z-sticky: 10;
98
139
  --z-menu: 20;
99
140
  --z-tooltip: 50;
100
141
  --z-status: 60;
101
142
  }
102
143
 
144
+ @media (forced-colors: active) {
145
+ :root {
146
+ --state-selected: Highlight;
147
+ --state-selected-fg: HighlightText;
148
+ --state-disabled: GrayText;
149
+ }
150
+ }
151
+
103
152
  @supports (block-size: 100dvh) {
104
153
  :root {
105
154
  --viewport-block: 100dvh;
@@ -113,12 +162,17 @@
113
162
  }
114
163
 
115
164
  @supports (color: color-mix(in oklch, red, white)) {
116
- :root {
117
- --shadow-xs: 0 1px 2px color-mix(in oklch, var(--shadow-color) 4%, transparent);
165
+ :root,
166
+ [data-theme] {
167
+ /* Shadows derive from --shadow-color (theme.css) — re-declared on every
168
+ theme boundary so islands follow their own shadow-color. */
118
169
  --shadow:
119
170
  0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent),
120
171
  0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent);
121
172
  --shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent);
173
+ }
174
+
175
+ :root {
122
176
  --backdrop-fill: color-mix(
123
177
  in oklch,
124
178
  var(--backdrop-color) calc(var(--backdrop-opacity) * 100%),
@@ -30,7 +30,7 @@
30
30
  min-inline-size: 0;
31
31
  }
32
32
 
33
- .nowrap {
33
+ .text-nowrap {
34
34
  white-space: nowrap;
35
35
  }
36
36
 
@@ -125,15 +125,13 @@
125
125
  text-decoration: none;
126
126
  }
127
127
 
128
- @media (hover: hover) {
129
- .link-muted:hover {
130
- color: var(--text);
131
- }
128
+ .link-muted:hover {
129
+ color: var(--text);
132
130
  }
133
131
 
134
132
  /* Spacing step helpers — semantic scale (sm / default / lg).
135
133
  Names map to logical properties for writing-direction safety. The default
136
- step uses --space-4, which matches the framework's built-in padding on
134
+ step uses --space-40, which matches the framework's built-in padding on
137
135
  cards, dialogs, and form-actions. */
138
136
 
139
137
  /* Gap */
@@ -143,20 +141,20 @@
143
141
 
144
142
  /* Padding — block (vertical) */
145
143
  .py {
146
- padding-block: var(--variant-space);
144
+ padding-block: var(--density-space);
147
145
  }
148
146
 
149
147
  /* Padding — inline (horizontal) */
150
148
  .px {
151
- padding-inline: var(--variant-space);
149
+ padding-inline: var(--density-space);
152
150
  }
153
151
 
154
152
  /* Margin — block-start */
155
153
  .mbs {
156
- margin-block-start: var(--variant-space);
154
+ margin-block-start: var(--density-space);
157
155
  }
158
156
 
159
157
  /* Margin — block-end */
160
158
  .mbe {
161
- margin-block-end: var(--variant-space);
159
+ margin-block-end: var(--density-space);
162
160
  }
@@ -2,15 +2,24 @@
2
2
  * Cross-cutting variant token mapping.
3
3
  *
4
4
  * Variant scope: .solid/.soft/.outline set --ui-* for components that opt into
5
- * shared variants. Component-specific defaults (.btn solid-by-default, .badge
6
- * solid-by-default, .alert soft-by-default) live with their components:
5
+ * shared variants. .inverted is a surface modifier, not a variant: it paints
6
+ * the element directly (background/color/border-color) so it works on any bare
7
+ * block, and remaps the shared --ui-* tokens so participating components
8
+ * (.card, .navbar, .alert, .badge) invert through their own rules. Loaded
9
+ * before components, so a component with its own explicit surface (e.g.
10
+ * .accordion) naturally keeps that surface — .inverted never forces a
11
+ * non-participant.
12
+ * Component-specific defaults (.btn solid-by-default, .badge
13
+ * soft-by-default, .alert soft-by-default) live with their components:
7
14
  * components/button.css — .btn default solid + .btn.ghost + .btn.link
8
- * components/badge.css — .badge default solid
9
- * components/alert.css — .alert default + .alert.solid/.alert.outline
15
+ * components/badge.css — .badge default soft behind explicit --ui-*
16
+ * components/alert.css — .alert default soft behind explicit --ui-*
10
17
  *
11
- * .sm/.lg set shared size tokens. Controls consume --control-*; non-control
12
- * components may consume --variant-* when the variant should affect their
13
- * local density. Compact components may consume --variant-compact-*.
18
+ * .sm/.lg set shared density tokens: spacing rhythm and geometry only.
19
+ * Density never changes typography — controls keep their font-size and
20
+ * content components keep theirs. Controls consume --control-size; non-control
21
+ * components may consume --density-* when the density context should affect
22
+ * their local rhythm. Compact components may consume --density-compact-size.
14
23
  * Components with bespoke sizing (spinner, avatar) define local .sm/.lg;
15
24
  * applying .sm/.lg to card, dialog, accordion, navbar, table is
16
25
  * a no-op.
@@ -32,31 +41,43 @@
32
41
 
33
42
  .outline {
34
43
  --ui-bg: transparent;
35
- --ui-fg: var(--intent, var(--text));
36
- --ui-border: var(--intent, var(--border));
44
+ --ui-fg: var(--intent, currentColor);
45
+ --ui-border: var(--intent, currentColor);
46
+ }
47
+
48
+ /* Inverse surface treatment. Paints the element directly (so it works on any
49
+ bare block) and remaps the shared --ui-* tokens so opt-in components
50
+ (.card, .navbar) invert through their own rules. Because this loads before
51
+ component base surfaces, components with their own explicit surface
52
+ (e.g. .accordion) keep it — participation is opt-in, never forced. --heading
53
+ is set so global headings (var(--heading, inherit)) do not inherit an outside
54
+ heading color onto the dark surface; surface-owning descendants derive
55
+ their contextual aliases from their own local surface. */
56
+ .inverted {
57
+ --ui-bg: var(--surface-solid);
58
+ --ui-fg: var(--surface);
59
+ --ui-border: transparent;
60
+ --ui-hover-bg: var(--surface-solid);
61
+
62
+ --heading: var(--surface);
63
+ --busy-overlay-bg: var(--surface-solid);
64
+
65
+ background: var(--ui-bg);
66
+ color: var(--ui-fg);
67
+ border-color: var(--ui-border);
37
68
  }
38
69
 
39
70
  .sm {
40
- --gap: var(--space-2);
41
- --variant-space: var(--space-2);
71
+ --gap: var(--space-20);
72
+ --density-space: var(--space-20);
42
73
  --control-size: var(--control-size-sm);
43
- --control-font-size: var(--font-size-sm);
44
- --variant-font-size: var(--font-size-sm);
45
- --variant-icon-size: 1rem;
46
- --variant-pad-block: var(--space-3);
47
- --variant-compact-size: 1.25rem;
48
- --variant-compact-font-size: 0.75rem;
74
+ --density-compact-size: 1.25rem;
49
75
  }
50
76
  .lg {
51
- --gap: var(--space-5);
52
- --variant-space: var(--space-5);
77
+ --gap: var(--space-50);
78
+ --density-space: var(--space-50);
53
79
  --control-size: var(--control-size-lg);
54
- --control-font-size: var(--font-size-lg);
55
- --variant-font-size: var(--font-size-lg);
56
- --variant-icon-size: 1.5rem;
57
- --variant-pad-block: var(--space-5);
58
- --variant-compact-size: 1.875rem;
59
- --variant-compact-font-size: var(--font-size-sm);
80
+ --density-compact-size: 1.875rem;
60
81
  }
61
82
 
62
83
  /* Modern color enhancement. Baseline variant values above remain usable
@@ -72,47 +93,11 @@
72
93
  );
73
94
  /* @sync soft-recipe */
74
95
  }
75
- }
76
-
77
- /* Interactive hover recipe shared by .btn and .badge. Declared outside :hover
78
- on purpose: --ui-hover-bg is only consumed inside their @media (hover: hover)
79
- rules, so these values are inert until hovered. :is() carries real
80
- specificity so the components' :where() intent boundaries
81
- (--ui-hover-bg: initial) cannot override the recipe. The solid/soft overlay
82
- darkening lives in the consuming components. */
83
- :is(.btn, a.badge, button.badge):is(.outline, .ghost) {
84
- --ui-hover-bg: var(--surface-subtle);
85
- }
86
-
87
- @supports (color: color-mix(in oklch, red, white)) {
88
- /* Solid has no literal class requirement: bare .btn/.badge default to solid,
89
- so this must match anything that isn't one of the other variants rather
90
- than an explicit .solid class. Stacks with the box-shadow overlay the
91
- consuming components apply on top for extra depth. */
92
- :is(.btn, a.badge, button.badge):not(:where(.soft, .outline, .ghost, .link)) {
93
- --ui-hover-bg: color-mix(in oklch, var(--intent, var(--neutral)) 88%, black);
94
- }
95
-
96
- /* Same recipe as the resting .soft bg with the surface share reduced by
97
- --soft-hover-alpha: stays opaque, so it reads on any backdrop instead of
98
- letting the container show through on hover. */
99
- :is(.btn, a.badge, button.badge):is(.soft) {
100
- --ui-hover-bg: color-mix(
101
- in oklch,
102
- var(--surface) calc(var(--soft-bg-mix) - var(--soft-hover-alpha)),
103
- var(--intent, var(--neutral))
104
- );
105
- }
106
96
 
107
- /* Mixed against the opaque --surface-subtle rather than transparent: a
108
- transparent-based tint reads clean on light surfaces but goes muddy and
109
- low-contrast on dark ones, since the same low alpha lands very
110
- differently over a light vs. a near-black backdrop. */
111
- :is(.btn, a.badge, button.badge):is(.outline) {
112
- --ui-hover-bg: color-mix(in oklch, var(--surface-subtle) 92%, var(--intent, var(--neutral)));
113
- }
114
-
115
- :is(.btn, a.badge, button.badge):is(.ghost) {
116
- --ui-hover-bg: color-mix(in oklch, var(--intent, var(--text)) 8%, transparent);
97
+ .inverted {
98
+ --ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black);
117
99
  }
118
100
  }
101
+
102
+ /* Interactive hover recipes live in the components that consume them:
103
+ components/button.css and components/badge.css. */
package/src/js/command.js CHANGED
@@ -25,9 +25,7 @@ function commandNames(commands, caller) {
25
25
  // Native command keywords are ASCII case-insensitive. Custom commands keep
26
26
  // their exact spelling, as required by the command invoker contract.
27
27
  function commandKey(command) {
28
- return command.startsWith("--")
29
- ? command
30
- : command.replace(/[A-Z]/g, (character) => character.toLowerCase());
28
+ return command.startsWith("--") ? command : command.toLowerCase();
31
29
  }
32
30
 
33
31
  /**