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/docs/tokens.md DELETED
@@ -1,455 +0,0 @@
1
- # Tokens
2
-
3
- Tokens are the public theming surface. They should make the library feel easy to restyle without requiring users to understand every component implementation detail.
4
-
5
- The surface stays small by keeping most tokens semantic, not component-specific.
6
-
7
- - Global tokens are public API.
8
- - Component-local tokens are implementation details unless documented by that component.
9
- - Components should map global tokens to local variables internally.
10
- - Themes override tokens, not selectors.
11
- - Defaults must work without modern color features or build tooling.
12
- - Modern enhancements can derive better values with `color-mix()`, `light-dark()`, or container queries.
13
-
14
- ## Public Surface
15
-
16
- > Tokens exposed as the public theming API, kept small and semantic.
17
-
18
- ### Color
19
-
20
- Intent tokens are used by `.primary`, `.secondary`, `.success`, `.warning`, `.danger`, and `.neutral`.
21
- They are curated color/foreground pairs. Actual CSS does not compute foregrounds automatically in the core theme contract.
22
-
23
- ```css
24
- :root {
25
- --primary: hsl(270 24% 32%);
26
- --primary-fg: hsl(0 0% 98%);
27
- --secondary: hsl(18 24% 36%);
28
- --secondary-fg: hsl(0 0% 98%);
29
- --success: hsl(145 55% 32%);
30
- --success-fg: hsl(0 0% 98%);
31
- --warning: hsl(38 76% 34%);
32
- --warning-fg: hsl(0 0% 98%);
33
- --danger: hsl(2 62% 45%);
34
- --danger-fg: hsl(0 0% 98%);
35
- --neutral: hsl(220 8% 42%);
36
- --neutral-fg: hsl(0 0% 98%);
37
- }
38
- ```
39
-
40
- Surface tokens describe the canvas and elevation model. Components should prefer these over hard-coded colors.
41
-
42
- ```css
43
- :root {
44
- --surface: hsl(0 0% 100%);
45
- --surface-raised: hsl(0 0% 100%);
46
- --surface-subtle: hsl(220 14% 96%);
47
- --surface-solid: hsl(222 18% 12%);
48
-
49
- --text: hsl(222 18% 12%);
50
- --text-muted: hsl(220 8% 42%);
51
- --text-subtle: hsl(220 7% 58%);
52
-
53
- --border: hsl(220 12% 88%);
54
- --focus: var(--primary);
55
- --focus-outline: 2px solid var(--focus);
56
- --focus-outline-offset: 2px;
57
- --focus-ring: hsl(258 21% 32% / 0.22);
58
- --hover-overlay: hsl(220 20% 10% / 0.04);
59
- }
60
- ```
61
-
62
- The surface levels are: `surface` (canvas), `surface-raised` (cards, raised surfaces), `surface-subtle` (the working subtle level — for hover overlays, form fields, table headers), and `surface-solid` (the dark inverse surface, used for accent areas).
63
-
64
- 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.
65
-
66
- Focus styling is outline-first. A global focus baseline uses `--focus-outline`
67
- and `--focus-outline-offset` for the guaranteed visible affordance: older
68
- browsers show it on `:focus`, while modern browsers limit it to
69
- `:focus-visible`. Components add `--focus-ring-shadow` only where a softer
70
- halo helps.
71
- `forced-colors: active` disables the shadow and maps the outline to
72
- `Highlight`.
73
-
74
- ### Default theme philosophy
75
-
76
- The default theme is intentionally calm. `primary` is a near-neutral dark grey rather than a saturated brand color, `secondary` is a neutral surface rather than a colored intent, and the semantic states are desaturated. Optional themes can re-introduce vivid intent palettes on top of this foundation; the default is meant to be the adult, editorial baseline.
77
-
78
- Soft surfaces (`.btn.soft`, `.badge.soft`, and the default `.alert`) are generated from intent colors through `color-mix()`. Themes can tune the mix globally instead of rewriting component selectors.
79
-
80
- ```css
81
- :root {
82
- --soft-bg-mix: 88%;
83
- --soft-border-mix: 65%;
84
- --soft-hover-alpha: 12%;
85
- }
86
- ```
87
-
88
- ### Shape
89
-
90
- Shape is intentionally small. Components can derive local radii from these.
91
-
92
- ```css
93
- :root {
94
- --radius-sm: 0.25rem;
95
- --radius: 0.5rem;
96
- --radius-lg: 0.75rem;
97
- --border-width: 1px;
98
- }
99
- ```
100
-
101
- Do not create shape APIs like `.rounded`, `.pill`, or `.square` for every component. Prefer theme-level changes, for example a square theme can set all radius tokens to `0`.
102
-
103
- ### Spacing
104
-
105
- Spacing should be useful without becoming a design-token encyclopedia.
106
-
107
- ```css
108
- :root {
109
- --gap: 0.75rem;
110
- --space-1: 0.25rem;
111
- --space-2: 0.5rem;
112
- --space-3: 0.75rem;
113
- --space-4: 1rem;
114
- --space-5: 1.5rem;
115
- --space-6: 2rem;
116
- }
117
- ```
118
-
119
- Use `em` for spacing that should scale with the component text size, such as button gaps and inline padding. Use `rem` for global rhythm, layouts, and fixed density.
120
-
121
- ### Typography
122
-
123
- Typography tokens should cover the document baseline and common component needs.
124
-
125
- ```css
126
- :root {
127
- --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
128
- --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
129
-
130
- --line-height: 1.5;
131
- --font-weight: 400;
132
- --font-weight-strong: 650;
133
-
134
- /* Type scale */
135
- --font-size-xs: 0.8125rem;
136
- --font-size-sm: 0.875rem;
137
- --font-size-md: 1rem; /* base */
138
- --font-size-lg: 1.125rem;
139
- }
140
- ```
141
-
142
- Components can use local font variables for their own mapping, but should inherit by default. `--font-size-md` is the baseline — going smaller (`.sm`/`--font-size-sm`/`-xs`) is the easy direction; going larger adjusts line-heights and surrounding paddings, so prefer keeping base body text at `--font-size-md`.
143
-
144
- ### Controls
145
-
146
- Controls share sizing so buttons, inputs, selects, and compact app UI always align. `.sm`/`.lg` set shared density tokens for controls, content components, and compact labels.
147
-
148
- ```css
149
- :root {
150
- --control-size-sm: 2rem; /* 32px → .sm */
151
- --control-size-md: 2.375rem; /* 38px → default */
152
- --control-size-lg: 2.75rem; /* 44px → .lg */
153
- --control-size: var(--control-size-md);
154
- --control-pad-x: 1em;
155
- --control-font-size: var(--font-size-md);
156
- }
157
- ```
158
-
159
- - `.lg` — 44px control height, `--font-size-lg` (18px)
160
- - default — 38px control height, `--font-size-md` (16px, the baseline)
161
- - `.sm` — 32px control height, `--font-size-sm` (14px)
162
-
163
- Size modifiers swap the shared control tokens:
164
-
165
- ```css
166
- .sm {
167
- --control-size: var(--control-size-sm);
168
- --control-font-size: var(--font-size-sm);
169
- --variant-font-size: var(--font-size-sm);
170
- --variant-icon-size: 1rem;
171
- --variant-pad-block: var(--space-3);
172
- --variant-compact-size: 1.25rem;
173
- --variant-compact-font-size: 0.75rem;
174
- }
175
-
176
- .lg {
177
- --control-size: var(--control-size-lg);
178
- --control-font-size: var(--font-size-lg);
179
- --variant-font-size: var(--font-size-lg);
180
- --variant-icon-size: 1.5rem;
181
- --variant-pad-block: var(--space-5);
182
- --variant-compact-size: 1.875rem;
183
- --variant-compact-font-size: var(--font-size-sm);
184
- }
185
- ```
186
-
187
- Content components can consume `--variant-*` for local density. Compact labels such as `.badge` consume `--variant-compact-*`; display elements with special geometry, such as avatar and spinner, keep their own size scales.
188
-
189
- ### Elevation
190
-
191
- Use a tiny elevation surface. Components should not invent their own shadow scales.
192
-
193
- ```css
194
- :root {
195
- --shadow: 0 0.5rem 1.5rem hsl(220 30% 10% / 0.08);
196
- --shadow-popout: 0 0.25rem 0.75rem hsl(220 30% 10% / 0.15);
197
- }
198
- ```
199
-
200
- ### Motion
201
-
202
- Motion tokens keep transitions consistent and easy to disable.
203
-
204
- ```css
205
- :root {
206
- --duration: 150ms; /* component transitions (hover/focus) */
207
- --duration-slow: 200ms; /* one-shot open/close (.drawer animation) */
208
- --duration-spin: 0.75s; /* continuous spin (.spinner) */
209
- --duration-shimmer: 1.2s; /* continuous shimmer (.skeleton, indeterminate .progress) */
210
- --ease: ease;
211
- }
212
-
213
- @media (prefers-reduced-motion: reduce) {
214
- :root {
215
- --duration: 0.01ms;
216
- }
217
- }
218
- ```
219
-
220
- `reset.css` clamps raw animation and transition durations to `0.01ms`, resets
221
- their delays, and restores `html` scroll behavior under
222
- `prefers-reduced-motion: reduce` in addition to the `--duration` token above.
223
- Decorative loops effectively complete immediately without blanket-disabling
224
- animation support.
225
-
226
- ### Icons
227
-
228
- Icons used by CSS-only controls are public tokens because users may need to replace them.
229
-
230
- ```css
231
- :root {
232
- --icon-chevron-mask: url("data:image/svg+xml,...stroke='black'...");
233
- --icon-check-mask: url("data:image/svg+xml,...stroke='black'...");
234
- --icon-chevron-image: url("data:image/svg+xml,...");
235
- }
236
- ```
237
-
238
- Prefer `*-mask` tokens for CSS icons so components can color them with `background-color: currentColor` or a semantic token. Use image tokens only where the platform requires `background-image` (native select chevrons cannot use `currentColor`). Only add an icon token when CSS needs the icon. Markup icons should stay in markup.
239
-
240
- ### Composition tokens
241
-
242
- A few cross-component helpers:
243
-
244
- - `--indicator-offset` / `--indicator-ring` — positioning and contrast ring for a status dot attached to `.avatar > .badge:empty`.
245
- - `--backdrop-color` / `--backdrop-opacity` / `--backdrop-fill` — scrim color and opacity shared by `dialog.modal::backdrop`, `dialog.drawer::backdrop`, and `.surface-backdrop`. Override `--backdrop-opacity` per theme for a denser or lighter scrim.
246
-
247
- ```css
248
- :root {
249
- --indicator-offset: 14.65%; /* circle-aware inset */
250
- --indicator-ring: var(--surface);
251
- --backdrop-color: var(--surface-solid);
252
- --backdrop-opacity: 0.45;
253
- }
254
- ```
255
-
256
- ## Internal Tokens
257
-
258
- > Component-local tokens allowed when they reduce duplication or make component code clearer.
259
-
260
- Internal tokens are allowed when they reduce duplication or make component code clearer.
261
-
262
- ### Variant Plumbing
263
-
264
- The shared visual variants are:
265
-
266
- - `.solid`: full background, paired foreground. This is the default for buttons and badges.
267
- - `.soft`: lightly tinted background, colored text. Good for alerts and secondary badges.
268
- - `.outline`: transparent background, colored border and text.
269
-
270
- Buttons also support two button-only variants:
271
-
272
- - `.ghost`: transparent button with hover affordance.
273
- - `.link`: button semantics with link presentation.
274
-
275
- Do not force `.ghost` or `.link` onto components where the interaction model does not fit. Shared API should follow shared use.
276
-
277
- ```css
278
- .btn {
279
- --btn-bg: var(--ui-bg, var(--btn-default-bg, var(--intent, var(--neutral))));
280
- --btn-fg: var(--ui-fg, var(--btn-default-fg, var(--intent-fg, var(--neutral-fg))));
281
- --btn-border: var(--ui-border, var(--btn-default-border, transparent));
282
-
283
- border: var(--border-width) solid var(--btn-border-color, var(--btn-border));
284
- border-block-end-color: var(
285
- --btn-border-block-end-color,
286
- var(--btn-border-color, var(--btn-border))
287
- );
288
- border-block-end-width: var(--btn-border-block-end-width, var(--border-width));
289
- }
290
- ```
291
-
292
- Button-local defaults are intentionally inherited extension points for themes
293
- that change button grammar, such as turning the default solid button into a
294
- surface button with an intent-colored lower edge. Keep them local until the
295
- same need appears across multiple components.
296
-
297
- Text controls (`.input`/`.textarea`/`.select`, forms/control.css) expose the
298
- same lower-edge extension point: `--control-border-block-end-color` and
299
- `--control-border-block-end-width` are unset by default and fall back to the
300
- regular border, so a theme can give inputs an underline-style lower edge the
301
- same way `edge.css` does for buttons, without fighting the hover/focus/disabled
302
- state rules that still own `--control-border` itself.
303
-
304
- Alerts (components/alert.css) expose the same idea on the leading edge instead
305
- of the lower one: `--alert-border-inline-start-color` and
306
- `--alert-border-inline-start-width` are unset by default and fall back to the
307
- regular `--ui-border`, so a theme can turn an alert into a callout with a
308
- colored flag on the inline-start side, matching the classic notice pattern,
309
- without a per-variant override.
310
-
311
- A theme is not limited to the tokens above; it can restate any component-local
312
- token under a state selector to change the interaction recipe itself, not just
313
- the palette. `edge.css` does this for focus: the base input recipe
314
- (forms/control.css) flips `--control-border` to `--focus` on top of the shared
315
- outline + ring, which triples up with the lower-edge accent above. Edge drops
316
- the border-color flip under `:focus-visible` (thickening the accent edge
317
- instead) and also thins `--focus-outline` and tightens `--focus-outline-offset`
318
- so the outline hugs the control rather than floating a second ring around it.
319
-
320
- Buttons don't consume `--focus-outline` — button.css computes its own outline
321
- inline so the color can follow `--btn-focus-color` (intent-aware, unlike the
322
- plain `--focus` the shared token is hardcoded to), so it exposes a matching
323
- local token, `--btn-focus-outline-width`, defaulting to the same
324
- `calc(var(--border-width) * 2)` the old hardcoded value used. `edge.css`
325
- overrides it the same way it overrides `--focus-outline` for inputs.
326
-
327
- The outline overrides are gated in `@media not (forced-colors: active)`. Under
328
- forced-colors, `theme.css` repoints `--focus-outline` to a Highlight-colored
329
- system outline via a `:root, [data-theme="light"], [data-theme="dark"]` rule —
330
- `:root` always matches the root element regardless of its `data-theme`
331
- attribute, so that fallback reaches every theme, edge included. But it is a
332
- lower-specificity, inherited value: an unguarded same-property override
333
- declared directly on `.input` would out-specificity it in every color mode,
334
- including forced-colors, because direct beats inherited regardless of the
335
- media condition either rule was written under. Any theme that restyles
336
- `--focus-outline`, `--btn-focus-outline-width`, or `--focus-ring-shadow` needs
337
- the same guard.
338
-
339
- Rules:
340
-
341
- - Prefix internal component tokens with the component name (`--btn-*`, `--alert-*`, `--card-*`).
342
- - Prefix shared variant plumbing with `--ui-*`.
343
- - Keep component-specific variants in component code when they do not generalize.
344
- - Do not require users to override internal tokens for ordinary theming.
345
- - Promote an internal token to public only when there is a repeated, reasonable customization need.
346
-
347
- ## Theme Contract
348
-
349
- > What a theme must override to restyle the library without touching components.
350
-
351
- Themes override tokens, not selectors. The themes in `src/css/themes/` are demo examples, not shipped by the default stylesheet; they exist to show valid ways to use this contract.
352
-
353
- A minimal recolor theme overrides the intent pairs, surfaces, text colors, border, focus, focus ring, and hover overlay.
354
-
355
- Shape, shadow, motion, typography, and soft-variant mix tokens are optional knobs. Override them only when the theme actually changes that part of the system.
356
-
357
- A full theme can override:
358
-
359
- - intent colors and foreground pairs
360
- - surface, text, border, focus, and overlay colors
361
- - radius tokens
362
- - optional shadow, motion, typography, and soft-variant mix tokens
363
-
364
- ```css
365
- [data-theme="brand"] {
366
- color-scheme: light;
367
-
368
- --primary: hsl(260 70% 54%);
369
- --primary-fg: white;
370
- --secondary: hsl(190 70% 38%);
371
- --secondary-fg: white;
372
- --success: hsl(150 58% 34%);
373
- --success-fg: white;
374
- --warning: hsl(42 92% 52%);
375
- --warning-fg: hsl(35 35% 10%);
376
- --danger: hsl(355 72% 52%);
377
- --danger-fg: white;
378
- --neutral: hsl(250 8% 42%);
379
- --neutral-fg: white;
380
-
381
- --surface: hsl(260 45% 98%);
382
- --surface-raised: white;
383
- --surface-subtle: hsl(260 35% 94%);
384
- --surface-solid: hsl(260 28% 16%);
385
-
386
- --text: hsl(260 24% 14%);
387
- --text-muted: hsl(260 10% 42%);
388
- --text-subtle: hsl(260 8% 56%);
389
- --border: hsl(260 24% 84%);
390
- --focus: var(--primary);
391
- --focus-ring: hsl(260 70% 54% / 0.22);
392
- --hover-overlay: hsl(260 30% 10% / 0.04);
393
- --soft-bg-mix: 84%;
394
- --soft-border-mix: 58%;
395
-
396
- --radius: 0.625rem;
397
- --radius-sm: 0.3125rem;
398
- --radius-lg: 0.875rem;
399
- }
400
- ```
401
-
402
- Without `data-theme`, the default theme advertises `color-scheme: light dark`
403
- and follows the user's OS preference in browsers that support `light-dark()`.
404
- Dark themes should set `color-scheme: dark`. Light themes should set
405
- `color-scheme: light`. Browsers without `light-dark()` receive the light
406
- fallback.
407
-
408
- Because the native select chevron is an SVG background image and cannot inherit
409
- `currentColor`, the select hands the arrow back to the browser under
410
- `prefers-contrast: more` (same treatment as `forced-colors: active`) instead of
411
- shipping a recolored chevron per theme — one native fallback covers every
412
- theme without a high-contrast icon variant to maintain.
413
-
414
- In `forced-colors: active`, the default theme maps the public color tokens to
415
- system colors (`Canvas`, `CanvasText`, `ButtonText`, `Highlight`, and
416
- `HighlightText`) and removes decorative shadows. Components should inherit
417
- that token mode by default; keep local forced-color rules only for custom
418
- control geometry, native bar parts, disabled states, or selected states that
419
- would otherwise be communicated only by background color.
420
-
421
- ## Component Mapping
422
-
423
- > How components map global tokens to local aliases for a clear theme and variant contract.
424
-
425
- Components should map global tokens once at the top of the component and use local tokens afterwards.
426
-
427
- ```css
428
- .alert {
429
- --alert-bg: var(--ui-bg, var(--surface-subtle));
430
- --alert-fg: var(--ui-fg, var(--text));
431
- --alert-border: var(--ui-border, var(--border));
432
- --alert-radius: var(--radius-lg);
433
-
434
- border: var(--border-width) solid var(--alert-border);
435
- border-radius: var(--alert-radius);
436
- background: var(--alert-bg);
437
- color: var(--alert-fg);
438
- }
439
- ```
440
-
441
- This makes the contract obvious:
442
-
443
- - theme authors adjust global tokens
444
- - variant code adjusts `--ui-*`
445
- - component code consumes local aliases
446
-
447
- ## Non-Goals
448
-
449
- > Decisions deliberately excluded from the token surface to keep it small.
450
-
451
- - Do not expose a separate token for every selector.
452
- - Do not mirror Pico-style component-specific variables unless there is a clear need.
453
- - Do not add Tailwind-like color scales as public API.
454
- - Do not rely on Sass, PostCSS, or generated token files.
455
- - Do not make dark mode required for old browsers. Light mode is the fallback.