@pugi/cli 1.0.0-alpha.24 → 1.0.0-alpha.241
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/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +39 -0
- package/bin/pugi +19 -5
- package/models.json +1 -0
- package/package.json +20 -6
- package/skills/a11y/SKILL.md +27 -0
- package/skills/ab-test-analysis/SKILL.md +15 -0
- package/skills/ai-sdk/SKILL.md +27 -0
- package/skills/api-integration/SKILL.md +27 -0
- package/skills/architecture-review/SKILL.md +19 -0
- package/skills/brand-voice/SKILL.md +27 -0
- package/skills/ci-cd-pipeline/SKILL.md +15 -0
- package/skills/code-review/SKILL.md +27 -0
- package/skills/competitor-analysis/SKILL.md +27 -0
- package/skills/content-design/SKILL.md +35 -0
- package/skills/css-tailwind/SKILL.md +39 -0
- package/skills/css-tailwind/quirks.md +86 -0
- package/skills/db-vector/SKILL.md +27 -0
- package/skills/design-radix/SKILL.md +35 -0
- package/skills/design-system/SKILL.md +35 -0
- package/skills/detecting-data-anomalies/SKILL.md +15 -0
- package/skills/docker-deploy/SKILL.md +15 -0
- package/skills/edge-case-finder/SKILL.md +27 -0
- package/skills/fonts/SKILL.md +34 -0
- package/skills/framework-grammy/SKILL.md +27 -0
- package/skills/framework-mastra/SKILL.md +27 -0
- package/skills/framework-nestjs/SKILL.md +27 -0
- package/skills/framework-prisma/SKILL.md +27 -0
- package/skills/funnel-optimization/SKILL.md +19 -0
- package/skills/ha-prototype-web/LICENSE-APACHE-2.0 +201 -0
- package/skills/ha-prototype-web/NOTICE.md +85 -0
- package/skills/ha-prototype-web/SKILL.md +25 -0
- package/skills/icons/SKILL.md +34 -0
- package/skills/landing-templates/SKILL.md +35 -0
- package/skills/market-research/SKILL.md +27 -0
- package/skills/metrics-dashboard/SKILL.md +19 -0
- package/skills/monitoring-setup/SKILL.md +15 -0
- package/skills/motion/SKILL.md +27 -0
- package/skills/nestjs-api/SKILL.md +38 -0
- package/skills/nestjs-api/quirks.md +36 -0
- package/skills/nextjs-page/SKILL.md +27 -0
- package/skills/od-craft/LICENSE-APACHE-2.0 +201 -0
- package/skills/od-craft/NOTICE.md +85 -0
- package/skills/od-craft/SKILL.md +58 -0
- package/skills/od-craft/references/README.md +108 -0
- package/skills/od-craft/references/accessibility-baseline.md +203 -0
- package/skills/od-craft/references/animation-discipline.md +156 -0
- package/skills/od-craft/references/anti-ai-slop.md +86 -0
- package/skills/od-craft/references/color.md +90 -0
- package/skills/od-craft/references/form-validation.md +223 -0
- package/skills/od-craft/references/laws-of-ux.md +298 -0
- package/skills/od-craft/references/rtl-and-bidi.md +188 -0
- package/skills/od-craft/references/state-coverage.md +136 -0
- package/skills/od-craft/references/typography-hierarchy-editorial.md +184 -0
- package/skills/od-craft/references/typography-hierarchy.md +167 -0
- package/skills/od-craft/references/typography.md +124 -0
- package/skills/od-reference-design-contract/LICENSE-APACHE-2.0 +201 -0
- package/skills/od-reference-design-contract/NOTICE.md +85 -0
- package/skills/od-reference-design-contract/SKILL.md +131 -0
- package/skills/od-reference-design-contract/references/checklist.md +26 -0
- package/skills/od-web-prototype/LICENSE-APACHE-2.0 +201 -0
- package/skills/od-web-prototype/NOTICE.md +85 -0
- package/skills/od-web-prototype/SKILL.md +102 -0
- package/skills/od-web-prototype/assets/template.html +348 -0
- package/skills/od-web-prototype/references/checklist.md +47 -0
- package/skills/od-web-prototype/references/layouts.md +247 -0
- package/skills/orchestrating-multi-agent-systems/SKILL.md +19 -0
- package/skills/preprocessing-data/SKILL.md +15 -0
- package/skills/project-memory-keeper/SKILL.md +19 -0
- package/skills/public-apis/SKILL.md +27 -0
- package/skills/pugi-patterns/SKILL.md +19 -0
- package/skills/react-component/SKILL.md +27 -0
- package/skills/sprint-planning/SKILL.md +19 -0
- package/skills/stack-selection/SKILL.md +15 -0
- package/skills/standup-summary/SKILL.md +15 -0
- package/skills/task-decomposition/SKILL.md +19 -0
- package/skills/team-routing/SKILL.md +15 -0
- package/skills/tech-survey/SKILL.md +15 -0
- package/skills/test-plan/SKILL.md +15 -0
- package/skills/typescript-refactor/SKILL.md +15 -0
- package/skills/ui-components/SKILL.md +35 -0
- package/skills/ux-patterns/SKILL.md +35 -0
- package/skills/web-platform/SKILL.md +27 -0
- package/skills/web-scrape/SKILL.md +27 -0
- package/skills/web-search/SKILL.md +15 -0
- package/skills/webapp-testing/SKILL.md +37 -0
- package/skills/webapp-testing/quirks.md +26 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<!-- Vendored from nexu-io/open-design@78d5637cb49d (craft/form-validation.md), Apache-2.0, Copyright 2026 Open Design contributors. Modified by Pugi: this header line added; the text below is otherwise verbatim. Paths it names (apps/daemon/src/lint-artifact.ts, pnpm lint:craft, od.craft.requires) are upstream's, not Pugi's: the Pugi port of the linter is packages/opencode/src/session/design-lint.ts. See NOTICE.md in the skill root. -->
|
|
2
|
+
|
|
3
|
+
# Form validation craft rules
|
|
4
|
+
|
|
5
|
+
Universal rules for form validation lifecycle, error wiring beyond the
|
|
6
|
+
accessibility baseline, and the schema-as-contract layer that makes
|
|
7
|
+
the same validation work on the server and the client. The active
|
|
8
|
+
`DESIGN.md` decides how the field looks; this file decides *when* the
|
|
9
|
+
field tells the user it's wrong, *how* the error reaches assistive
|
|
10
|
+
tech, and *where* the rule lives.
|
|
11
|
+
|
|
12
|
+
> Grounded in primary sources: WHATWG HTML Living Standard
|
|
13
|
+
> (Constraint Validation section under "Form control infrastructure"),
|
|
14
|
+
> CSS Selectors L4 (`:user-invalid`), WCAG 2.2 SC 3.3.x
|
|
15
|
+
> Understanding pages, ARIA APG forms patterns, Standard Schema spec
|
|
16
|
+
> (`@standard-schema/spec`), Baymard 2024 inline-validation research
|
|
17
|
+
> checkout-UX benchmark, WebAIM Million 2026 forms findings.
|
|
18
|
+
|
|
19
|
+
## Prior art and scope
|
|
20
|
+
|
|
21
|
+
Existing OSS forms guidance for AI agents pins to one layer at a time
|
|
22
|
+
— `szilu/ux-designer-skill` is UX-opinion grade with no spec anchors,
|
|
23
|
+
`Community-Access/accessibility-agents/forms-specialist` is
|
|
24
|
+
WCAG-anchored but AT-only and doesn't reach the platform validity
|
|
25
|
+
layer or the schema contract. This file connects the four layers a
|
|
26
|
+
real form spans: **WHATWG Constraint Validation as the platform
|
|
27
|
+
floor, validation timing as a state machine on the input, WCAG 3.3.x
|
|
28
|
+
as the announcement and recovery contract, schema as the cross-stack
|
|
29
|
+
truth.** A11y wiring lives next door in `accessibility-baseline.md`
|
|
30
|
+
(label + describedby + invalid + `role="alert"` for inline errors);
|
|
31
|
+
this file picks up where that ends.
|
|
32
|
+
|
|
33
|
+
## The input state machine
|
|
34
|
+
|
|
35
|
+
Every input passes through these states. The names trace back to RHF /
|
|
36
|
+
Formik vocabulary on web; the *shape* applies regardless of stack.
|
|
37
|
+
Drive error chrome off the state, not off raw `:invalid` or
|
|
38
|
+
focus/blur booleans.
|
|
39
|
+
|
|
40
|
+
| State | Meaning | UI |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `pristine` | User has not interacted | No error chrome, no green check |
|
|
43
|
+
| `dirty` | User has typed but not committed (still focused) | No error chrome yet |
|
|
44
|
+
| `touched` | User has blurred at least once after editing | Field-level constraint runs |
|
|
45
|
+
| `invalid-after-touched` | Constraint failed after blur | Show error, link via `aria-describedby` |
|
|
46
|
+
| `invalid-after-submit` | Submit attempted, field still invalid | Same plus focus management to summary or first invalid field |
|
|
47
|
+
| `recovering` | User editing an already-invalid field | Re-validate on `input`, not on next blur |
|
|
48
|
+
| `submitting` | Action in flight | Disable submit, announce status via a polite live region |
|
|
49
|
+
| `server-error` | Server returned an error for this field | Use server's message text; treat as `invalid-after-submit` |
|
|
50
|
+
|
|
51
|
+
Decision rule that collapses validation-timing debates: errors appear
|
|
52
|
+
on transition into `invalid-after-touched`, clear on transition out
|
|
53
|
+
of any invalid state, and never appear from `pristine` or plain
|
|
54
|
+
`dirty`. CSS `:user-invalid` matches the `invalid-after-touched` /
|
|
55
|
+
`invalid-after-submit` states for free.
|
|
56
|
+
|
|
57
|
+
## Validation timing
|
|
58
|
+
|
|
59
|
+
Baymard's checkout-UX benchmark (2024-01-09 inline-validation article):
|
|
60
|
+
**31% of sites have no inline validation, and most of the rest fire
|
|
61
|
+
too early.** The participant quote that anchors the research: *"Why
|
|
62
|
+
are you telling me my email address is wrong, I haven't had a chance
|
|
63
|
+
to fill it all out yet?"* Premature firing is the loudest UX failure
|
|
64
|
+
in this space.
|
|
65
|
+
|
|
66
|
+
The four rules:
|
|
67
|
+
|
|
68
|
+
1. **First blur after edit** runs the field-level constraint. Not on focus, not on first keystroke, not on every keystroke.
|
|
69
|
+
2. **Once a field is invalid, switch to `input`-event re-validation** so the error clears the moment input becomes valid. Don't make the user blur again to dismiss it.
|
|
70
|
+
3. **On submit**, run the schema parse. Move focus to the error summary at the top of the form (a heading-led container with `tabindex="-1"`, no `role="alert"` — see the wiring section), or to the first invalid field if no summary exists. Don't move focus on every keystroke.
|
|
71
|
+
4. **Async checks** split into two paths. *Background preflight* (uniqueness while typing, address lookup) debounces 250-500 ms, announces via a polite live region, and never gates typing or keeps the submit button disabled indefinitely. *Authoritative server validation on submit* is different: the submit path must await the server's response and surface field errors from it, since the server is the truth. Don't conflate the two — the rule is "don't let a slow background check freeze the form," not "don't ever wait for the server."
|
|
72
|
+
|
|
73
|
+
CSS gets you most of timing rule 1 for free: style off `:user-invalid`
|
|
74
|
+
not `:invalid`. The `:user-invalid` selector is Baseline Newly
|
|
75
|
+
available 2023 (Chrome 119, Firefox 88, Safari 16.5; Firefox shipped
|
|
76
|
+
the prefixed `:-moz-ui-invalid` years earlier and unprefixed in v88)
|
|
77
|
+
and matches only after the user has either submitted the form or
|
|
78
|
+
blurred the field with bad input.
|
|
79
|
+
|
|
80
|
+
## Constraint Validation API as the platform floor
|
|
81
|
+
|
|
82
|
+
Native HTML constraints are not an alternative to JS validation; they
|
|
83
|
+
are the substrate the rest of the layers run on. They survive JS
|
|
84
|
+
failure, they integrate with autofill, and they are what
|
|
85
|
+
`reportValidity()` and screen-reader native announcements key off.
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<input type="email" name="email" required>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use these declaratively for every field that has them: `required`,
|
|
92
|
+
`type` (email, url, number, tel), `pattern`, `min`/`max`,
|
|
93
|
+
`minlength`/`maxlength`, `step`. Cross-field rules and dynamic
|
|
94
|
+
constraints go through `setCustomValidity()` on both `input` and
|
|
95
|
+
`change` events — autofill flows historically fired one without the
|
|
96
|
+
other on some browsers, so listening on both is the cheap defense.
|
|
97
|
+
|
|
98
|
+
Rules of the API:
|
|
99
|
+
|
|
100
|
+
- **Empty string clears `setCustomValidity`.** Not `null`, not no-arg.
|
|
101
|
+
- **`form.requestSubmit()` honors validation; `form.submit()` skips it.** Never call the second.
|
|
102
|
+
- `disabled` controls are barred from validation and not submitted. The HTML spec says `readonly` is also barred, but `readonly` only has defined behavior on `<input>` and `<textarea>` — implementations diverge for `<select readonly>` and `<button readonly>` ([whatwg/html#11841](https://github.com/whatwg/html/issues/11841)). For non-input controls where the value must still submit, the safe pattern is `disabled` plus a same-named hidden `<input>` carrying the value, or rendering the non-editable text alongside a hidden `<input>`. `aria-readonly` alone is not enough — a `<select>` or custom widget tagged `aria-readonly="true"` is still interactable, so the visible control can drift while the hidden input ships a stale or different value. If you do use `aria-readonly`, you must also block the interaction or keep both values in sync.
|
|
103
|
+
- `inputmode` is a virtual-keyboard hint, **not** validation. `<input type="text" inputmode="numeric" pattern="[0-9]*">` is the Baymard-recommended shape for ZIPs / OTPs / card numbers; `pattern="[0-9]*"` is the historical iOS-Safari trigger for the numeric keypad on top of `inputmode`. `type="number"` adds spinners, strips leading zeros, applies locale-decimal handling, and varies field width across browsers — wrong for any of these.
|
|
104
|
+
|
|
105
|
+
## Error wiring beyond the baseline
|
|
106
|
+
|
|
107
|
+
The default error pattern in `accessibility-baseline.md` (`<label>` +
|
|
108
|
+
`aria-describedby` + `aria-invalid` + `role="alert"`) covers WCAG
|
|
109
|
+
3.3.1 / 3.3.2. Three additions matter for real forms:
|
|
110
|
+
|
|
111
|
+
**Adaptive error messages.** Baymard 2023: 98% of audited sites use
|
|
112
|
+
generic catch-all errors ("Provide a valid phone number") rather than
|
|
113
|
+
the specific subrule that fired ("Phone number is too short"). The
|
|
114
|
+
back end already knows the subrule; surfacing it cuts re-submit
|
|
115
|
+
attempts. Ship 4-7 distinct messages per high-traffic complex field
|
|
116
|
+
(email, phone, card, postal code). The scale of the problem matches
|
|
117
|
+
WebAIM Million 2026: missing form-input labels appear on **51% of
|
|
118
|
+
the top 1M home pages** (input-level rate **33.1%** of all 6.9M
|
|
119
|
+
inputs sampled) — labels and error messages are the categories
|
|
120
|
+
trending sideways or worse year-over-year while overall a11y errors
|
|
121
|
+
drop.
|
|
122
|
+
|
|
123
|
+
**Error summary at the top, on submit only.** Long forms benefit from
|
|
124
|
+
a summary list of in-page anchor links to invalid fields, focused on
|
|
125
|
+
submit:
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<div id="form-errors" tabindex="-1">
|
|
129
|
+
<h2>2 problems</h2>
|
|
130
|
+
<ul>
|
|
131
|
+
<li><a href="#email">Email is required</a></li>
|
|
132
|
+
<li><a href="#dob">Date of birth must be in the past</a></li>
|
|
133
|
+
</ul>
|
|
134
|
+
</div>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The container is heading-led with `tabindex="-1"` so JS can move
|
|
138
|
+
focus to it on submit (render the summary into the DOM, *then*
|
|
139
|
+
`.focus()` it; a `hidden` element can't take focus). It does **not**
|
|
140
|
+
carry `role="alert"` because combining a moved-focus target with an
|
|
141
|
+
alert role causes double-announcement: alert fires on insertion,
|
|
142
|
+
focus fires the accessible name + role. Reserve `role="alert"` for
|
|
143
|
+
inline per-field errors that appear without focus moving — that's
|
|
144
|
+
the canonical baseline pattern in `accessibility-baseline.md`. WCAG
|
|
145
|
+
technique G139 covers the summary; not required, high-value for long
|
|
146
|
+
forms.
|
|
147
|
+
|
|
148
|
+
**Preserve user input on error.** Baymard 2024: 34% of audited
|
|
149
|
+
checkouts wipe the credit-card field when an unrelated error reloads
|
|
150
|
+
the page. Direct cause of abandonment. Either field-level-validate
|
|
151
|
+
non-sensitive fields first, or split the payment step. PCI-wise,
|
|
152
|
+
persisting card values across an error reload is fine via tokenized
|
|
153
|
+
hosted iframes; never store raw PAN in your own session.
|
|
154
|
+
|
|
155
|
+
## Schema as the cross-stack contract
|
|
156
|
+
|
|
157
|
+
Validation expressed once, consumed everywhere. The 2026 React shape
|
|
158
|
+
— `useActionState` + Server Actions + Conform (which added Standard
|
|
159
|
+
Schema support during the v1.x line) + a Zod 4 / Valibot / ArkType
|
|
160
|
+
schema — is the most-cited concrete instance: one schema,
|
|
161
|
+
server-authoritative, validator hot-swappable via the `~standard`
|
|
162
|
+
interface. The same architecture works in TanStack Form, oRPC, Hono
|
|
163
|
+
validator middleware, Nuxt UForm, and any other consumer that reads
|
|
164
|
+
`~standard`.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const Signup = z.object({
|
|
168
|
+
email: z.email(), // Zod 4 top-level form
|
|
169
|
+
password: z.string().min(12),
|
|
170
|
+
});
|
|
171
|
+
// Same schema parses on the Server Action and on the Conform client.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Three rules that survive across stacks:
|
|
175
|
+
|
|
176
|
+
- **Server is the truth, client is the optimization.** Same schema runs in both. Returning `{ errors }` from the action (not throwing) is what feeds back into `useActionState`'s state slot — throwing routes to the Error Boundary and loses the form data.
|
|
177
|
+
- **Standard Schema is the contract, not Zod.** A form library that ships per-validator resolver shims (`zodResolver`, `valibotResolver`, etc.) is yesterday's stack. Accept any `~standard`-compliant validator.
|
|
178
|
+
- **`novalidate` on `<form>` does not mean "skip validation".** It means "let the form library repaint errors instead of the browser's bubble." But the trade-off is real: a literal server-rendered `<form novalidate>` disables the browser's submit-blocking and native validation UI **even when JS is unavailable**, which loses the no-JS constraint-validation floor. Pick one of two patterns. **A:** render `<form>` without `novalidate` server-side and have the form library set `form.noValidate = true` after hydration — the no-JS user keeps the browser's native validation, the JS user gets the library's chrome. **B:** ship `novalidate` from the start only when the submit path reaches server validation without JS (Server Action, classic POST handler) so the no-JS user is still protected by the server. Either way, keep `required` / `pattern` / `type` attributes — they survive JS failure and integrate with autofill. (HTML attribute is lowercase `novalidate`; the IDL property on the form element is `noValidate`.)
|
|
179
|
+
|
|
180
|
+
## WCAG 3.3.x beyond Error Identification
|
|
181
|
+
|
|
182
|
+
`accessibility-baseline.md` covers 3.3.1 (Error ID), 3.3.2 (Labels),
|
|
183
|
+
and 3.3.7 (Redundant Entry). The rest of 3.3 binds harder on
|
|
184
|
+
transactional forms:
|
|
185
|
+
|
|
186
|
+
- **3.3.3 Error Suggestion (AA):** when the fix is determinable, suggest it in text. Adaptive errors satisfy this. "Date must be MM/DD/YYYY. You entered 5-3-26. Did you mean 05/03/2026?"
|
|
187
|
+
- **3.3.4 Error Prevention — Legal, Financial, Data (AA):** for any submission with legal / financial / data-modifying consequence, provide one of: reversibility, server-side check + correction step, or a confirm-summary screen before commit.
|
|
188
|
+
- **3.3.8 Accessible Authentication (AA, WCAG 2.2):** auth steps must not require a cognitive function test (remember a password, transcribe a code, recognize images) without an alternative. CAPTCHAs are the canonical thing this SC restricts; only object-recognition or personal-content variants escape via the narrow exceptions, and not all CAPTCHAs do. Practical floor: never block paste on password / verification-code fields, support password managers, accept verification-code paste from a clipboard.
|
|
189
|
+
- **3.3.9 Accessible Authentication, No Exception (AAA):** removes even the object-recognition / personal-content exceptions. Aspirational; flag if a project commits to it.
|
|
190
|
+
|
|
191
|
+
## Native mobile parity
|
|
192
|
+
|
|
193
|
+
Web validation primitives don't auto-translate. Each platform has its
|
|
194
|
+
own validity machinery and its own AT path. Skills that emit web-only
|
|
195
|
+
artifacts can skim this section; it's the entry point for skills
|
|
196
|
+
that ship to mobile (mobile-onboarding, mobile-app, etc.).
|
|
197
|
+
|
|
198
|
+
| Platform | Validity primitive | Error announcement |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| iOS UIKit | Hand-rolled state on the view controller; `UITextField` doesn't carry a built-in invalid flag | `UIAccessibility.post(notification: .announcement, argument: "Email is required")` |
|
|
201
|
+
| iOS SwiftUI | `TextField` + `@State`-driven validation; no built-in `Form`-level validity API as of iOS 18 | `AccessibilityNotification.Announcement("…").post()` (iOS 17+) |
|
|
202
|
+
| Android Compose | `OutlinedTextField(isError = true, supportingText = { Text("…") })` — `isError` wires the AT error semantic for you | `Modifier.semantics { liveRegion = LiveRegionMode.Polite }` on the supporting-text node, or `LocalView.current.announceForAccessibility(message)` |
|
|
203
|
+
| Flutter | `TextFormField(validator: (v) => …)` inside a `Form`, `formKey.currentState!.validate()` | `SemanticsService.announce(message, Directionality.of(context))` — never hardcode `TextDirection.ltr`; pull ambient direction so Arabic / Hebrew / Persian flows announce correctly |
|
|
204
|
+
| React Native | Hand-rolled per field; no platform validity flag | `accessibilityLiveRegion="polite"` on the error node (Android) + `AccessibilityInfo.announceForAccessibility(...)` (iOS) |
|
|
205
|
+
|
|
206
|
+
Two parity rules that catch most AI-generated mobile forms:
|
|
207
|
+
|
|
208
|
+
- **Use the platform's native validation flag — and pair it with the platform's error-message semantic where one exists.** On Compose, `isError = true` is the right boolean state for the field visuals and AT error-state cue, but it does *not* carry the localized error message. Pair it with `Modifier.semantics { error(message) }` so accessibility services get the actual text — the same string you render in `supportingText`. The trap is duplication: a hand-rolled `Modifier.semantics { error("Email is required") }` next to a different supporting-text string desyncs. Source `error()` from the same state field as `supportingText` so they stay in sync.
|
|
209
|
+
- **Don't mirror web ARIA into mobile semantics.** `aria-describedby` on a SwiftUI `TextField` is a no-op. Use the platform announcement primitive (`AccessibilityNotification.Announcement` on SwiftUI, `UIAccessibility.post` on UIKit, `announceForAccessibility` on Android, `SemanticsService.announce` on Flutter) for state-change events that need to reach the screen reader.
|
|
210
|
+
|
|
211
|
+
## Common mistakes (lint these)
|
|
212
|
+
|
|
213
|
+
- Styling off `input:invalid` instead of `input:user-invalid`. Red borders on page load is the loudest "this validation was added without testing" signal.
|
|
214
|
+
- Validating on every keystroke. Hostile; fires before the user has finished typing.
|
|
215
|
+
- Generic catch-all error messages ("Invalid input") when the back end already knows which subrule fired. Baymard 2023 found 98% of audited sites do this — the most-cited preventable validation failure in their corpus.
|
|
216
|
+
- Throwing from a Server Action on validation failure. Routes to the Error Boundary and loses the form data. Return `{ errors }` instead.
|
|
217
|
+
- `role="alert"` on the error-summary container that focus moves to. Double-announces. Reserve `role="alert"` for inline per-field errors that appear without focus moving.
|
|
218
|
+
- `aria-busy="true"` on the submit button while submitting. `aria-busy` is for stale containers; for buttons use `disabled` plus a polite live-region status message.
|
|
219
|
+
- Email-confirm fields ("retype your email"). 3.3.7 redundant entry — exceptions are essential / security / no-longer-valid, not "we want to catch typos." Allow paste and validate the single field instead.
|
|
220
|
+
- Per-validator resolver shims (`zodResolver`, `valibotResolver`) on a 2026 stack. Accept Standard Schema's `~standard` interface and the validator becomes swappable.
|
|
221
|
+
- Wiping the credit-card field when an unrelated field errors. Baymard 2024: 34% of audited e-commerce sites; direct abandonment cause.
|
|
222
|
+
- `setCustomValidity(null)` to clear an error. Pass empty string; `null` does not clear.
|
|
223
|
+
- Mirroring web ARIA onto SwiftUI / Compose / Flutter. Each platform has its own validity API; `aria-*` attributes don't reach the mobile AT path.
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<!-- Vendored from nexu-io/open-design@78d5637cb49d (craft/laws-of-ux.md), Apache-2.0, Copyright 2026 Open Design contributors. Modified by Pugi: this header line added; the text below is otherwise verbatim. Paths it names (apps/daemon/src/lint-artifact.ts, pnpm lint:craft, od.craft.requires) are upstream's, not Pugi's: the Pugi port of the linter is packages/opencode/src/session/design-lint.ts. See NOTICE.md in the skill root. -->
|
|
2
|
+
|
|
3
|
+
# Laws of UX craft rules
|
|
4
|
+
|
|
5
|
+
Universal cognitive, perceptual, and behavioral heuristics that decide
|
|
6
|
+
what a UI composes — how many pricing tiers fit on a screen, where a
|
|
7
|
+
primary action anchors in scanning order, when a progress indicator
|
|
8
|
+
earns its place, why a settings list needs grouping. The active
|
|
9
|
+
`DESIGN.md` decides brand visual language; the existing craft files
|
|
10
|
+
decide rendering rules (color, typography, motion, states, ARIA, RTL,
|
|
11
|
+
forms); this file decides composition rules grounded in named research.
|
|
12
|
+
|
|
13
|
+
> Distilled from primary sources: Hick (1952) + Hyman (1953), Miller
|
|
14
|
+
> (1956) for chunking / `7±2` channel capacity, Cowan (2001) for the
|
|
15
|
+
> modern ~4 working-memory bound, Fitts (1954), Wertheimer (1923) for
|
|
16
|
+
> proximity / similarity / Prägnanz, Palmer (1992) for Common Region,
|
|
17
|
+
> Palmer & Rock (1994) for Uniform Connectedness, Kahneman /
|
|
18
|
+
> Fredrickson / Schreiber / Redelmeier (1993) for Peak-End, Zeigarnik
|
|
19
|
+
> (1927), Csíkszentmihályi (1975), Hull (1932), von Restorff (1933),
|
|
20
|
+
> Broadbent (1958), Sweller (1988), Postel (RFC 760, 1980), Carroll &
|
|
21
|
+
> Rosson (1987), Tversky & Kahneman (1974) for Anchoring, Kurosu &
|
|
22
|
+
> Kashimura (1995), Iyengar & Lepper (2000), Toffler (1970), Pareto
|
|
23
|
+
> (c.1906) / Juran (*Quality Control Handbook*, 1951), Ebbinghaus
|
|
24
|
+
> (1885), Ockham (14th c.), Tesler at Apple (1980s), Nielsen (2000),
|
|
25
|
+
> Norman *POET* (1988), Parkinson (1955).
|
|
26
|
+
|
|
27
|
+
## Prior art and scope
|
|
28
|
+
|
|
29
|
+
Existing public catalogs of UX heuristics (Yablonski's lawsofux.com,
|
|
30
|
+
NN/g's 10 usability heuristics, Material 3 motion + interaction
|
|
31
|
+
guidance, Apple HIG, Baymard Institute checkout research) inventory the
|
|
32
|
+
laws but rarely tie each one to a concrete code-gen directive. This
|
|
33
|
+
file does the translation: every entry ends with one actionable move
|
|
34
|
+
for an HTML / Tailwind / React-emitting agent. Sibling craft files
|
|
35
|
+
(`accessibility-baseline.md`, `state-coverage.md`, `typography.md`,
|
|
36
|
+
`anti-ai-slop.md`, `color.md`, `animation-discipline.md`,
|
|
37
|
+
`form-validation.md`) own the auto-checked rules; this file names the
|
|
38
|
+
underlying law and surfaces the folklore. Out of scope: Weber-Fechner
|
|
39
|
+
psychophysics and Signal Detection Theory — both apply to UI but the
|
|
40
|
+
prompt-emission directives are too narrow to earn a slot here. Add
|
|
41
|
+
later if a skill needs them.
|
|
42
|
+
|
|
43
|
+
The rules below are guidance, not auto-checked. Reviewers and the agent
|
|
44
|
+
apply them; the linter does not. Where a law has a sibling rule already
|
|
45
|
+
auto-checked elsewhere — touch-target floor in
|
|
46
|
+
`accessibility-baseline.md`, the 300 ms / 2 s / 10 s / 30 s / 60 s
|
|
47
|
+
loading thresholds in `state-coverage.md`, ALL CAPS letter-spacing in
|
|
48
|
+
`typography.md`, the indigo / gradient / emoji-icon list in
|
|
49
|
+
`anti-ai-slop.md` — the entry below cross-references rather than
|
|
50
|
+
duplicates.
|
|
51
|
+
|
|
52
|
+
## Perception and visual grouping
|
|
53
|
+
|
|
54
|
+
Five Gestalt laws plus three attention-and-recognition laws govern how
|
|
55
|
+
the eye groups elements before the brain reads them.
|
|
56
|
+
|
|
57
|
+
- **Law of Proximity** (Wertheimer, 1923). Objects near each other read
|
|
58
|
+
as a group. Cheapest grouping signal — cheaper than borders or shared
|
|
59
|
+
color. Apply variable vertical rhythm: 8–12 px within a group,
|
|
60
|
+
32–48 px between groups. Uniform spacing reads as nothing being
|
|
61
|
+
grouped.
|
|
62
|
+
- **Law of Similarity** (Wertheimer, 1923). Visually similar elements
|
|
63
|
+
read as a group. Equivalent affordances must share treatment — every
|
|
64
|
+
list row identical class set, every secondary button identical, every
|
|
65
|
+
destructive action identical. Visible deviation is reserved for the
|
|
66
|
+
one item meant to draw attention (the recommended pricing tier, the
|
|
67
|
+
selected nav item).
|
|
68
|
+
- **Law of Common Region** (Palmer, 1992). A shared bounded area binds
|
|
69
|
+
enclosed elements. Use enclosure when proximity is not enough — and
|
|
70
|
+
reserve it. Concrete numbers: padding ≥16 px inside the region,
|
|
71
|
+
distinct surface (border + tinted background, or card chrome at
|
|
72
|
+
≥1 px hairline). A page where every section is bordered destroys the
|
|
73
|
+
signal.
|
|
74
|
+
- **Law of Prägnanz / Good Figure** (Wertheimer, 1923). The eye
|
|
75
|
+
resolves complex layouts into the simplest underlying form. Designs
|
|
76
|
+
that align with a clear underlying grid (12-column, F-pattern,
|
|
77
|
+
4-quadrant) feel inevitable; ornate breaks that add nothing semantic
|
|
78
|
+
feel arbitrary.
|
|
79
|
+
- **Law of Uniform Connectedness** (Palmer & Rock, 1994). The
|
|
80
|
+
strongest grouping signal in the Gestalt hierarchy: connected lines,
|
|
81
|
+
shared toolbars, or bracketing containers tie items together more
|
|
82
|
+
strongly than proximity or similarity. Use for wizard steps,
|
|
83
|
+
comparison sets, and explicit navigation flows.
|
|
84
|
+
- **Selective Attention** (Broadbent, *Perception and Communication*,
|
|
85
|
+
1958). Cognitive bandwidth is finite. Users filter aggressively and
|
|
86
|
+
ignore anything that looks irrelevant to their goal — banner blindness
|
|
87
|
+
comes from this. Reserve the strongest visual contrast for the single
|
|
88
|
+
goal-relevant action; let supporting content recede in weight.
|
|
89
|
+
- **Von Restorff Effect** (von Restorff, 1933). The item that differs
|
|
90
|
+
from a uniform field is the one most likely to be remembered. Make
|
|
91
|
+
the recommended pricing tier, the active nav item, the warning state
|
|
92
|
+
visually distinct. Pair contrast with a non-color signal (icon, text
|
|
93
|
+
label, position) — `accessibility-baseline.md` rules out color-alone
|
|
94
|
+
signaling.
|
|
95
|
+
- **Aesthetic-Usability Effect** (Kurosu & Kashimura, Hitachi Design
|
|
96
|
+
Center, 1995). Visual polish biases perceived usability. Refined
|
|
97
|
+
typography, generous whitespace, and a calm palette earn the benefit
|
|
98
|
+
of the doubt for minor friction. Never substitutes for measurable
|
|
99
|
+
usability or for `state-coverage.md`'s required-states rule.
|
|
100
|
+
|
|
101
|
+
## Decision-making
|
|
102
|
+
|
|
103
|
+
Six laws govern how fast and how well users decide when an interface
|
|
104
|
+
offers a choice.
|
|
105
|
+
|
|
106
|
+
- **Hick's Law** (Hick, 1952; Hyman, 1953 replication). Decision time
|
|
107
|
+
grows roughly log(n+1) with the number of equivalent options. Cap any
|
|
108
|
+
single decision-screen to 3–5 visible primary options; collapse the
|
|
109
|
+
rest behind a "More" / progressive disclosure pattern; visually
|
|
110
|
+
distinguish the recommended choice. Aggressive truncation that hides
|
|
111
|
+
the path forward is the opposite failure mode — surface the full
|
|
112
|
+
option set, just don't render every option at the same visual weight.
|
|
113
|
+
- **Choice Overload** (Iyengar & Lepper, *Journal of Personality and
|
|
114
|
+
Social Psychology*, 2000; framing dates to Toffler, *Future Shock*,
|
|
115
|
+
1970). Too many roughly-equivalent options stall or abandon the
|
|
116
|
+
decision. Pricing pages: 3–4 tiers, exactly one marked recommended.
|
|
117
|
+
Product grids: 6–9 hero cards above the fold. Settings panels: ≤5
|
|
118
|
+
named groups. Never emit a flat wall of equivalents.
|
|
119
|
+
- **Anchoring** (Tversky & Kahneman, *Science* 185:1124–1131, 1974).
|
|
120
|
+
The first number a user sees re-weights every subsequent number.
|
|
121
|
+
Place the recommended pricing tier where it anchors the comparison;
|
|
122
|
+
render yearly-billing savings as concrete dollar deltas, not just
|
|
123
|
+
percentage badges; pre-select the safer default in radio groups.
|
|
124
|
+
Visual weight matches intended decision weight.
|
|
125
|
+
- **Pareto Principle / 80-20** (Pareto, c.1906; Juran, *Quality Control
|
|
126
|
+
Handbook*, 1951 — popularized the management-application framing). A
|
|
127
|
+
small share of features drives most of the value. Identify the 2–3 actions
|
|
128
|
+
that drive the dominant journey for the target persona; emphasize
|
|
129
|
+
those visually; demote the long tail to overflow menus, footer
|
|
130
|
+
surfaces, or settings.
|
|
131
|
+
- **Tesler's Law / Conservation of Complexity** (Tesler, Apple, 1980s).
|
|
132
|
+
Every product has an irreducible amount of complexity. The design
|
|
133
|
+
choice is *where* it lives — engineering team, interface, user — not
|
|
134
|
+
whether to eliminate it. When complexity reaches the user, surface
|
|
135
|
+
contextual guidance (tooltips, smart defaults, inline empty-state
|
|
136
|
+
coaching, progressive disclosure) at the exact step where it
|
|
137
|
+
surfaces. Hiding it is not the same as removing it.
|
|
138
|
+
- **Occam's Razor** (Ockham, 14th c.). Among options that explain the
|
|
139
|
+
data equally well, prefer the one with the fewest assumptions. Specify
|
|
140
|
+
a minimal element inventory; forbid decorative chrome that doesn't
|
|
141
|
+
serve a stated user task. The law constrains assumptions, not feature
|
|
142
|
+
count — a "minimum viable" framing misreads it.
|
|
143
|
+
|
|
144
|
+
## Memory and learning
|
|
145
|
+
|
|
146
|
+
Five laws cover how working memory handles information density and
|
|
147
|
+
what the user retains afterward.
|
|
148
|
+
|
|
149
|
+
- **Miller's Law and Chunking** (Miller, *The Magical Number Seven,
|
|
150
|
+
Plus or Minus Two*, *Psychological Review*, 1956 — channel capacity /
|
|
151
|
+
`7±2` and chunking; Cowan, *Behavioral and Brain Sciences* 24:1, 2001
|
|
152
|
+
for the modern ~4-item working-memory bound). Working memory holds
|
|
153
|
+
about four items reliably and up to seven for short-term recall. Each
|
|
154
|
+
slot can hold a *larger familiar unit*, constrained by the user's
|
|
155
|
+
domain knowledge — chunking does not let you pack arbitrary content
|
|
156
|
+
into a single slot. Often misread as a rule about menu length;
|
|
157
|
+
Miller's paper is about chunks. Group related fields with clear
|
|
158
|
+
section headings, dividers, or card containers. A settings page with
|
|
159
|
+
sections "Account / Notifications / Privacy / Billing / Danger zone"
|
|
160
|
+
beats one flat list of 30 toggles.
|
|
161
|
+
- **Working Memory** (Baddeley & Hitch, 1974; lineage to Atkinson &
|
|
162
|
+
Shiffrin, 1968). Items decay in seconds without rehearsal. Recognition
|
|
163
|
+
beats recall: persisting prior context across screens, marking visited
|
|
164
|
+
elements, and surfacing comparison views beats forcing the user to
|
|
165
|
+
memorize. On dashboards specifically: sticky filter chips, last-N
|
|
166
|
+
selections persisted, breadcrumbs that include applied filters.
|
|
167
|
+
- **Serial Position Effect** (Ebbinghaus, *Über das Gedächtnis*, 1885).
|
|
168
|
+
Recall favors the extremes — primacy at the start, recency at the
|
|
169
|
+
end — while middle items fade. Anchor the most important nav items at
|
|
170
|
+
the leftmost and rightmost positions of a horizontal menu; cluster
|
|
171
|
+
utilities in the middle.
|
|
172
|
+
- **Peak-End Rule** (Kahneman, Fredrickson, Schreiber, Redelmeier,
|
|
173
|
+
*Psychological Science*, 1993). Memory of an experience is dominated
|
|
174
|
+
by the emotional peak and the ending, not the average. Stage a
|
|
175
|
+
high-effort celebratory success state; let intermediate steps stay
|
|
176
|
+
calm. Mediocre middles matter less than a strong close. The peak
|
|
177
|
+
belongs at the *end* of a flow, not as arbitrary mid-flow motion —
|
|
178
|
+
`animation-discipline.md` rejects motion that performs (rather than
|
|
179
|
+
confirms) a state change.
|
|
180
|
+
- **Zeigarnik Effect** (Zeigarnik, *Über das Behalten erledigter und
|
|
181
|
+
unerledigter Handlungen*, 1927). Uncompleted tasks create cognitive
|
|
182
|
+
tension that pulls the user back. Visible progress ("3 of 5 steps",
|
|
183
|
+
greyed-out next sections) converts that tension into completion
|
|
184
|
+
pressure. Reserve for genuinely beneficial flows like onboarding;
|
|
185
|
+
applying the same lever to streaks, daily-quest counters, or notification-
|
|
186
|
+
reduction nags is a dark pattern.
|
|
187
|
+
|
|
188
|
+
## Interaction and motor
|
|
189
|
+
|
|
190
|
+
Five laws cover how fast and how accurately users can act on the UI.
|
|
191
|
+
|
|
192
|
+
- **Fitts's Law** (Fitts, *Journal of Experimental Psychology*, 1954).
|
|
193
|
+
Time to acquire a target depends on its distance and size — bigger
|
|
194
|
+
and closer is faster. Spacing between adjacent hit zones matters as
|
|
195
|
+
much as size. Pair with `accessibility-baseline.md`'s 24 × 24 CSS px
|
|
196
|
+
AA touch-target floor; on mobile, place high-frequency controls in
|
|
197
|
+
the natural thumb arc.
|
|
198
|
+
- **Doherty Threshold** (Doherty & Thadani, *IBM Systems Journal*,
|
|
199
|
+
1982). Sub-second feedback keeps users in flow; latency above ~1 s
|
|
200
|
+
breaks attention. The implementable directive lives in
|
|
201
|
+
`state-coverage.md`'s loading-threshold table (no indicator under
|
|
202
|
+
300 ms; skeleton 300 ms – 2 s; labelled spinner 2 – 10 s; determinate
|
|
203
|
+
bar with cancel 10 – 60 s; stop and offer error/retry past 60 s).
|
|
204
|
+
This entry exists to name the underlying law and flag its folklore
|
|
205
|
+
(the 400 ms number doesn't appear in the 1982 paper — see
|
|
206
|
+
`animation-discipline.md` for the 400 ms folklore trace).
|
|
207
|
+
- **Flow** (Csíkszentmihályi, *Beyond Boredom and Anxiety*, 1975). Flow
|
|
208
|
+
sits in the balance between challenge and skill — too hard breeds
|
|
209
|
+
frustration, too easy breeds boredom. Continuous feedback and a clear
|
|
210
|
+
sense of control keep the user inside the state. System friction and
|
|
211
|
+
latency are the fastest ways to break it.
|
|
212
|
+
- **Goal-Gradient Effect** (Hull, *Psychological Review*, 1932; Kivetz,
|
|
213
|
+
Urminsky, Zheng, 2006 for the punch-card replication). Motivation to
|
|
214
|
+
finish rises as the goal gets closer. Multi-step flows render with a
|
|
215
|
+
prominent progress indicator that reflects *real* endowed progress —
|
|
216
|
+
show completed prerequisites when they truly exist (saved profile,
|
|
217
|
+
imported team, prior survey answer). When no real prerequisite
|
|
218
|
+
exists, render the current step honestly as `1 of N` with the
|
|
219
|
+
empty/current-step state clearly marked. Hull's hypothesis is
|
|
220
|
+
descriptive; treating it as license for fabricated progress, streak
|
|
221
|
+
dark patterns, or loyalty-program quota inflation is a misread.
|
|
222
|
+
- **Postel's Law / Robustness Principle** (Postel, RFC 760, 1980). "Be
|
|
223
|
+
liberal in what you accept, conservative in what you send." Take
|
|
224
|
+
input in whatever shape users naturally give it (phone numbers with
|
|
225
|
+
or without dashes, dates in mixed formats, percentages with or
|
|
226
|
+
without `%`); normalize internally to a canonical form; emit one
|
|
227
|
+
consistent format on output. The error-timing and ARIA-wiring half
|
|
228
|
+
lives in `form-validation.md`; the input-tolerance half is the
|
|
229
|
+
directive above. RFC 9413 (Thomson, IAB, 2023; built on the earlier
|
|
230
|
+
`draft-iab-protocol-maintenance`) retracts the maxim for protocol
|
|
231
|
+
design citing security surface; the UX-input application stands.
|
|
232
|
+
|
|
233
|
+
## Behavior and expectation
|
|
234
|
+
|
|
235
|
+
Five laws cover what users predict and how that prediction interacts
|
|
236
|
+
with the rendered surface.
|
|
237
|
+
|
|
238
|
+
- **Jakob's Law** (Nielsen, Nielsen Norman Group, 2000). Users spend
|
|
239
|
+
most of their time on other sites and expect yours to work the same.
|
|
240
|
+
Reuse category convention — nav placement, cart icon, settings gear,
|
|
241
|
+
primary CTA in the upper right of a SaaS landing — so the user spends
|
|
242
|
+
zero cycles relearning interaction grammar. Novelty must earn its
|
|
243
|
+
keep against the convention's ROI; "innovate everywhere" is the
|
|
244
|
+
opposite failure mode.
|
|
245
|
+
- **Mental Model** (Craik, *The Nature of Explanation*, 1943; Norman,
|
|
246
|
+
*POET / The Psychology of Everyday Things*, 1988). Every user
|
|
247
|
+
arrives with a prior built from competitor products and the physical
|
|
248
|
+
world. When the prediction holds, the product feels intuitive; when
|
|
249
|
+
it breaks, friction shows up as confusion, not curiosity. When the
|
|
250
|
+
brief names a reference product, anchor explicitly — capture the
|
|
251
|
+
reference in the prompt and the agent inherits a transferable
|
|
252
|
+
interaction grammar.
|
|
253
|
+
- **Paradox of the Active User** (Carroll & Rosson, in *Interfacing
|
|
254
|
+
Thought: Cognitive Aspects of Human-Computer Interaction*, MIT Press,
|
|
255
|
+
1987, pp. 80–111). Users skip the manual and start using the
|
|
256
|
+
software immediately, even when reading it would speed them up. Bake
|
|
257
|
+
guidance into the surface itself — empty-state coaching, inline
|
|
258
|
+
tooltips, contextual hints — at the action point.
|
|
259
|
+
- **Parkinson's Law** (Parkinson, *The Economist*, 1955). Work expands
|
|
260
|
+
to fill the time allotted to it. Loose interfaces let users dawdle;
|
|
261
|
+
cut friction and pre-fill what you can — autofill, smart defaults,
|
|
262
|
+
saved state — so a checkout finishes faster than the user expected.
|
|
263
|
+
Beating anticipated duration becomes the felt win.
|
|
264
|
+
- **Cognitive Load** (Sweller, *Cognitive Science*, 1988). Total
|
|
265
|
+
mental effort splits into intrinsic (the task's inherent difficulty)
|
|
266
|
+
and extraneous (poor layout, jargon, inconsistent patterns, visual
|
|
267
|
+
noise). Designers can't reduce intrinsic load; they own extraneous
|
|
268
|
+
fully. The visual-restraint directives (single accent in `color.md`,
|
|
269
|
+
three-weight typography rhythm in `typography.md`, P0 anti-default
|
|
270
|
+
list in `anti-ai-slop.md`) already constrain extraneous load; this
|
|
271
|
+
entry exists to name the cognitive cost the sibling rules reduce.
|
|
272
|
+
|
|
273
|
+
## Common mistakes (lint these)
|
|
274
|
+
|
|
275
|
+
Folklore corrections that don't survive a primary-source check, in the
|
|
276
|
+
same vein as the busts already documented in `animation-discipline.md`
|
|
277
|
+
and `accessibility-baseline.md`. The first three are attribution
|
|
278
|
+
corrections (year / venue / institution) the body entries already
|
|
279
|
+
applied — restated here so a reviewer reading just this section sees
|
|
280
|
+
them. The remainder are folklore not addressed in the body.
|
|
281
|
+
|
|
282
|
+
- "Anchoring effect = Tversky & Kahneman 1972." Wrong year. The
|
|
283
|
+
*Science* paper introducing the anchoring framing is 1974; the 1972
|
|
284
|
+
paper is "Subjective probability: A judgment of representativeness"
|
|
285
|
+
(different work).
|
|
286
|
+
- "Tesler's Law was developed at Xerox PARC." Tesler left PARC for
|
|
287
|
+
Apple in 1980; the Conservation-of-Complexity formulation traces to
|
|
288
|
+
his Apple years.
|
|
289
|
+
- "Paradox of the Active User was a CACM article." It's a chapter in
|
|
290
|
+
Carroll's *Interfacing Thought* (MIT Press, 1987), not CACM.
|
|
291
|
+
- "Selective Attention = solved by red dots and badges." Repeated
|
|
292
|
+
attention-grabbers train banner blindness. Reserve the strongest
|
|
293
|
+
contrast for one goal-relevant action per surface.
|
|
294
|
+
- "Fitts's Law alone is enough for touch targets." Fitts gives the
|
|
295
|
+
speed-accuracy tradeoff. WCAG 2.2 SC 2.5.8 sets the AA floor at
|
|
296
|
+
24 × 24 CSS px (`accessibility-baseline.md`); iOS HIG suggests
|
|
297
|
+
44 × 44 pt; Material 3 suggests 48 × 48 dp. Fitts plus the
|
|
298
|
+
platform floor — never just Fitts.
|