# 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 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.