actual-css 0.1.0 → 0.3.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 (215) hide show
  1. package/CHANGELOG.md +90 -1
  2. package/README.md +25 -8
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1492 -931
  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 +95 -6
  10. package/docs/design-notes/cascade-layer.md +1 -1
  11. package/docs/design-notes/enhancement-contract.md +101 -0
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +3 -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 +119 -0
  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 +205 -0
  25. package/docs/pages/components/card.md +183 -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 +41 -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 +92 -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 +57 -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 +21 -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 +18 -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 +31 -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 +10 -4
  103. package/package.json +30 -16
  104. package/src/css/actual.css +12 -1
  105. package/src/css/base.css +3 -9
  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 +162 -49
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +82 -39
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +7 -0
  113. package/src/css/components/button.css +82 -38
  114. package/src/css/components/card.css +17 -37
  115. package/src/css/components/drawer.css +30 -13
  116. package/src/css/components/flyout.css +39 -110
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -3
  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 +17 -18
  124. package/src/css/components/overline.css +0 -7
  125. package/src/css/components/pagination.css +1 -1
  126. package/src/css/components/progress.css +8 -26
  127. package/src/css/components/scrollspy.css +2 -2
  128. package/src/css/components/skeleton.css +7 -0
  129. package/src/css/components/spinner.css +10 -11
  130. package/src/css/components/status-bar.css +18 -28
  131. package/src/css/components/surface.css +2 -8
  132. package/src/css/components/tab.css +20 -48
  133. package/src/css/components/table.css +7 -5
  134. package/src/css/components/tooltip.css +14 -7
  135. package/src/css/focus.css +16 -3
  136. package/src/css/forms/choice-card.css +13 -15
  137. package/src/css/forms/choice.css +21 -28
  138. package/src/css/forms/control.css +16 -26
  139. package/src/css/forms/custom-select.css +9 -11
  140. package/src/css/forms/form-actions.css +14 -3
  141. package/src/css/forms/form.css +7 -4
  142. package/src/css/forms/index.css +5 -4
  143. package/src/css/forms/input-icon.css +30 -12
  144. package/src/css/forms/native.css +48 -33
  145. package/src/css/forms/range.css +103 -0
  146. package/src/css/forms/select.css +8 -8
  147. package/src/css/forms/switch.css +14 -13
  148. package/src/css/forms/validation.css +32 -5
  149. package/src/css/grid.css +19 -18
  150. package/src/css/intents.css +2 -2
  151. package/src/css/layout.css +29 -1
  152. package/src/css/optional/aura.css +93 -0
  153. package/src/css/optional/chat.css +105 -0
  154. package/src/css/optional/fab.css +104 -0
  155. package/src/css/optional/floating-field.css +87 -0
  156. package/src/css/optional/index.css +5 -0
  157. package/src/css/optional/layout-extra.css +7 -4
  158. package/src/css/optional/otp.css +115 -0
  159. package/src/css/optional/scroll-snap.css +6 -0
  160. package/src/css/optional/scroller.css +19 -7
  161. package/src/css/optional/typography-fluid.css +5 -0
  162. package/src/css/optional/utilities-extra.css +17 -2
  163. package/src/css/prose.css +18 -8
  164. package/src/css/reset.css +0 -10
  165. package/src/css/theme.css +29 -13
  166. package/src/css/themes/cyberpunk.css +1 -1
  167. package/src/css/themes/dim.css +3 -3
  168. package/src/css/themes/edge.css +11 -37
  169. package/src/css/themes/index.css +3 -3
  170. package/src/css/themes/{dark.css → indigo.css} +5 -4
  171. package/src/css/themes/neon.css +5 -5
  172. package/src/css/themes/square.css +0 -3
  173. package/src/css/tokens.css +81 -21
  174. package/src/css/utilities.css +8 -10
  175. package/src/css/variants.css +37 -65
  176. package/src/js/command.js +1 -3
  177. package/src/js/context-menu.js +72 -53
  178. package/src/js/dialog.js +68 -79
  179. package/src/js/dismiss.js +15 -0
  180. package/src/js/enhance.js +76 -19
  181. package/src/js/enhancement-loader.js +301 -0
  182. package/src/js/events.js +3 -0
  183. package/src/js/filter.js +3 -3
  184. package/src/js/floating.js +101 -131
  185. package/src/js/flyout.js +106 -96
  186. package/src/js/focus-group.js +116 -0
  187. package/src/js/focus.js +6 -1
  188. package/src/js/index.js +4 -2
  189. package/src/js/input.js +1 -1
  190. package/src/js/keys.js +27 -0
  191. package/src/js/mask.js +7 -6
  192. package/src/js/menu.js +89 -35
  193. package/src/js/parse-config.js +47 -0
  194. package/src/js/password.js +9 -1
  195. package/src/js/scrollspy.js +133 -62
  196. package/src/js/selectors.js +14 -31
  197. package/src/js/status.js +16 -14
  198. package/src/js/surface.js +219 -125
  199. package/src/js/tab.js +46 -66
  200. package/src/js/tooltip.js +53 -31
  201. package/src/js/validation.js +45 -51
  202. package/dist/actual.js.map +0 -31
  203. package/docs/README.md +0 -26
  204. package/docs/components.md +0 -860
  205. package/docs/design-notes/previous-work.md +0 -5
  206. package/docs/forms.md +0 -1265
  207. package/docs/javascript.md +0 -426
  208. package/docs/layout.md +0 -480
  209. package/docs/patterns.md +0 -415
  210. package/docs/reserved.md +0 -7
  211. package/docs/tokens.md +0 -455
  212. package/docs/ui.md +0 -937
  213. package/docs/utilities.md +0 -384
  214. package/src/css/dialog-fallback.css +0 -56
  215. 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
  }
