96 lines
4.8 KiB
Markdown
96 lines
4.8 KiB
Markdown
# The finish pass
|
||
|
||
What separates "it works" from "it looks designed". Go through this per screen, not per file.
|
||
|
||
## 1. Every interactive element has all its states
|
||
|
||
For each button, link, card, input, filter chip:
|
||
|
||
| State | Must be | Common miss |
|
||
| --- | --- | --- |
|
||
| default | on the token scale | — |
|
||
| `:hover` | inside `@media (hover: hover)` | sticks after tap on touch |
|
||
| `:focus-visible` | visible ring, 3:1 contrast | removed via `outline: none` |
|
||
| `:active` | perceptible press | missing entirely |
|
||
| `:disabled` | dimmed **and** `cursor: not-allowed`, still 4.5:1 if it carries text | unreadable grey |
|
||
| loading | spinner + `aria-busy`, size unchanged | layout jumps |
|
||
|
||
The element must not resize between states — animate `box-shadow`, `background`, `transform`, never
|
||
`padding`, `border-width` or `font-size`.
|
||
|
||
## 2. Vertical rhythm and spacing
|
||
|
||
- All spacing from `--ac-space-*`. A `margin: 13px` anywhere is a defect.
|
||
- Space belongs to **one** side consistently (block-end margins, or `gap` — pick one per container).
|
||
Prefer `gap` in flex/grid; it doesn't collapse and doesn't need `:last-child` cleanups.
|
||
- Related things sit closer than unrelated things. If the label is as far from its input as from the
|
||
previous field, the form reads as noise.
|
||
- Cards in a grid must be equal height (`align-items: stretch` + the price pinned with `margin-top: auto`).
|
||
|
||
## 3. Typography
|
||
|
||
- One scale (`--ac-text-*`), max 3 weights, max 2 sizes per card.
|
||
- Headings are structural (`h1`→`h2`→`h3`); size comes from a class, never from picking `h4` because
|
||
it looks right.
|
||
- `max-width: 65ch` on paragraphs of real prose.
|
||
- Numbers in tables: `font-variant-numeric: tabular-nums` and right-aligned so columns line up.
|
||
- Long product names truncate predictably: `text-overflow: ellipsis` with `title` attribute, or
|
||
`-webkit-line-clamp: 2` with a fixed `min-height` so cards don't stagger.
|
||
|
||
## 4. Colour
|
||
|
||
- Semantic, not decorative: success/danger/warning always the same hue for the same meaning.
|
||
- Status never colour-only — icon or text alongside (colour blindness, and grayscale printing).
|
||
- Max ~2 accent colours per screen. Everything else is surface, border, text.
|
||
- Check the rendered contrast, including: placeholder text, disabled buttons, badges on coloured
|
||
backgrounds, white text on `--warning` (this one almost always fails).
|
||
|
||
## 5. The states people forget
|
||
|
||
Every list, grid and total needs four designs, not one:
|
||
|
||
1. **Loading** — skeletons matched to the real layout (not a centred spinner that collapses the page).
|
||
2. **Empty** — heading, one sentence explaining why it's empty, and the button that fixes it.
|
||
"No products found" alone is a dead end; offer "Clear filters".
|
||
3. **Error** — what failed, in the user's language, and what to do now. Never a raw traceback or code.
|
||
4. **Partial / stale** — an order past its cutoff, an out-of-stock line, a price that changed. Say it
|
||
inline, next to the affected row, not only in a toast that disappears.
|
||
|
||
## 6. Feedback and motion
|
||
|
||
- Any action taken by the user gets a response within 100 ms — disable the button, show the spinner,
|
||
optimistic-update the quantity. Silence reads as "broken", and users double-submit.
|
||
- Toasts: `aria-live="polite"`, dismissible, never the only place an error is shown.
|
||
- Motion is 150–250 ms and eases out. Anything above 400 ms feels slow; anything that moves the page
|
||
under the cursor is a bug.
|
||
- Everything animated respects `prefers-reduced-motion`.
|
||
|
||
## 7. Content and copy
|
||
|
||
- Sentence case for buttons and labels; imperative verbs ("Add to cart", not "Adding products").
|
||
- Prices always formatted server-side with the currency and locale (`i18nManager.formatCurrency`),
|
||
never string-concatenated in the template.
|
||
- Dates: locale-formatted, and relative only when the absolute date is also available (`title`).
|
||
- Nothing user-visible is a hardcoded English literal — templates get translatable text, JS gets
|
||
`window.i18nManager.get(key)`.
|
||
|
||
## 8. Cross-cutting checks before "done"
|
||
|
||
```
|
||
[ ] 320 px: no horizontal scroll on any page
|
||
[ ] 200 % zoom: nothing clipped or overlapping
|
||
[ ] Tab through the whole flow: visible focus, logical order, no trap
|
||
[ ] All actions reachable without a mouse, Esc closes overlays
|
||
[ ] No hex outside base/variables.css
|
||
[ ] No inline style="" in views/*.xml
|
||
[ ] No new !important (except the reduced-motion reset)
|
||
[ ] Every <img> has alt and width/height
|
||
[ ] Every icon-only control has aria-label
|
||
[ ] Empty / loading / error states exist for each list
|
||
[ ] es, eu and ca render without overflow (German-length strings break tight buttons)
|
||
[ ] pre-commit run --all-files is clean
|
||
[ ] Lighthouse a11y ≥ 95 on shop, cart, checkout
|
||
```
|
||
|
||
The last three matter as much as the rest: a layout tuned only against Spanish copy usually breaks in
|
||
Basque, and a11y regressions land silently.
|