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.
- package/CHANGELOG.md +90 -1
- package/README.md +25 -8
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1492 -931
- package/dist/actual.js +1 -3
- package/dist/actual.min.css +1 -1
- package/dist/optional.css +1048 -0
- package/dist/optional.min.css +1 -0
- package/docs/design-notes/browser-support.md +95 -6
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +101 -0
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +3 -1
- package/docs/design-notes/platform-alignment.md +492 -0
- package/docs/design-notes/references.md +8 -0
- package/docs/design-notes/widget-primitives.md +119 -0
- package/docs/navigation.json +135 -0
- package/docs/pages/components/accordion.md +80 -0
- package/docs/pages/components/alert.md +203 -0
- package/docs/pages/components/aura.md +35 -0
- package/docs/pages/components/avatar.md +95 -0
- package/docs/pages/components/badge.md +102 -0
- package/docs/pages/components/breadcrumb.md +19 -0
- package/docs/pages/components/button.md +205 -0
- package/docs/pages/components/card.md +183 -0
- package/docs/pages/components/chat.md +50 -0
- package/docs/pages/components/dialog.md +367 -0
- package/docs/pages/components/drawer.md +113 -0
- package/docs/pages/components/fab.md +118 -0
- package/docs/pages/components/key.md +33 -0
- package/docs/pages/components/meter.md +23 -0
- package/docs/pages/components/navbar.md +92 -0
- package/docs/pages/components/pagination.md +55 -0
- package/docs/pages/components/progress.md +29 -0
- package/docs/pages/components/skeleton.md +38 -0
- package/docs/pages/components/spinner.md +93 -0
- package/docs/pages/components/table.md +102 -0
- package/docs/pages/enhancements/flyout.md +391 -0
- package/docs/pages/enhancements/overview.md +36 -0
- package/docs/pages/enhancements/scrollspy.md +71 -0
- package/docs/pages/enhancements/status-bar.md +72 -0
- package/docs/pages/enhancements/tabs.md +80 -0
- package/docs/pages/enhancements/tooltip.md +130 -0
- package/docs/pages/examples/overview.md +41 -0
- package/docs/pages/forms/choice-cards.md +102 -0
- package/docs/pages/forms/controls.md +32 -0
- package/docs/pages/forms/detached-actions.md +59 -0
- package/docs/pages/forms/floating-field.md +88 -0
- package/docs/pages/forms/form-actions.md +56 -0
- package/docs/pages/forms/input-policy.md +91 -0
- package/docs/pages/forms/joined-controls.md +37 -0
- package/docs/pages/forms/labels.md +42 -0
- package/docs/pages/forms/otp.md +50 -0
- package/docs/pages/forms/overview.md +188 -0
- package/docs/pages/forms/password.md +40 -0
- package/docs/pages/forms/range.md +17 -0
- package/docs/pages/forms/states-and-sizes.md +321 -0
- package/docs/pages/forms/sticky-actions.md +187 -0
- package/docs/pages/forms/switch.md +31 -0
- package/docs/pages/forms/validation.md +241 -0
- package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
- package/docs/pages/foundations/theming.md +92 -0
- package/docs/pages/foundations/tokens.md +421 -0
- package/docs/{typography.md → pages/foundations/typography.md} +27 -9
- package/docs/pages/guides/bootstrap.md +438 -0
- package/docs/pages/guides/introduction.md +157 -0
- package/docs/pages/guides/optional-components.md +33 -0
- package/docs/pages/guides/progressive-enhancement.md +686 -0
- package/docs/pages/guides/tailwind.md +455 -0
- package/docs/pages/layout/app-shell.md +25 -0
- package/docs/pages/layout/center.md +29 -0
- package/docs/pages/layout/cluster.md +57 -0
- package/docs/pages/layout/frame.md +30 -0
- package/docs/pages/layout/grid.md +142 -0
- package/docs/pages/layout/list.md +41 -0
- package/docs/pages/layout/media.md +21 -0
- package/docs/pages/layout/overview.md +37 -0
- package/docs/pages/layout/scroll-snap.md +54 -0
- package/docs/pages/layout/sidebar-layout.md +27 -0
- package/docs/pages/layout/stack.md +18 -0
- package/docs/pages/layout/switcher.md +36 -0
- package/docs/pages/layout/topbar.md +23 -0
- package/docs/pages/patterns/actions.md +52 -0
- package/docs/pages/patterns/code-block.md +36 -0
- package/docs/pages/patterns/exclusive-accordion.md +16 -0
- package/docs/pages/patterns/hierarchical-nav.md +26 -0
- package/docs/pages/patterns/icon-input.md +25 -0
- package/docs/pages/patterns/intent-nesting.md +3 -0
- package/docs/pages/patterns/navigation-list.md +235 -0
- package/docs/pages/patterns/overview.md +58 -0
- package/docs/pages/patterns/structured-lists.md +47 -0
- package/docs/pages/utilities/accessibility.md +34 -0
- package/docs/pages/utilities/flex.md +105 -0
- package/docs/pages/utilities/links.md +18 -0
- package/docs/pages/utilities/list.md +31 -0
- package/docs/pages/utilities/overflow.md +43 -0
- package/docs/pages/utilities/overview.md +18 -0
- package/docs/pages/utilities/shape.md +25 -0
- package/docs/pages/utilities/sizing-wrapping.md +115 -0
- package/docs/pages/utilities/spacing.md +79 -0
- package/docs/pages/utilities/text.md +43 -0
- package/llms.txt +10 -4
- package/package.json +30 -16
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -9
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +162 -49
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +82 -39
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +7 -0
- package/src/css/components/button.css +82 -38
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -110
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -3
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +125 -0
- package/src/css/components/meter.css +0 -23
- package/src/css/components/modal.css +162 -7
- package/src/css/components/navbar.css +17 -18
- package/src/css/components/overline.css +0 -7
- package/src/css/components/pagination.css +1 -1
- package/src/css/components/progress.css +8 -26
- package/src/css/components/scrollspy.css +2 -2
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +10 -11
- package/src/css/components/status-bar.css +18 -28
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +20 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +16 -3
- package/src/css/forms/choice-card.css +13 -15
- package/src/css/forms/choice.css +21 -28
- package/src/css/forms/control.css +16 -26
- package/src/css/forms/custom-select.css +9 -11
- package/src/css/forms/form-actions.css +14 -3
- package/src/css/forms/form.css +7 -4
- package/src/css/forms/index.css +5 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +48 -33
- package/src/css/forms/range.css +103 -0
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -13
- package/src/css/forms/validation.css +32 -5
- package/src/css/grid.css +19 -18
- package/src/css/intents.css +2 -2
- package/src/css/layout.css +29 -1
- package/src/css/optional/aura.css +93 -0
- package/src/css/optional/chat.css +105 -0
- package/src/css/optional/fab.css +104 -0
- package/src/css/optional/floating-field.css +87 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +7 -4
- package/src/css/optional/otp.css +115 -0
- package/src/css/optional/scroll-snap.css +6 -0
- package/src/css/optional/scroller.css +19 -7
- package/src/css/optional/typography-fluid.css +5 -0
- package/src/css/optional/utilities-extra.css +17 -2
- package/src/css/prose.css +18 -8
- package/src/css/reset.css +0 -10
- package/src/css/theme.css +29 -13
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +11 -37
- package/src/css/themes/index.css +3 -3
- package/src/css/themes/{dark.css → indigo.css} +5 -4
- package/src/css/themes/neon.css +5 -5
- package/src/css/themes/square.css +0 -3
- package/src/css/tokens.css +81 -21
- package/src/css/utilities.css +8 -10
- package/src/css/variants.css +37 -65
- package/src/js/command.js +1 -3
- package/src/js/context-menu.js +72 -53
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +76 -19
- package/src/js/enhancement-loader.js +301 -0
- package/src/js/events.js +3 -0
- package/src/js/filter.js +3 -3
- package/src/js/floating.js +101 -131
- package/src/js/flyout.js +106 -96
- package/src/js/focus-group.js +116 -0
- package/src/js/focus.js +6 -1
- package/src/js/index.js +4 -2
- package/src/js/input.js +1 -1
- package/src/js/keys.js +27 -0
- package/src/js/mask.js +7 -6
- package/src/js/menu.js +89 -35
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +133 -62
- package/src/js/selectors.js +14 -31
- package/src/js/status.js +16 -14
- package/src/js/surface.js +219 -125
- package/src/js/tab.js +46 -66
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +45 -51
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -26
- package/docs/components.md +0 -860
- package/docs/design-notes/previous-work.md +0 -5
- package/docs/forms.md +0 -1265
- package/docs/javascript.md +0 -426
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -415
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -937
- package/docs/utilities.md +0 -384
- package/src/css/dialog-fallback.css +0 -56
- 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
|
-
#
|
|
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
|
|
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`
|
|
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
|
-
##
|
|
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
|
-
|
|
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">
|
|
55
|
-
<
|
|
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
|
-
|
|
68
|
+
The same class on a component is local density, not a second meaning:
|
|
60
69
|
|
|
61
|
-
```html
|
|
62
|
-
<button class="btn sm">
|
|
63
|
-
<span class="badge lg">
|
|
70
|
+
```html demo
|
|
71
|
+
<button class="btn sm">Compact button</button>
|
|
72
|
+
<span class="badge lg">Spacious badge</span>
|
|
64
73
|
```
|
|
65
74
|
|
|
66
|
-
|
|
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,92 @@
|
|
|
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 any block (`<header class="inverted">`, `<section class="inverted">`,
|
|
88
|
+
`<div class="card inverted">`) for the inverse surface treatment: it paints
|
|
89
|
+
`--surface-solid` and swaps the foreground to `--surface`, so in a dark theme the
|
|
90
|
+
surface relationship inverts instead of staying dark.
|
|
91
|
+
|
|
92
|
+
→ Components · Card · Foundations · Tokens (theme contract)
|