@ctxr/skill-frontend-excellence 0.1.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.
- package/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/SKILL.md +227 -0
- package/package.json +63 -0
- package/references/accessibility.md +396 -0
- package/references/audit-workflow.md +390 -0
- package/references/components.md +247 -0
- package/references/data-viz.md +457 -0
- package/references/defects.md +152 -0
- package/references/design.md +513 -0
- package/references/forms.md +485 -0
- package/references/lighthouse.md +242 -0
- package/references/motion.md +642 -0
- package/references/performance.md +416 -0
- package/references/pre-launch.md +342 -0
- package/references/responsive.md +519 -0
- package/references/seo.md +422 -0
- package/references/ui-ux.md +565 -0
- package/scripts/check-no-dashes.mjs +90 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
# Forms and Feedback
|
|
2
|
+
|
|
3
|
+
Framework-agnostic patterns for forms that are easy to fill, easy to recover from, and accessible. Forms are where most products earn or lose users.
|
|
4
|
+
|
|
5
|
+
## Form Anatomy
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
[ Optional: page heading ]
|
|
9
|
+
[ Optional: short context: why we're asking ]
|
|
10
|
+
|
|
11
|
+
[ Field group ]
|
|
12
|
+
[ Label (always visible) ] [ Optional indicator: required, optional ]
|
|
13
|
+
[ Input ]
|
|
14
|
+
[ Helper text (always when complex) ]
|
|
15
|
+
[ Error message (when invalid) ]
|
|
16
|
+
|
|
17
|
+
[ ... more field groups ]
|
|
18
|
+
|
|
19
|
+
[ Submit button ] [ Cancel / secondary action ]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Labels
|
|
23
|
+
|
|
24
|
+
### Always visible
|
|
25
|
+
|
|
26
|
+
The label must be visible at all times, not just when the field is empty.
|
|
27
|
+
|
|
28
|
+
- **Wrap or associate**: `<label for="email">Email</label> <input id="email" />` or `<label>Email <input /></label>`.
|
|
29
|
+
- **Float labels** (label transitions to small position when focused/filled) are acceptable visually but the label must remain associated with the input.
|
|
30
|
+
- **Placeholder is not a label.** The placeholder disappears when typing, and most placeholders fail contrast.
|
|
31
|
+
|
|
32
|
+
### Position
|
|
33
|
+
|
|
34
|
+
- **Top-aligned** is the default. Easiest to scan.
|
|
35
|
+
- **Left-aligned** for compact dense forms (settings).
|
|
36
|
+
- **Right-aligned** rarely; harder to scan.
|
|
37
|
+
|
|
38
|
+
### Required vs optional
|
|
39
|
+
|
|
40
|
+
- Mark the smaller set, not the larger. If most fields are required, mark optional ones.
|
|
41
|
+
- Use both visual (asterisk) AND text (`required` somewhere visible).
|
|
42
|
+
- `aria-required="true"` (or use the native `required` attribute, which implies it).
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<label for="email">
|
|
46
|
+
Email <span aria-hidden="true" class="required">*</span>
|
|
47
|
+
</label>
|
|
48
|
+
<input id="email" type="email" required aria-required="true" />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Input Types
|
|
52
|
+
|
|
53
|
+
Use the right `type` and `inputmode` for the data:
|
|
54
|
+
|
|
55
|
+
| Data | type | inputmode | autocomplete |
|
|
56
|
+
|------|------|-----------|--------------|
|
|
57
|
+
| Email | email | email | email |
|
|
58
|
+
| Phone | tel | tel | tel |
|
|
59
|
+
| URL | url | url | url |
|
|
60
|
+
| Numeric ID, code | text | numeric | one-time-code (for OTP) |
|
|
61
|
+
| Money | text | decimal | (use a formatted text input) |
|
|
62
|
+
| Date | date | (auto) | bday or other |
|
|
63
|
+
| Search | search | search | (none) |
|
|
64
|
+
| Password | password | (auto) | new-password / current-password |
|
|
65
|
+
| Name | text | text | given-name / family-name / name |
|
|
66
|
+
| Address | text | text | street-address, address-line1, postal-code, country |
|
|
67
|
+
| Credit card | text | numeric | cc-number / cc-exp / cc-csc / cc-name |
|
|
68
|
+
|
|
69
|
+
### `autocomplete` is critical
|
|
70
|
+
|
|
71
|
+
Browsers and password managers use `autocomplete` to fill fields automatically. Without it, users have to type or copy-paste, which they hate.
|
|
72
|
+
|
|
73
|
+
WHATWG `autocomplete` reference (commonly used):
|
|
74
|
+
|
|
75
|
+
- `name`, `given-name`, `additional-name`, `family-name`
|
|
76
|
+
- `email`, `tel`, `tel-national`
|
|
77
|
+
- `street-address`, `address-line1`, `address-line2`, `address-level1` (state), `address-level2` (city), `postal-code`, `country`
|
|
78
|
+
- `cc-name`, `cc-number`, `cc-exp`, `cc-csc`, `cc-type`
|
|
79
|
+
- `username`, `current-password`, `new-password`, `one-time-code`
|
|
80
|
+
- `organization`, `organization-title`
|
|
81
|
+
- `bday`, `bday-day`, `bday-month`, `bday-year`
|
|
82
|
+
|
|
83
|
+
Multi-form pages: prefix with the form purpose (`shipping`, `billing`):
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<input autocomplete="shipping street-address" />
|
|
87
|
+
<input autocomplete="billing street-address" />
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Native vs custom controls
|
|
91
|
+
|
|
92
|
+
Native first:
|
|
93
|
+
|
|
94
|
+
- `<input type="date">` is fine on mobile; custom date pickers are appropriate for desktop ranges.
|
|
95
|
+
- `<input type="checkbox">` and `<input type="radio">` style with `:checked` selectors plus pseudo-elements; use ARIA pattern only when truly necessary.
|
|
96
|
+
- `<input type="range">` for sliders.
|
|
97
|
+
- `<select>` for short lists; combobox for long or async.
|
|
98
|
+
- `<input type="file">` for uploads; style the trigger with a `<label>` to match design.
|
|
99
|
+
|
|
100
|
+
Custom controls require:
|
|
101
|
+
|
|
102
|
+
- Full keyboard support (arrow keys, Enter/Space, Esc).
|
|
103
|
+
- ARIA role and state.
|
|
104
|
+
- Focus management.
|
|
105
|
+
- Touch and mouse parity.
|
|
106
|
+
|
|
107
|
+
Don't reinvent the date picker unless you have a year of work to invest.
|
|
108
|
+
|
|
109
|
+
## Validation
|
|
110
|
+
|
|
111
|
+
### When to validate
|
|
112
|
+
|
|
113
|
+
- **Don't validate on every keystroke.** It's distracting and often wrong (you haven't typed enough).
|
|
114
|
+
- **Validate on blur** (when the user leaves the field). Sufficient time has passed; the user has indicated they're done.
|
|
115
|
+
- **Re-validate on submit** as a safety net; the server validates regardless.
|
|
116
|
+
- **For password strength meters**, update on keystroke since the user expects live feedback.
|
|
117
|
+
- **For confirm-password**, validate when the second field loses focus.
|
|
118
|
+
|
|
119
|
+
### Error placement
|
|
120
|
+
|
|
121
|
+
- **Inline, below the field.** The error is about THIS field; show it HERE.
|
|
122
|
+
- **`aria-invalid="true"` on the input.** Programmatic error state.
|
|
123
|
+
- **`aria-describedby`** on the input pointing to the error message id.
|
|
124
|
+
- **`role="alert"`** on the error so screen readers announce it.
|
|
125
|
+
|
|
126
|
+
```html
|
|
127
|
+
<label for="email">Email</label>
|
|
128
|
+
<input
|
|
129
|
+
id="email"
|
|
130
|
+
type="email"
|
|
131
|
+
required
|
|
132
|
+
aria-invalid="true"
|
|
133
|
+
aria-describedby="email-error"
|
|
134
|
+
/>
|
|
135
|
+
<p id="email-error" role="alert" class="error">
|
|
136
|
+
Enter a valid email address (example: name@example.com)
|
|
137
|
+
</p>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Error message content
|
|
141
|
+
|
|
142
|
+
- **Cause AND fix.** "Email is required" beats "Validation error". "Enter a valid email address" beats "Invalid".
|
|
143
|
+
- **Plain language.** Don't expose backend codes ("ERR_INVALID_INPUT"); translate.
|
|
144
|
+
- **Don't blame the user.** "Couldn't save your draft" not "You haven't filled everything in".
|
|
145
|
+
- **Stay positive when possible.** "Try a longer password" beats "Password too short".
|
|
146
|
+
|
|
147
|
+
### Multi-error submission
|
|
148
|
+
|
|
149
|
+
If the user submits with multiple errors:
|
|
150
|
+
|
|
151
|
+
1. Move focus to the first invalid field (or to a top-of-form summary).
|
|
152
|
+
2. Show a summary at the top with anchor links to each error:
|
|
153
|
+
|
|
154
|
+
```html
|
|
155
|
+
<div role="alert" aria-live="assertive" class="error-summary">
|
|
156
|
+
<h2>3 issues to fix:</h2>
|
|
157
|
+
<ul>
|
|
158
|
+
<li><a href="#email">Enter a valid email</a></li>
|
|
159
|
+
<li><a href="#password">Password must be at least 8 characters</a></li>
|
|
160
|
+
<li><a href="#terms">Accept the terms to continue</a></li>
|
|
161
|
+
</ul>
|
|
162
|
+
</div>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
3. Each invalid field also shows its inline error.
|
|
166
|
+
|
|
167
|
+
### Server-side errors
|
|
168
|
+
|
|
169
|
+
After submit, server may reject with errors not caught client-side (email taken, payment failed). Treat them like any other inline error:
|
|
170
|
+
|
|
171
|
+
- Show inline near the relevant field if known.
|
|
172
|
+
- Show a top-of-form alert if the error doesn't map to a field (network failure).
|
|
173
|
+
- Provide a recovery action (retry, contact support).
|
|
174
|
+
- Preserve user input. Never make them retype.
|
|
175
|
+
|
|
176
|
+
## Helper Text
|
|
177
|
+
|
|
178
|
+
Persistent text below the field, before any error, that explains:
|
|
179
|
+
|
|
180
|
+
- Format expectations ("3 to 30 characters, letters and numbers")
|
|
181
|
+
- Why we're asking ("Used for password reset only; never shown publicly")
|
|
182
|
+
- Examples ("e.g., +1 555 123 4567")
|
|
183
|
+
|
|
184
|
+
Place between input and error. When error shows, error replaces or appears below helper.
|
|
185
|
+
|
|
186
|
+
## Submit Button
|
|
187
|
+
|
|
188
|
+
### Location
|
|
189
|
+
|
|
190
|
+
- Primary submit at the bottom of the form, aligned to the form's primary text direction (left in LTR forms with fields stacked left, right in form-aligned-right cases).
|
|
191
|
+
- For mobile, full-width is acceptable for primary submit.
|
|
192
|
+
|
|
193
|
+
### Label
|
|
194
|
+
|
|
195
|
+
- **Specific verb-noun.** "Create account" beats "Submit". "Save draft" beats "Save".
|
|
196
|
+
- **Match the action.** "Send invoice" if that's what happens.
|
|
197
|
+
|
|
198
|
+
### Loading state
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<button type="submit" disabled aria-busy="true">
|
|
202
|
+
<span class="spinner" aria-hidden="true"></span>
|
|
203
|
+
Creating account
|
|
204
|
+
</button>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- Disable while submitting (prevents double-submit).
|
|
208
|
+
- Show spinner inside the button.
|
|
209
|
+
- Maintain button width to prevent layout shift.
|
|
210
|
+
- Optionally update the label ("Creating account" instead of "Create account").
|
|
211
|
+
|
|
212
|
+
### Cancel
|
|
213
|
+
|
|
214
|
+
- Provide a secondary action when the user might back out (cancel, "back").
|
|
215
|
+
- Confirm if there are unsaved changes:
|
|
216
|
+
|
|
217
|
+
```html
|
|
218
|
+
<button type="button" onclick="confirmExit()">Cancel</button>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
function confirmExit() {
|
|
223
|
+
if (formIsDirty()) {
|
|
224
|
+
if (confirm('Discard your changes?')) {
|
|
225
|
+
// navigate away
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
// navigate away
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Multi-Step Forms
|
|
234
|
+
|
|
235
|
+
For long forms, break into steps.
|
|
236
|
+
|
|
237
|
+
### Stepper
|
|
238
|
+
|
|
239
|
+
Show progress and current step:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
[Step 1: Account] -> [Step 2: Profile] -> [Step 3: Payment]
|
|
243
|
+
Done In progress Upcoming
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- Current step visually distinct.
|
|
247
|
+
- Completed steps clickable (allow back navigation).
|
|
248
|
+
- Upcoming steps not clickable.
|
|
249
|
+
|
|
250
|
+
### Save state
|
|
251
|
+
|
|
252
|
+
- Auto-save draft at each step transition.
|
|
253
|
+
- Restore on return.
|
|
254
|
+
- Allow exit without losing data.
|
|
255
|
+
|
|
256
|
+
### Validation per step
|
|
257
|
+
|
|
258
|
+
- Validate the current step on "Next".
|
|
259
|
+
- Don't validate steps the user hasn't reached.
|
|
260
|
+
- Show validation errors before advancing.
|
|
261
|
+
|
|
262
|
+
## Long Forms
|
|
263
|
+
|
|
264
|
+
For very long forms (> 10 fields):
|
|
265
|
+
|
|
266
|
+
- Group fields with `<fieldset>` + `<legend>`:
|
|
267
|
+
|
|
268
|
+
```html
|
|
269
|
+
<fieldset>
|
|
270
|
+
<legend>Shipping address</legend>
|
|
271
|
+
<!-- fields -->
|
|
272
|
+
</fieldset>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
- Use visual section headings with horizontal lines or background variation.
|
|
276
|
+
- Auto-save drafts every 30-60s or on blur of significant fields.
|
|
277
|
+
- Show a "Saved 5s ago" microcopy.
|
|
278
|
+
|
|
279
|
+
### Autosave UX
|
|
280
|
+
|
|
281
|
+
- Don't interrupt the user's typing with the save indicator.
|
|
282
|
+
- Show in a fixed corner ("Saving...", "Saved", "Failed to save - retry?").
|
|
283
|
+
- Use `aria-live="polite"` so screen readers announce status changes without interrupting.
|
|
284
|
+
|
|
285
|
+
## Confirmation and Destructive Actions
|
|
286
|
+
|
|
287
|
+
### Confirm before destructive
|
|
288
|
+
|
|
289
|
+
For "Delete account", "Cancel subscription", "Discard draft": require an explicit confirmation.
|
|
290
|
+
|
|
291
|
+
- Modal with clear title ("Delete account?").
|
|
292
|
+
- Body text states consequences ("This permanently deletes your account and all data. This cannot be undone.").
|
|
293
|
+
- Two buttons: "Cancel" (primary) and "Delete account" (destructive secondary, danger color).
|
|
294
|
+
- For very destructive actions, require typing the resource name to confirm: "Type your account email to confirm".
|
|
295
|
+
|
|
296
|
+
### Don't confirm trivial actions
|
|
297
|
+
|
|
298
|
+
- Don't confirm "Save" (the action is non-destructive).
|
|
299
|
+
- Don't confirm "Send message" unless the user requested confirm-before-send.
|
|
300
|
+
- Don't confirm form resets if "Cancel" already exists.
|
|
301
|
+
|
|
302
|
+
### Undo
|
|
303
|
+
|
|
304
|
+
For undoable destructive actions, prefer undo over confirm:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
[Toast: "Item deleted. Undo (5s)"]
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Undo is faster, less interruptive, and works with screen readers. Use confirm for actions that genuinely cannot be undone (account deletion, payment).
|
|
311
|
+
|
|
312
|
+
## File Upload
|
|
313
|
+
|
|
314
|
+
- Show drop zone (visible on hover/dragover).
|
|
315
|
+
- Click-to-browse via styled `<label for="file-input">`.
|
|
316
|
+
- Show thumbnail/preview of selected files.
|
|
317
|
+
- Show progress bar during upload.
|
|
318
|
+
- Allow cancel during upload.
|
|
319
|
+
- Show error per file (size, type, network).
|
|
320
|
+
- Accept multiple files where appropriate.
|
|
321
|
+
|
|
322
|
+
```html
|
|
323
|
+
<label for="resume" class="dropzone">
|
|
324
|
+
<input id="resume" type="file" accept=".pdf,.doc,.docx" hidden />
|
|
325
|
+
Drag and drop your resume, or click to browse.
|
|
326
|
+
PDF, DOC, or DOCX, up to 5 MB.
|
|
327
|
+
</label>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Hide the native input, style the label as the drop zone.
|
|
331
|
+
|
|
332
|
+
## Search Forms
|
|
333
|
+
|
|
334
|
+
- Single field with magnifier icon.
|
|
335
|
+
- Submit on Enter (native `<form>` behavior).
|
|
336
|
+
- Optionally submit on type with debounce (300ms).
|
|
337
|
+
- Show recent searches when empty.
|
|
338
|
+
- Show suggestions as the user types.
|
|
339
|
+
- Show clear-input button when populated.
|
|
340
|
+
- Show search results in real-time or after submit.
|
|
341
|
+
|
|
342
|
+
```html
|
|
343
|
+
<form role="search" action="/search">
|
|
344
|
+
<label for="q" class="sr-only">Search</label>
|
|
345
|
+
<input id="q" name="q" type="search" autocomplete="off" />
|
|
346
|
+
<button type="submit">Search</button>
|
|
347
|
+
</form>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`role="search"` on the form is a landmark for screen readers.
|
|
351
|
+
|
|
352
|
+
## Login and Sign-Up
|
|
353
|
+
|
|
354
|
+
### Login
|
|
355
|
+
|
|
356
|
+
```html
|
|
357
|
+
<form>
|
|
358
|
+
<label for="username">Email</label>
|
|
359
|
+
<input id="username" name="username" type="email" autocomplete="username" required />
|
|
360
|
+
|
|
361
|
+
<label for="current-password">Password</label>
|
|
362
|
+
<input id="current-password" name="password" type="password" autocomplete="current-password" required />
|
|
363
|
+
|
|
364
|
+
<button type="submit">Sign in</button>
|
|
365
|
+
|
|
366
|
+
<a href="/forgot">Forgot password?</a>
|
|
367
|
+
<a href="/signup">Create account</a>
|
|
368
|
+
</form>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
- Email/username must use `autocomplete="username"` (counterintuitive, but the spec).
|
|
372
|
+
- Password must use `autocomplete="current-password"`.
|
|
373
|
+
- Always use a `<form>` (not just floating fields), so password managers detect it.
|
|
374
|
+
|
|
375
|
+
### Sign-up
|
|
376
|
+
|
|
377
|
+
- New password: `autocomplete="new-password"`.
|
|
378
|
+
- Show password strength inline.
|
|
379
|
+
- Show "Show / hide" toggle for the password field.
|
|
380
|
+
- After submit, send to verification or onboarding, not back to a login screen.
|
|
381
|
+
|
|
382
|
+
### Password reset
|
|
383
|
+
|
|
384
|
+
- Email field with `autocomplete="username"`.
|
|
385
|
+
- Submit reveals "Check your email" message regardless of whether the email exists (security best practice).
|
|
386
|
+
- The reset link page uses `autocomplete="new-password"`.
|
|
387
|
+
|
|
388
|
+
### One-time codes
|
|
389
|
+
|
|
390
|
+
- `autocomplete="one-time-code"` so iOS suggests codes from SMS.
|
|
391
|
+
- `inputmode="numeric"` for numeric codes.
|
|
392
|
+
- `pattern="\d*"` for native validation on numeric codes.
|
|
393
|
+
- Auto-submit when the expected number of digits is entered.
|
|
394
|
+
|
|
395
|
+
## Payment Forms
|
|
396
|
+
|
|
397
|
+
- Card number: `autocomplete="cc-number"`, `inputmode="numeric"`, `pattern` for digits.
|
|
398
|
+
- Expiry: `autocomplete="cc-exp"`, `inputmode="numeric"`.
|
|
399
|
+
- CVC: `autocomplete="cc-csc"`, `inputmode="numeric"`, `maxlength="4"`.
|
|
400
|
+
- Cardholder name: `autocomplete="cc-name"`.
|
|
401
|
+
- Use Stripe Elements / Apple Pay / Google Pay for production. Never collect raw card data.
|
|
402
|
+
- Address autofill: `autocomplete="postal-code"` etc.
|
|
403
|
+
|
|
404
|
+
## Mobile-Specific
|
|
405
|
+
|
|
406
|
+
### Keyboard
|
|
407
|
+
|
|
408
|
+
- Set the right `inputmode` and `type` so the right mobile keyboard appears.
|
|
409
|
+
- For numeric codes, `inputmode="numeric"` shows the number pad on iOS.
|
|
410
|
+
- For dates, `<input type="date">` shows the native date picker on most platforms.
|
|
411
|
+
|
|
412
|
+
### Avoid auto-zoom
|
|
413
|
+
|
|
414
|
+
iOS auto-zooms when an input has font-size below 16px. Set inputs to >= 16px to prevent this.
|
|
415
|
+
|
|
416
|
+
### Touch targets
|
|
417
|
+
|
|
418
|
+
Inputs at least 44px tall (`height: 44px` or sufficient padding). Same for buttons.
|
|
419
|
+
|
|
420
|
+
### Sticky submit
|
|
421
|
+
|
|
422
|
+
For long mobile forms, consider a sticky submit bar at the bottom (with safe-area padding):
|
|
423
|
+
|
|
424
|
+
```css
|
|
425
|
+
.sticky-submit {
|
|
426
|
+
position: sticky;
|
|
427
|
+
bottom: 0;
|
|
428
|
+
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Form Accessibility
|
|
433
|
+
|
|
434
|
+
- Every input has a programmatic label.
|
|
435
|
+
- Required fields marked.
|
|
436
|
+
- Error messages associated via `aria-describedby`.
|
|
437
|
+
- Errors announced via `role="alert"` or `aria-live`.
|
|
438
|
+
- Focus moves to first invalid field on submit error.
|
|
439
|
+
- Group with `<fieldset>` + `<legend>`.
|
|
440
|
+
- Use `<form>` so password managers and SR landmarks detect it.
|
|
441
|
+
|
|
442
|
+
## Common Form Mistakes
|
|
443
|
+
|
|
444
|
+
- Placeholder as label.
|
|
445
|
+
- No `autocomplete` attributes.
|
|
446
|
+
- Placeholders that look like real values ("name@example.com") confused with filled state.
|
|
447
|
+
- Validating on every keystroke.
|
|
448
|
+
- Errors only at the top, no inline indication.
|
|
449
|
+
- Generic errors ("Invalid", "Error").
|
|
450
|
+
- "Required" indicator on all fields when most are required.
|
|
451
|
+
- Submit button labeled "Submit".
|
|
452
|
+
- Disabled submit when fields are empty (instead, validate on submit attempt and show errors).
|
|
453
|
+
- No loading state on submit; user clicks twice and creates duplicates.
|
|
454
|
+
- No autosave on long forms.
|
|
455
|
+
- Fields without `type` (default `type="text"` shows the wrong keyboard on mobile).
|
|
456
|
+
- Asking for unnecessary data ("Why does signup need my phone?").
|
|
457
|
+
- Multi-line text in a single-line input.
|
|
458
|
+
- Cancel button styled as primary, submit as secondary.
|
|
459
|
+
|
|
460
|
+
## Self-Healing for Forms
|
|
461
|
+
|
|
462
|
+
Before declaring work complete:
|
|
463
|
+
|
|
464
|
+
- [ ] Every input has a visible, programmatic label
|
|
465
|
+
- [ ] Right `type` and `inputmode` for the data
|
|
466
|
+
- [ ] `autocomplete` set per WHATWG spec
|
|
467
|
+
- [ ] Required fields marked visually + `aria-required`
|
|
468
|
+
- [ ] Errors inline, near the field, with cause + fix
|
|
469
|
+
- [ ] Errors announced via `role="alert"` or `aria-live`
|
|
470
|
+
- [ ] On submit error, focus moves to first invalid field
|
|
471
|
+
- [ ] Submit button has loading state, disabled during submission
|
|
472
|
+
- [ ] Submit button label is specific (verb-noun)
|
|
473
|
+
- [ ] Helper text for complex fields
|
|
474
|
+
- [ ] Long forms autosave; "Saved" indicator visible
|
|
475
|
+
- [ ] Multi-step forms show progress and allow back navigation
|
|
476
|
+
- [ ] Destructive actions confirm or provide undo
|
|
477
|
+
- [ ] Mobile: 16px+ font size, 44px+ touch targets
|
|
478
|
+
- [ ] `<form>` element used; password manager detects fields
|
|
479
|
+
- [ ] Tested with keyboard only, screen reader, and password manager
|
|
480
|
+
|
|
481
|
+
## See Also
|
|
482
|
+
|
|
483
|
+
- [accessibility.md](accessibility.md) for input accessibility
|
|
484
|
+
- [ui-ux.md](ui-ux.md) for state design (loading, error, success)
|
|
485
|
+
- [motion.md](motion.md) for transitions on validation feedback
|