@@ -61,11 +72,10 @@
61
72
 
62
73
  /* Lines and focus */
63
74
  --border: hsl(260 15% 88%);
64
- --focus: var(--primary);
75
+ --focus: var(--neutral);
65
76
  /* Pre-color-mix fallback only — superseded below by a derivation from
66
- --focus so overriding --primary/--focus never leaves a stale ring. */
67
- --focus-ring: hsl(268 32% 33% / 0.22);
68
- --focus-ring-shadow: 0 0 0 3px var(--focus-ring);
77
+ --focus so overriding --neutral/--focus never leaves a stale ring. */
78
+ --focus-ring: hsl(258 7% 43% / 0.3);
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
@@ -106,7 +122,7 @@
106
122
  :root,
107
123
  [data-theme="light"],
108
124
  [data-theme="dark"] {
109
- --focus-ring: color-mix(in oklch, var(--focus) 22%, transparent);
125
+ --focus-ring: color-mix(in oklch, var(--focus) 30%, transparent);
110
126
  }
111
127
  }
112
128
 
@@ -146,7 +162,7 @@
146
162
  --text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%));
147
163
 
148
164
  --border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%));
149
- --focus-ring: light-dark(hsl(268 32% 33% / 0.22), hsl(268 42% 76% / 0.28));
165
+ --focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28));
150
166
  --hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06));
151
167
  --shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%));
152
168
  }
@@ -156,7 +172,7 @@
156
172
  :root,
157
173
  [data-theme="light"] {
158
174
  --focus-ring: light-dark(
159
- color-mix(in oklch, var(--focus) 22%, transparent),
175
+ color-mix(in oklch, var(--focus) 30%, transparent),
160
176
  color-mix(in oklch, var(--focus) 28%, transparent)
161
177
  );
162
178
  }
@@ -166,7 +182,7 @@
166
182
  :root,
167
183
  [data-theme="light"],
168
184
  [data-theme="dark"] {
169
- --focus-outline: calc(var(--border-width) * 2) solid Highlight;
185
+ --focus-outline-color: Highlight;
170
186
  --focus-ring-shadow: none;
171
187
 
172
188
  --backdrop-fill: Canvas;
@@ -179,7 +195,7 @@
179
195
  [data-theme="dark"] {
180
196
  --border-width: 2px;
181
197
  --focus-outline-offset: 3px;
182
- --focus-ring-shadow: 0 0 0 4px var(--focus-ring);
198
+ --focus-ring-width: 4px;
183
199
  --text-muted: var(--text);
184
200
  --text-subtle: var(--text);
185
201
  }
@@ -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:
@@ -79,38 +77,14 @@
79
77
  }
80
78
 
81
79
  /* Focus recipe override — the base recipe (control.css) flips --control-border
82
- to --focus on top of the outline + ring, which triples up with the lower-edge
83
- accent already visible here. Edge drops the border-color flip and thickens
84
- the accent edge instead; the ring stays untouched. */
80
+ to --focus and draws a box-shadow ring. Edge drops the border-color flip and
81
+ thickens the accent edge instead so the intent-coloured lower edge stays
82
+ dominant; the focus ring and its 3px width remain the same as the default. */
85
83
  [data-theme="edge"] :is(.input, .textarea, .select):focus-visible {
86
84
  --control-border: var(--border);
87
85
  --control-border-block-end-width: 3px;
88
86
  }
