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
@@ -0,0 +1,187 @@
1
+ # Sticky Actions
2
+
3
+ > Use `.form-actions sticky` when long forms need reachable submit actions while scrolling.
4
+
5
+ ## Inside the form
6
+
7
+ The `<form>` is the bounding parent. The sticky stays pinned to the viewport bottom while the form is taller than the viewport, then releases when the form's bottom edge reaches it.
8
+
9
+ Scroll the page to see the actions stay reachable.
10
+
11
+ ```html demo
12
+ <form novalidate>
13
+ <div class="stack">
14
+ <label class="field">
15
+ <span class="field-label">Full name <span class="required-mark" aria-hidden="true">*</span></span>
16
+ <input class="input" type="text" autocomplete="name" required placeholder="Jane Doe" />
17
+ </label>
18
+
19
+ <label class="field">
20
+ <span class="field-label">Email <span class="required-mark" aria-hidden="true">*</span></span>
21
+ <input class="input" type="email" autocomplete="email" required />
22
+ </label>
23
+
24
+ <label class="field">
25
+ <span class="field-label">Phone</span>
26
+ <input class="input" type="tel" autocomplete="tel" />
27
+ </label>
28
+
29
+ <label class="field">
30
+ <span class="field-label">Bio</span>
31
+ <textarea class="textarea" rows="4" placeholder="Tell us about yourself…"></textarea>
32
+ </label>
33
+
34
+ <label class="field">
35
+ <span class="field-label">Website</span>
36
+ <input class="input" type="url" autocomplete="url" placeholder="https://example.com" />
37
+ </label>
38
+
39
+ <label class="field">
40
+ <span class="field-label">Language</span>
41
+ <select class="select">
42
+ <option>English</option>
43
+ <option>Français</option>
44
+ <option>Deutsch</option>
45
+ </select>
46
+ </label>
47
+
48
+ <label class="field">
49
+ <span class="field-label">Experience level</span>
50
+ <input class="range" type="range" min="0" max="10" value="5" />
51
+ </label>
52
+
53
+ <label class="field">
54
+ <span class="field-label">Profile picture</span>
55
+ <input class="file" type="file" accept="image/*" />
56
+ </label>
57
+
58
+ <fieldset class="field-group">
59
+ <legend class="field-label">Theme</legend>
60
+ <div class="stack">
61
+ <label class="choice">
62
+ <input class="radio" type="radio" name="theme" value="light" checked />
63
+ <span>Light</span>
64
+ </label>
65
+ <label class="choice">
66
+ <input class="radio" type="radio" name="theme" value="dark" />
67
+ <span>Dark</span>
68
+ </label>
69
+ <label class="choice">
70
+ <input class="radio" type="radio" name="theme" value="auto" />
71
+ <span>System</span>
72
+ </label>
73
+ </div>
74
+ </fieldset>
75
+
76
+ <label class="choice">
77
+ <input class="check" type="checkbox" />
78
+ <span>Email notifications</span>
79
+ </label>
80
+ </div>
81
+
82
+ <div class="form-actions sticky">
83
+ <button type="button" class="btn outline">Cancel</button>
84
+ <button type="submit" class="btn primary">Save changes</button>
85
+ </div>
86
+ </form>
87
+ ```
88
+
89
+ ## Detached from the form
90
+
91
+ For a sticky page footer, dialog footer, or card footer, detach the actions and connect the submit button with the `form` attribute. The bounding parent becomes the wrapper that holds both the form and the actions.
92
+
93
+ Scroll the page to see the detached footer stay reachable.
94
+
95
+ ```html demo
96
+ <form id="account-form" novalidate>
97
+ <div class="stack">
98
+ <label class="field">
99
+ <span class="field-label">Full name <span class="required-mark" aria-hidden="true">*</span></span>
100
+ <input class="input" type="text" autocomplete="name" required placeholder="Jane Doe" />
101
+ </label>
102
+
103
+ <label class="field">
104
+ <span class="field-label">Email <span class="required-mark" aria-hidden="true">*</span></span>
105
+ <input class="input" type="email" autocomplete="email" required />
106
+ </label>
107
+
108
+ <label class="field">
109
+ <span class="field-label">Phone</span>
110
+ <input class="input" type="tel" autocomplete="tel" />
111
+ </label>
112
+
113
+ <label class="field">
114
+ <span class="field-label">Bio</span>
115
+ <textarea class="textarea" rows="4" placeholder="Tell us about yourself…"></textarea>
116
+ </label>
117
+
118
+ <label class="field">
119
+ <span class="field-label">Website</span>
120
+ <input class="input" type="url" autocomplete="url" placeholder="https://example.com" />
121
+ </label>
122
+
123
+ <label class="field">
124
+ <span class="field-label">Language</span>
125
+ <select class="select">
126
+ <option>English</option>
127
+ <option>Français</option>
128
+ <option>Deutsch</option>
129
+ </select>
130
+ </label>
131
+
132
+ <label class="field">
133
+ <span class="field-label">Experience level</span>
134
+ <input class="range" type="range" min="0" max="10" value="5" />
135
+ </label>
136
+
137
+ <label class="field">
138
+ <span class="field-label">Profile picture</span>
139
+ <input class="file" type="file" accept="image/*" />
140
+ </label>
141
+
142
+ <fieldset class="field-group">
143
+ <legend class="field-label">Theme</legend>
144
+ <div class="stack">
145
+ <label class="choice">
146
+ <input class="radio" type="radio" name="theme-detached" value="light" checked />
147
+ <span>Light</span>
148
+ </label>
149
+ <label class="choice">
150
+ <input class="radio" type="radio" name="theme-detached" value="dark" />
151
+ <span>Dark</span>
152
+ </label>
153
+ <label class="choice">
154
+ <input class="radio" type="radio" name="theme-detached" value="auto" />
155
+ <span>System</span>
156
+ </label>
157
+ </div>
158
+ </fieldset>
159
+
160
+ <label class="choice">
161
+ <input class="check" type="checkbox" />
162
+ <span>Email notifications</span>
163
+ </label>
164
+ </div>
165
+ </form>
166
+
167
+ <footer class="form-actions sticky">
168
+ <button type="button" class="btn outline">Cancel</button>
169
+ <button type="submit" class="btn primary" form="account-form">
170
+ Save changes
171
+ </button>
172
+ </footer>
173
+ ```
174
+
175
+ ## CSS hooks
176
+
177
+ - `--z-sticky` — layering token, defaults to `10`.
178
+ - `--form-actions-sticky-padding` — inner padding, defaults to `--space-30`.
179
+ - `--form-actions-sticky-inline-offset` — full-bleed escape hatch for sticky actions inside padded containers. Set this on the page, not the form.
180
+
181
+ ```css
182
+ .settings-page {
183
+ --form-actions-sticky-inline-offset: var(--space-40);
184
+ }
185
+ ```
186
+
187
+ A sticky footer can cover the last field on short pages. That is a page-layout concern, not a framework concern. Add bottom padding to the page or scroll container if needed.
@@ -0,0 +1,31 @@
1
+ # Switches
2
+
3
+ > Toggle controls that share a native checkbox at the markup level, with a switch visual.
4
+
5
+ - The API is `class="switch"`. Use `role="switch"` for ARIA correctness.
6
+ - The visual state tracks the native `checked` attribute. No JavaScript is required.
7
+ - For strict ARIA correctness, keep `aria-checked` in sync. This is an enhancement, not the baseline.
8
+
9
+ ```html demo
10
+ <!-- Default: native state, no JS required. -->
11
+ <input class="switch" type="checkbox" role="switch" checked />
12
+ ```
13
+
14
+ ```html demo
15
+ <!-- Enhancement: keep aria-checked in sync. -->
16
+ <input class="switch"
17
+ type="checkbox"
18
+ role="switch"
19
+ aria-checked="true"
20
+ checked
21
+ onchange="this.setAttribute('aria-checked', this.checked)" />
22
+ ```
23
+
24
+ ## CSS hooks
25
+
26
+ - `--switch-width` — track width.
27
+ - `--switch-block-size` — track height.
28
+ - `--switch-knob-margin` — inset between knob and track.
29
+
30
+ Knob size and travel distance are derived from these three; set the three rather
31
+ than the derived values. Prefer `.sm` / `.lg`, which change `--control-size`.
@@ -0,0 +1,241 @@
1
+ # Validation
2
+
3
+ > Inline error feedback that respects assistive technology and form state.
4
+
5
+ ## Class reference
6
+
7
+ | Class | Kind | Description |
8
+ |---|---|---|
9
+ | `.field-error` | Composition | Canonical inline error message; shown while the field is invalid. |
10
+ | `.field-help` | Composition | Canonical helper text; carries supporting copy before and after validation. |
11
+ | `.field.danger` | Modifier | Manual invalid marking on the field wrapper. |
12
+ | `.needs-validation` | Modifier | Opt-in class for the validation enhancer and `:user-invalid` styling. |
13
+ | `.was-validated` | Modifier | Added to the form by the enhancer after the first submit attempt. |
14
+ | `.status-bar` | Component | Optional status element that shows `data-validation-message` on invalid submit. |
15
+
16
+ - `.field-error` is the canonical error message. It is wired to `aria-invalid="true"` via the input, so the visual border and the assistive-tech announcement move together.
17
+ - `.field-help` is the canonical helper text. It carries supporting copy before and after validation.
18
+ - Set `aria-invalid="true"` on the input. The `.field-error` element is linked via `aria-describedby`.
19
+ - `role="alert"` on `.field-error` is only required when the message is inserted dynamically. Static messages do not need it.
20
+
21
+ ```html demo
22
+ <form novalidate>
23
+ <div class="stack">
24
+ <label class="field">
25
+ <span class="field-label">Email</span>
26
+ <input class="input" type="email"
27
+ value="jane@"
28
+ aria-invalid="true"
29
+ aria-describedby="email-error" />
30
+ <span class="field-error" id="email-error" role="alert">
31
+ Enter a valid email address
32
+ </span>
33
+ </label>
34
+
35
+ <label class="field">
36
+ <span class="field-label">Username</span>
37
+ <input class="input" type="text" value="janedoe" aria-describedby="username-help" />
38
+ <span class="field-help" id="username-help">
39
+ Username is available.
40
+ </span>
41
+ </label>
42
+ </div>
43
+ </form>
44
+ ```
45
+
46
+ ## Optional JavaScript enhancer
47
+
48
+ Native validation first; the enhancer only adds state and focus behavior.
49
+
50
+ Actual CSS ships validation *styles*. Invalid fields are marked with `aria-invalid="true"` (or the manual `.field.danger` wrapper class). The default forms bundle imports `forms/validation.css`; custom builds may omit that file when validation styling is app-owned. A small enhancer (`actual-css/js/validation`), included in the default runtime (`actual-css/js`) and also importable on its own, prevents premature error display, marks invalid fields on blur and submit, focuses the first invalid field on submit, and supports a few custom rules. It is not a validation framework — server and AJAX validation stay in app code.
51
+
52
+ Opt in with the `.needs-validation` class. Importing the module registers the behavior; there is no init call.
53
+
54
+ ```html demo
55
+ <form class="needs-validation" data-enhance="validation" data-validation-message="Please check the highlighted fields.">
56
+ <div class="stack">
57
+ <label class="field">
58
+ <span class="field-label">Password</span>
59
+ <input class="input" type="password" name="password" id="password" required
60
+ minlength="8" maxlength="128"
61
+ aria-describedby="password-error" />
62
+ <span class="field-error" id="password-error">Enter a password.</span>
63
+ </label>
64
+
65
+ <label class="field">
66
+ <span class="field-label">Confirm password</span>
67
+ <input class="input" type="password" name="confirm" required
68
+ data-validation-rules="same #password"
69
+ aria-describedby="confirm-error" />
70
+ <span class="field-error" id="confirm-error" role="alert">Passwords must match.</span>
71
+ </label>
72
+ </div>
73
+
74
+ <div class="form-actions">
75
+ <button class="btn primary" type="submit">Submit</button>
76
+ <button class="btn neutral outline" type="reset">Reset</button>
77
+ </div>
78
+ </form>
79
+ ```
80
+
81
+ On blur, the enhancer sets `aria-invalid="true"` on the invalid field so visual and assistive-tech state move together. On submit, it adds `.was-validated` to the form and marks every invalid field. When the form is invalid it prevents submission, focuses the first invalid field, and dispatches a bubbling `actual:invalid` event with `{ form, firstInvalid, message }`. On reset, it clears all validation state — `.was-validated`, `aria-invalid`, `.field.danger`, and custom-validity messages — which native reset alone would leave behind.
82
+
83
+ The status bar (`actual-css/js/status`) auto-wires to that event: import it and add one status element, and the form's `data-validation-message` appears automatically on invalid submit — no manual listener.
84
+
85
+ ```html demo
86
+ <form class="needs-validation" data-enhance="validation" data-validation-message="Please check the highlighted fields.">
87
+ <div class="stack">
88
+ <label class="field">
89
+ <span class="field-label">Email</span>
90
+ <input class="input" type="email" name="email" required
91
+ aria-describedby="demo-email-error" />
92
+ <span class="field-error" id="demo-email-error" role="alert">
93
+ Enter a valid email.
94
+ </span>
95
+ </label>
96
+
97
+ <label class="field">
98
+ <span class="field-label">Quantity</span>
99
+ <input class="input" name="quantity" inputmode="numeric" required
100
+ data-validation-rules="digits"
101
+ aria-describedby="demo-quantity-error" />
102
+ <span class="field-error" id="demo-quantity-error" role="alert">
103
+ Use digits only.
104
+ </span>
105
+ </label>
106
+
107
+ <div class="field">
108
+ <label class="choice">
109
+ <input class="check" type="checkbox" name="terms" required
110
+ aria-describedby="demo-terms-error" />
111
+ <span>
112
+ <span class="field-label">Accept the terms</span>
113
+ </span>
114
+ </label>
115
+ <span class="field-error" id="demo-terms-error" role="alert">
116
+ Required before continuing.
117
+ </span>
118
+ </div>
119
+
120
+ <div class="field">
121
+ <label class="choice">
122
+ <input class="switch" type="checkbox" role="switch" name="confirm" required
123
+ aria-describedby="demo-confirm-error" />
124
+ <span>
125
+ <span class="field-label">Confirm setup</span>
126
+ </span>
127
+ </label>
128
+ <span class="field-error" id="demo-confirm-error" role="alert">
129
+ Switch this on to submit.
130
+ </span>
131
+ </div>
132
+ <div class="form-actions">
133
+ <button class="btn primary" type="submit">Submit</button>
134
+ <button class="btn neutral outline" type="reset">Reset</button>
135
+ </div>
136
+ </div>
137
+ </form>
138
+
139
+ <div class="status-bar" data-status role="status" aria-live="polite" aria-atomic="true"></div>
140
+ ```
141
+
142
+ If you prefer to handle the summary yourself, listen for `actual:invalid` instead of importing the status module:
143
+
144
+ ```js
145
+ document.addEventListener("actual:invalid", (event) => {
146
+ const { firstInvalid, message } = event.detail;
147
+ // e.g. scroll to firstInvalid, or route message to your own surface
148
+ });
149
+ ```
150
+
151
+ ### Built-in custom rules
152
+
153
+ Custom rules live in `data-validation-rules` as a comma-separated list; each rule may take space-separated options. The enhancer registers a few defaults:
154
+
155
+ - `same <selector>` — Value must equal the value of the element matched by `<selector>` (scoped to the field's form).
156
+ - `number` — Value is empty or a valid `Number`.
157
+ - `digits` — Value is empty or ASCII digits only.
158
+ - `alnum` — Value is empty or letters and digits only.
159
+ - `date` — Value is a valid date in `yyyy-mm-dd`, `dd/mm/yyyy`, `mm/dd/yyyy`, `dd.mm.yyyy`, `mm.dd.yyyy`, `dd-mm-yyyy`, or `mm-dd-yyyy` shape; rejects non-existent dates like `2026-02-29`. ISO remains the recommended wire format because servers can parse it without guessing.
160
+
161
+ Add your own with `FormValidator.registerRule(name, (value, el, ...opts) => boolean)`. The built-in rules treat empty values as valid so optional fields stay optional; custom rules should do the same when that behavior is wanted.
162
+
163
+ The `date` rule pairs naturally with `data-mask` — the mask structures input, the rule validates meaning. Same rule, different formats:
164
+
165
+ ```html demo
166
+ <form class="stack needs-validation" data-enhance="validation" novalidate>
167
+ <label class="field">
168
+ <span class="field-label">Date (ISO)</span>
169
+ <input class="input" name="date-iso"
170
+ data-mask="9999-99-99"
171
+ data-validation-rules="date"
172
+ inputmode="numeric"
173
+ autocomplete="off"
174
+ placeholder="yyyy-mm-dd"
175
+ aria-describedby="date-iso-help date-iso-error"
176
+ required />
177
+ <span class="field-help" id="date-iso-help">
178
+ yyyy-mm-dd format.
179
+ </span>
180
+ <span class="field-error" id="date-iso-error">
181
+ Enter a valid date.
182
+ </span>
183
+ </label>
184
+
185
+ <label class="field">
186
+ <span class="field-label">Date (European)</span>
187
+ <input class="input" name="date-eu"
188
+ data-mask="99/99/9999"
189
+ data-validation-rules="date"
190
+ inputmode="numeric"
191
+ autocomplete="off"
192
+ placeholder="dd/mm/yyyy"
193
+ aria-describedby="date-eu-help date-eu-error"
194
+ required />
195
+ <span class="field-help" id="date-eu-help">
196
+ dd/mm/yyyy format.
197
+ </span>
198
+ <span class="field-error" id="date-eu-error">
199
+ Enter a valid date.
200
+ </span>
201
+ </label>
202
+
203
+ <div class="form-actions">
204
+ <button class="btn primary" type="submit">Submit</button>
205
+ <button class="btn neutral outline" type="reset">Reset</button>
206
+ </div>
207
+ </form>
208
+ ```
209
+
210
+ Custom rules only run when the field has a value. Empty fields skip all custom rules — `required` is the sole gatekeeper for empty values. A custom date rule for a stricter app-specific format could look like:
211
+
212
+ ```js
213
+ FormValidator.registerRule("iso-date", (value) => {
214
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(value));
215
+ });
216
+ ```
217
+
218
+ ### Server and AJAX validation
219
+
220
+ The enhancer never makes network calls. After a valid client submit, your own `form[data-ajax]` flow (see the JavaScript docs) performs the request and maps server errors back onto fields through the public API:
221
+
222
+ ```js
223
+ import { FormValidator } from "actual-css/js/validation";
224
+
225
+ // server responded with field errors
226
+ FormValidator.setErrors(form, { email: "Already taken" });
227
+
228
+ // clear a single field once fixed
229
+ FormValidator.clearFieldError(form.elements.email);
230
+ ```
231
+
232
+ `setErrors` resolves each name to a field, sets `aria-invalid`, the field's `.field-error` text, and `setCustomValidity` so the next submit stays blocked until corrected.
233
+
234
+ ### State coverage
235
+
236
+ Invalid visuals converge across the three ways a field can be flagged:
237
+ `aria-invalid="true"`, `.needs-validation.was-validated :invalid`, and
238
+ `:user-invalid`. Each sets the danger border, and a checked or indeterminate
239
+ `.check`, `.radio`, or `.switch` also flips its fill to danger so border and
240
+ fill signal together. Choice cards surface the same invalid state on the card
241
+ border. The exact selector coverage is asserted by `tests/validation.test.js`.
@@ -1,4 +1,6 @@
1
- # Mental model
1
+ # Philosophy
2
+
3
+ How components, intents, variants, and density compose — and what each layer is responsible for.
2
4
 
3
5
  ## Components
4
6
 
@@ -8,7 +10,7 @@ Components are semantic boxes: `.btn`, `.badge`, `.alert`, `.card`, `.avatar`, e
8
10
 
9
11
  `.primary`, `.secondary`, `.success`, `.warning`, `.danger` do not style elements directly. They only expose `--intent` and `--intent-fg`. Components and variants decide how to consume them.
10
12
 
11
- ```html
13
+ ```html demo
12
14
  <button class="btn primary">Primary</button>
13
15
  <span class="badge success">Success</span>
14
16
  ```
@@ -26,48 +28,61 @@ Custom intents need no framework support — define `--intent` and `--intent-fg`
26
28
  }
27
29
  ```
28
30
 
29
- ```html
31
+ ```html demo
30
32
  <button class="btn tertiary">Tertiary</button>
31
33
  <span class="badge tertiary">Tertiary</span>
32
34
  ```
33
35
 
34
36
  ## Variants
35
37
 
36
- `.solid`, `.soft`, `.outline` define the visual treatment by setting `--ui-bg`, `--ui-fg`, `--ui-border` (and optionally `--ui-hover-bg`). Components consume these tokens with sensible fallbacks.
38
+ `.solid`, `.soft`, `.outline` define the visual treatment by setting `--ui-bg`, `--ui-fg`, `--ui-border`. Components consume these tokens with sensible fallbacks. Interactive components also set a `--ui-hover-bg` recipe that their hover rule reads.
37
39
 
38
- `.btn` and `.badge` default to `.solid`. `.alert` defaults to `.soft`.
40
+ `.btn` defaults to `.solid`; `.badge` and `.alert` default to `.soft`.
39
41
 
40
42
  `.ghost` and `.link` are button-only variants.
41
43
 
42
- ```html
44
+ ```html demo
43
45
  <button class="btn soft primary">Soft primary</button>
44
46
  <span class="badge outline success">Outline success</span>
45
47
  <div class="alert solid warning">Solid warning</div>
46
48
  ```
47
49
 
48
- ## Sizes
50
+ ## Density
51
+
52
+ `.sm` and `.lg` establish inherited density tokens. Density adjusts how much
53
+ space UI consumes — spacing and component geometry — not how large its content
54
+ is: typography and icon size never change. Components opt into the density
55
+ dimensions that make sense for them. The same modifiers can also be applied
56
+ directly to a component for local density.
49
57
 
50
- `.sm` and `.lg` are contextual modifiers. They set inherited size tokens (`--control-size`, `--variant-font-size`, etc.) that components opt into.
58
+ As inherited contexts, one class tightens or loosens the whole subtree:
51
59
 
52
- ```html
60
+ ```html demo
53
61
  <div class="sm">
54
- <button class="btn">Small button</button>
55
- <span class="badge">Small badge</span>
62
+ <button class="btn">Compact button</button>
63
+ <input class="input">
64
+ <span class="badge">Compact badge</span>
56
65
  </div>
57
66
  ```
58
67
 
59
- Works locally too:
68
+ The same class on a component is local density, not a second meaning:
60
69
 
61
- ```html
62
- <button class="btn sm">Small button</button>
63
- <span class="badge lg">Large badge</span>
70
+ ```html demo
71
+ <button class="btn sm">Compact button</button>
72
+ <span class="badge lg">Spacious badge</span>
64
73
  ```
65
74
 
66
- Components with bespoke sizing (`.avatar`, `.spinner`) define local `.sm`/`.lg` instead.
75
+ This is not "`.sm` sometimes means density and sometimes size": `.sm` always
76
+ establishes the same density tokens — only the scope changes. The density
77
+ tokens are `--gap`, `--density-space`, `--control-size`, and
78
+ `--density-compact-size`. Components consume the dimensions that make sense
79
+ for them and opt out of the rest.
80
+
81
+ Components with bespoke sizing (`.avatar`, `.spinner`) define local `.sm`/`.lg`
82
+ instead of consuming the shared tokens.
67
83
 
68
84
  ## Child semantics
69
85
 
70
86
  When using a component, child elements follow the semantic pattern of that component. For example, `.avatar` accepts `<img>`, `<picture>`, text content, an empty `.badge` for status dots, and can be a `<button>` or `<a>`.
71
87
 
72
- No child classes like `.avatar-img` are needed.
73
-
88
+ No child classes like `.avatar-img` are needed.
@@ -0,0 +1,99 @@
1
+ # Theming
2
+
3
+ Find the right lever when you want to change how a component or layout looks, without knowing in advance which custom property to use.
4
+
5
+ This is a problem-oriented index. It maps common intents to the public hooks and primitives that express them, then points to the canonical section. It is not a catalogue of every variable — the CSS source remains canonical for exact defaults and fallback chains. See the tokens page, Public surface section, for the public/internal rule.
6
+
7
+ ## I want to…
8
+
9
+ ### Split / spread / put two items apart / one left, one right
10
+
11
+ Use `.cluster` with `--cluster-justify: space-between`. There is no separate `.split` or `.spread` primitive.
12
+
13
+ → Layout · Cluster
14
+
15
+ ### Align cluster items / top-align a toolbar row
16
+
17
+ Set `--cluster-align`.
18
+
19
+ → Layout · Cluster
20
+
21
+ ### Change spacing without changing control size
22
+
23
+ Set `--gap` locally on the layout primitive. The optional `utilities-extra.css` layer ships `.gap-sm` / `.gap-md` / `.gap-lg` for row/column containers.
24
+
25
+ → Layout
26
+
27
+ ### Custom or asymmetric grid columns
28
+
29
+ Set `--grid-columns` on `.grid`. Use `--grid-min` to tune the responsive item width.
30
+
31
+ → Layout · Grid
32
+
33
+ ### Rounded card / change a card's radius
34
+
35
+ Set `--card-radius`. `--card-pad` also drives `.bleed` offsets.
36
+
37
+ → Components · Card
38
+
39
+ ### Wider or narrower modal / dialog
40
+
41
+ Set `--modal-size`.
42
+
43
+ → Components · Dialog
44
+
45
+ ### Resize a drawer or sidebar panel
46
+
47
+ Set `--drawer-size` for drawers, `--sidebar-layout-size` for sidebars.
48
+
49
+ → Components · Drawer · Layout · Sidebar layout
50
+
51
+ ### Right- or center-align form actions
52
+
53
+ Set `--form-actions-justify` (and `--form-actions-align`).
54
+
55
+ → Forms · Form actions
56
+
57
+ ### Customize a switch's size
58
+
59
+ Change `--switch-width`, `--switch-block-size`, and `--switch-knob-margin`; knob size and travel are derived from them. Prefer `.sm` / `.lg` first.
60
+
61
+ → Forms · Switch
62
+
63
+ ### Dim disabled controls
64
+
65
+ Set `--disabled-opacity`. All disabled-prone components — buttons, tabs, menu items, badges, avatars, choice cards, native inputs, and range sliders — read it for their dimmed state.
66
+
67
+ ### Loading / busy overlay on a container
68
+
69
+ Use `aria-busy="true"` with a direct last-child `.spinner`; tint the overlay with `--busy-overlay-bg`.
70
+
71
+ → Components · Card
72
+
73
+ ### Set card-rail item width in a scroll-snap row
74
+
75
+ Set `--scroll-snap-item-size`.
76
+
77
+ → Layout · Scroll snap
78
+
79
+ ### Resize an avatar or its stack
80
+
81
+ Set `--avatar-size` / `--avatar-radius`; tune overlap with `--avatar-stack-overlap` and `--avatar-stack-ring`.
82
+
83
+ → Components · Avatar
84
+
85
+ ### Make an inverted / contrasting surface
86
+
87
+ Apply `.inverted` to create one contrasting surface context. It paints a bare
88
+ block (`<header class="inverted">`, `<section class="inverted">`) with
89
+ `--surface-solid` and `--surface`; contextual content and transparent treatments
90
+ follow that foreground. Apply it directly to a component that consumes the
91
+ shared `--ui-*` contract when that component itself is the contrasting surface.
92
+
93
+ Surface-owning descendants keep their own component surface and establish a new
94
+ foreground context. Use `data-theme="dark"`, `data-theme="light"`, or a named
95
+ theme instead when an entire subtree — including forms, states, and nested
96
+ surfaces — must use another palette. More specific component states or explicit
97
+ surface variants (`.card.subtle`) still take precedence.
98
+
99
+ → Components · Card · Foundations · Tokens (theme contract)