Browse documents

UX Patterns, States, Accessibility & Content

Status: v1.0 · Owner: Design Lead + Frontend Lead + QA(a11y) · Gate input: G2, G4, G5, G8 The behavioural contract: how interactions work, the mandatory state matrix every surface implements, accessibility requirements (the WCAG 2.2 AA gate), responsive rules, performance budgets, and content/voice. These are testable and gated, not stylistic suggestions.


1. The State Matrix (mandatory for every data surface)

Principle (00_README.md #3): a blank screen is a bug. Every list, detail, widget, and form implements all applicable states. QA tests for these explicitly.

StateWhenWhat the user seesNotes
LoadingData in flight, layout knownSkeleton matching final layout (not a spinner)Spinner only when layout unknown
Loading (slow)> 1.2sSkeleton + subtle "still loading" affordanceNever freeze; keep chrome interactive
Empty (first-run)No data everIllustration + one-line explanation + primary CTASee IA §6; teaches next action
Empty (filtered)Filters exclude all"No results for these filters" + clear-filters actionDistinct from first-run empty
PartialSome data, some failedShow what loaded + inline notice for the failed partNever discard good data
Error (recoverable)Request failedPlain-language cause + Retry + support refNo raw stack traces
Error (boundary)Render crashFriendly boundary + reload + report; preserves shellReact error boundary per route
OfflineNo connectivity (PWA)Calm "Offline" banner; cached content usableField persona; never alarming
Queued / pending syncOffline write"Saved on device · will sync" badge per itemTruthful; clears on sync
No permissionLacks RBAC permHide if possible; else "You don't have access" + who to askPrefer hiding nav over dead clicks
Stale / liveSSE widgets"Live" pulse + last-updated; "reconnecting…" on dropTrust in real-time data
Read-onlyLocked / viewer roleControls visibly disabled with reason on hoverNot silently inert

A component's Storybook story (design_system.md §7.1) must render every applicable state. "Done" excludes any unimplemented state.


2. Interaction patterns (learn once, reuse everywhere)

2.1 Lists & tables

  • Default view per module (table or board); switchable; choice persists per user.
  • Saved views: named filter+sort+column sets, shareable within tenant.
  • Filters live in the URL (deep-linkable, shareable).
  • Bulk actions appear in a contextual action bar when rows are selected; destructive actions confirm.
  • Sticky header; column resize/reorder/show-hide; sort indicators; sticky first column on mobile-wide tables → collapse to cards below 768px (no horizontal scroll).
  • Pagination or virtualised infinite scroll (≥ 1k rows → virtualise).

2.2 Forms

  • Generated from Zod → React Hook Form so hand-built and inspector forms behave identically.
  • Inline validation on blur, summary on submit; first error focused.
  • Autosave for long/field work (inspections); explicit Save for transactional forms; never silent loss.
  • Optimistic submit where safe; show pending → confirmed → (rollback on error with toast).
  • Dangerous/irreversible actions require typed confirmation or a distinct destructive button + dialog.
  • Unsaved-changes guard on navigate-away.

2.3 Detail screens

  • Summary header: title, status pill, key facts, one primary action (sticky on mobile).
  • Tabbed sections (Overview / History / Related / Documents / Telemetry); tab in URL.
  • Related entities are drill-through links (IA §4).
  • Secondary actions in an overflow menu, never competing with the primary.

2.4 Drawers vs modals vs pages

  • Drawer (side sheet): quick view / quick action without losing list context. Back button closes it.
  • Modal/Dialog: focused decision or short form requiring full attention; trap focus; ESC closes (unless destructive-in-progress).
  • Full page: rich create/edit, anything deep-linkable or shareable.

2.5 Command palette (⌘K)

  • Navigate to any page, search any record, run common actions, switch tenant/theme.
  • Keyboard-first; fuzzy match; recent + suggested; every action labelled with its shortcut.

2.6 Notifications & feedback

  • Toast for transient confirmations (auto-dismiss, undo where possible).
  • Banner for persistent/contextual conditions (offline, degraded, budget warning).
  • Notification drawer for the durable feed (assignments, SLA, anomalies) with unread count.
  • Never use a toast for an error the user must act on — use an inline error or banner.

2.7 Real-time

  • Live values update in place with a subtle highlight; no layout shift; tabular-nums.
  • Connection state surfaced (live / reconnecting / stale) so a frozen feed is never mistaken for "all calm."

3. Accessibility (WCAG 2.2 AA — gate condition)

This is a hard gate (Phase_2.md AC, Phase_9.md §5.6), enforced by axe in CI and manual screen-reader passes.

3.1 Perceivable

  • Contrast per design_system.md §2.3 (4.5:1 text, 3:1 large/UI).
  • Never colour alone — status = icon + label + colour.
  • Text resizes to 200% without loss of content/function; layouts reflow at 320px-equivalent.
  • All meaningful images/icons have alt text / aria-label; decorative ones are aria-hidden.
  • Charts have an accessible alternative: data table toggle or aria summary of trend + key values.

3.2 Operable

  • Full keyboard parity — every action reachable and operable by keyboard; no keyboard traps (WCAG 2.2 includes focus-not-obscured).
  • Visible focus--ring focus-visible on every interactive element, ≥ 3:1.
  • Logical tab order; skip-to-content link; landmark regions (header/nav/main/aside).
  • Touch targets ≥ 44×44px (mobile forces ≥ comfortable density).
  • Dragging alternatives (WCAG 2.2 2.5.7): dashboard/workflow drag-drop has a non-drag path (move via keyboard/menu). Drag is an enhancement.
  • No content relies on hover alone; hover info also available on focus/tap.
  • Respect prefers-reduced-motion.

3.3 Understandable

  • Consistent navigation and component behaviour across the app.
  • Clear labels and instructions; errors identified in text, not colour, with how to fix.
  • Predictable: no context change on focus; explicit submit.
  • Plain language; expand jargon on first use.

3.4 Robust

  • Semantic HTML first; ARIA only to fill gaps (Radix primitives provide correct roles).
  • Custom widgets (dashboard, workflow canvas, command palette) get correct roles, names, states, and live-region announcements for async results ("3 results", "saved", "sync complete").
  • Tested with NVDA (Windows) + VoiceOver (macOS/iOS) on the critical journeys.

3.5 Accessibility acceptance per screen (QA checklist)

  • axe: zero critical/serious
  • keyboard-only walkthrough completes the primary task
  • visible focus throughout; logical order
  • screen-reader announces state changes
  • 200% zoom + 320px reflow OK
  • targets ≥ 44px on mobile
  • status conveyed without colour

4. Responsive design

Breakpoints align to IA §7 (<768 mobile, 768–1279 tablet, ≥1280 desktop, plus ≥1920 TV/wall).

  • Mobile-first authoring for shared screens; design the phone layout, then enhance.
  • Tables → card lists below 768px (no horizontal scroll for primary data).
  • Sidebar → bottom nav; modals → full-screen sheets on mobile.
  • Sticky primary action bar on detail screens (thumb reach).
  • Dashboards stack vertically by layout.y on mobile (Phase_8.md §6.3).
  • TV/War Room are kiosk/desktop-only with a friendly mobile redirect.
  • Test matrix: 360 / 414 / 768 / 1280 / 1920 (+ real-device pass in P8).

5. Performance & perceived-speed budgets (gated)

Sourced from Phase_5.md §6.3 and Phase_8.md §6.4; these are gate conditions, not goals.

MetricBudgetWhere
Dashboard cold render (12 widgets) p95< 3sG5
Live widget update lag p95< 2s after eventG5
TV-mode rotation transition< 250msG5
War Room 4×12 widgets, 24h soak< 1.5GB browser mem, no leakG5
Mobile LCP (4G) on /, /work-orders, /dashboards/:id< 3sG8
CLS (all routes)< 0.1G8
First-load JS per route (gzip)< 250KBG8
Lighthouse mobile (Perf/A11y/BP/PWA)≥ 90 / 95 / 95 / installableG8
AI chat first-token p95< 2sG6

Perceived-speed techniques (designed, not incidental): skeletons over spinners; optimistic UI on safe writes; instant theme switch (CSS-var swap); route-level code-splitting; defer off-screen chart paint; virtualise long lists and off-screen widgets.


6. Content & voice

The product's words are part of the UX. Tone: clear, calm, competent — never cute, never alarming.

  • Plain language, active voice, present tense. "Sync complete," not "Synchronisation has been completed."
  • Action labels are verbs: "Create work order", "Approve", "Retry sync" — not "OK"/"Submit".
  • Errors state what happened, why if known, and the next step — with a support reference, never a stack trace.
  • Empty states explain the value and offer the first action.
  • Numbers: locale-formatted; units always shown (kWh, °C); tabular-nums for live values.
  • Dates/times: locale + timezone aware; relative ("2h ago") with absolute on hover.
  • AI voice: helpful and honest — cites sources, says "I don't have enough data" rather than guessing, never claims certainty it lacks.
  • Destructive confirmations name the specific object ("Delete work order WO-1234?") and the consequence.

6.1 Internationalisation (EN + JA at launch)

  • All user-facing strings via next-intl; no hardcoded copy (lint-checked).
  • Layouts tolerate ~1.5× string length (Japanese/German headroom); no truncation of critical labels.
  • Locale-correct dates, numbers, units, and pluralisation.
  • JA review pass on every user-facing route before its phase gate; "no untranslated strings" is a Phase 9 check.

7. How patterns map to gates (QA traceability)

GatePattern checks
G2State matrix on shell/admin screens; a11y axe clean; command palette keyboard parity; EN/JA scaffold
G4State matrix on every SFMS screen; WO lifecycle interaction; a11y + i18n per screen
G5Widget state matrix; visual regression per theme; perf budgets; drag-drop keyboard alternative
G8Responsive matrix; touch targets; offline/queued states; Lighthouse; device pass
G9Full a11y manual SR pass; i18n coverage; all journey state-paths re-tested