89
87
 
90
- /* The shared 2px-solid/2px-offset outline reads as a second ring on top of
91
- the accent edge (inputs) or the box-shadow ring (buttons). Edge thins both
92
- to the border width and tightens the offset so the outline hugs the
93
- control instead of floating a second halo around it. Gated outside
94
- forced-colors so that mode's Highlight-colored --focus-outline (theme.css)
95
- stays in charge for inputs — a direct property on the element would
96
- otherwise out-specificity that fallback regardless of the media condition
97
- it was declared under. Buttons don't consume --focus-outline (they compute
98
- their own outline so it can use --btn-focus-color's intent awareness), so
99
- they get their own local width token, --btn-focus-outline-width. */
100
- @media not (forced-colors: active) {
101
- [data-theme="edge"] :is(.input, .textarea, .select, .btn) {
102
- --focus-outline-offset: 1px;
103
- }
104
-
105
- [data-theme="edge"] :is(.input, .textarea, .select) {
106
- --focus-outline: var(--border-width) solid var(--focus);
107
- }
108
-
109
- [data-theme="edge"] .btn {
110
- --btn-focus-outline-width: var(--border-width);
111
- }
112
- }
113
-
114
88
  @supports (color: light-dark(red, blue)) {
115
89
  [data-theme="edge"] {
116
90
  --primary: light-dark(hsl(224 72% 42%), hsl(220 80% 76%));
@@ -5,7 +5,7 @@
5
5
  * The themes are illustrative: each one demonstrates a different set of knobs
6
6
  * the token contract exposes, beyond recoloring.
7
7
  *
8
- * dark — canonical dark (single block, color-scheme: dark only)
8
+ * indigo — deep blue/violet dark palette (single block)
9
9
  * dim — dark variant with lifted surface tones
10
10
  * corporate — reference paired light/dark recolor via light-dark()
11
11
  * forest — paired recolor + scaled-up radius tokens
@@ -24,9 +24,9 @@
24
24
  * gradient — paired recolor + Bootstrap 4 style gradient on solid buttons
25
25
  * via component-local button tokens
26
26
  *
27
- * Single-block themes (dark, dim, cyberpunk, neon) pin one color-scheme and
27
+ * Single-block themes (indigo, dim, cyberpunk, neon) pin one color-scheme and
28
28
  * cannot adapt to prefers-color-scheme; the paired ones follow the OS. */
29
- @import "./dark.css";
29
+ @import "./indigo.css";
30
30
  @import "./dim.css";
31
31
  @import "./corporate.css";
32
32
  @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,23 @@
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: 0 0.5rem 1.5rem rgb(0 0 0 / 0.08);
52
+ --shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04);
33
53
  --shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15);
34
54
 
35
55
  /* Motion — see reset.css for the prefers-reduced-motion hard reset.
@@ -43,14 +63,15 @@
43
63
  --ease: ease;
44
64
 
45
65
  /* Focus ring — shared by every interactive component's :focus-visible */
46
- --focus-outline: calc(var(--border-width) * 2) solid var(--focus);
66
+ --focus-outline-color: currentColor;
67
+ --focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color);
47
68
  --focus-outline-offset: 2px;
69
+ --focus-ring-width: 3px;
48
70
 
49
71
  /* Status dot positioning — used by .avatar > .badge:empty. The offset is
50
72
  the circle-aware inset (1 - 1/√2) / 2 ≈ 14.65%; the ring surrounds the
51
73
  dot with the parent surface color. */
52
74
  --indicator-offset: 14.65%;
53
- --indicator-ring: var(--surface);
54
75
 
55
76
  /* Shared backdrop — consumed by modal, drawer, and mobile flyout sheets. */
56
77
  --backdrop-color: rgb(0 0 0);
@@ -61,7 +82,8 @@
61
82
  --bar-height: 0.65rem;
62
83
 
63
84
  /* Controls — inputs and buttons share --control-size so heights always match.
64
- .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. */
65
87
  --control-size-sm: 2rem;
66
88
  --control-size-md: 2.375rem;
67
89
  --control-size-lg: 2.75rem;
@@ -69,19 +91,38 @@
69
91
  --control-pad-x: 1em;
70
92
  --control-font-size: var(--font-size-md);
71
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
+ }
72
114
 
