4.8 KiB
4.8 KiB
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-*. Amargin: 13pxanywhere is a defect. - Space belongs to one side consistently (block-end margins, or
gap— pick one per container). Prefergapin flex/grid; it doesn't collapse and doesn't need:last-childcleanups. - 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 withmargin-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 pickingh4because it looks right. max-width: 65chon paragraphs of real prose.- Numbers in tables:
font-variant-numeric: tabular-numsand right-aligned so columns line up. - Long product names truncate predictably:
text-overflow: ellipsiswithtitleattribute, or-webkit-line-clamp: 2with a fixedmin-heightso 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:
- Loading — skeletons matched to the real layout (not a centred spinner that collapses the page).
- 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".
- Error — what failed, in the user's language, and what to do now. Never a raw traceback or code.
- 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.