115
+ :root {
73
116
  /* Variant tuning — themes may adjust generated soft surfaces without
74
117
  replacing the component rules. */
75
118
  --soft-bg-mix: 88%;
76
119
  --soft-border-mix: 65%;
77
120
  --soft-hover-alpha: 12%;
78
121
 
79
- /* Shared icons */
80
- --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");
81
- --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");
82
- /* Native select uses background-image, which cannot inherit currentColor.
83
- The stroke matches the default theme's text-subtle (hue 258 neutral). */
84
- --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");
85
126
 
86
127
  /* Viewport size tokens — fallback vh/vw with dvh/vi enhancement.
87
128
  Consumed by full-viewport components (app-shell, drawer, modal, flyout).
@@ -90,13 +131,24 @@
90
131
  --viewport-block: 100vh;
91
132
  --viewport-inline: 100vw;
92
133
 
93
- /* 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. */
94
138
  --z-sticky: 10;
95
139
  --z-menu: 20;
96
140
  --z-tooltip: 50;
97
141
  --z-status: 60;
98
142
  }
99
143
 
144
+ @media (forced-colors: active) {
145
+ :root {
146
+ --state-selected: Highlight;
147
+ --state-selected-fg: HighlightText;
148
+ --state-disabled: GrayText;
149
+ }
150
+ }
151
+
100
152
  @supports (block-size: 100dvh) {
101
153
  :root {
102
154
  --viewport-block: 100dvh;
@@ -110,9 +162,17 @@
110
162
  }
111
163
 
112
164
  @supports (color: color-mix(in oklch, red, white)) {
113
- :root {
114
- --shadow: 0 0.5rem 1.5rem color-mix(in oklch, var(--shadow-color) 8%, 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. */
169
+ --shadow:
170
+ 0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent),
171
+ 0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent);
115
172
  --shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent);
173
+ }
174
+
175
+ :root {
116
176
  --backdrop-fill: color-mix(
117
177
  in oklch,
118
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,20 @@
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 also
6
+ * paints the element directly (background/color/border-color) so it works on
7
+ * any block, competing with component surface treatments (.card.subtle).
8
+ * Component-specific defaults (.btn solid-by-default, .badge
9
+ * soft-by-default, .alert soft-by-default) live with their components:
7
10
  * 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
11
+ * components/badge.css — .badge default soft
12
+ * components/alert.css — .alert default soft (consumes --ui-* directly)
10
13
  *
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-*.
14
+ * .sm/.lg set shared density tokens: spacing rhythm and geometry only.
15
+ * Density never changes typography — controls keep their font-size and
16
+ * content components keep theirs. Controls consume --control-size; non-control
17
+ * components may consume --density-* when the density context should affect
18
+ * their local rhythm. Compact components may consume --density-compact-size.
14
19
  * Components with bespoke sizing (spinner, avatar) define local .sm/.lg;
15
20
  * applying .sm/.lg to card, dialog, accordion, navbar, table is
16
21
  * a no-op.
@@ -36,27 +41,34 @@
36
41
  --ui-border: var(--intent, var(--border));
37
42
  }
38
43
 
44
+ /* Inverse surface treatment. Components (e.g. .card) compose through --ui-*;
45
+ the direct background/color/border-color also make .inverted useful on a
46
+ generic element. --heading is set so global headings (var(--heading, inherit))
47
+ do not inherit an outside heading color onto the dark surface. */
48
+ .inverted {
49
+ --ui-bg: var(--surface-solid);
50
+ --ui-fg: var(--surface);
51
+ --ui-border: transparent;
52
+
53
+ --heading: var(--surface);
54
+ --busy-overlay-bg: var(--surface-solid);
55
+
56
+ background: var(--ui-bg);
57
+ color: var(--ui-fg);
58
+ border-color: var(--ui-border);
59
+ }
60
+
39
61
  .sm {
40
- --gap: var(--space-2);
41
- --variant-space: var(--space-2);
62
+ --gap: var(--space-20);
63
+ --density-space: var(--space-20);
42
64
  --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;
65
+ --density-compact-size: 1.25rem;
49
66
  }
50
67
  .lg {
51
- --gap: var(--space-5);
52
- --variant-space: var(--space-5);
68
+ --gap: var(--space-50);
69
+ --density-space: var(--space-50);
53
70
  --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);
71
+ --density-compact-size: 1.875rem;
60
72
  }
61
73
 
62
74
  /* Modern color enhancement. Baseline variant values above remain usable
@@ -74,45 +86,5 @@
74
86
  }
75
87
  }
76
88
 
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
-
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);
117
- }
118
- }
89
+ /* Interactive hover recipes live in the components that consume them:
90
+ 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
  /**