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.
| State | When | What the user sees | Notes |
|---|---|---|---|
| Loading | Data in flight, layout known | Skeleton matching final layout (not a spinner) | Spinner only when layout unknown |
| Loading (slow) | > 1.2s | Skeleton + subtle "still loading" affordance | Never freeze; keep chrome interactive |
| Empty (first-run) | No data ever | Illustration + one-line explanation + primary CTA | See IA §6; teaches next action |
| Empty (filtered) | Filters exclude all | "No results for these filters" + clear-filters action | Distinct from first-run empty |
| Partial | Some data, some failed | Show what loaded + inline notice for the failed part | Never discard good data |
| Error (recoverable) | Request failed | Plain-language cause + Retry + support ref | No raw stack traces |
| Error (boundary) | Render crash | Friendly boundary + reload + report; preserves shell | React error boundary per route |
| Offline | No connectivity (PWA) | Calm "Offline" banner; cached content usable | Field persona; never alarming |
| Queued / pending sync | Offline write | "Saved on device · will sync" badge per item | Truthful; clears on sync |
| No permission | Lacks RBAC perm | Hide if possible; else "You don't have access" + who to ask | Prefer hiding nav over dead clicks |
| Stale / live | SSE widgets | "Live" pulse + last-updated; "reconnecting…" on drop | Trust in real-time data |
| Read-only | Locked / viewer role | Controls visibly disabled with reason on hover | Not 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 arearia-hidden. - Charts have an accessible alternative: data table toggle or
ariasummary 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 —
--ringfocus-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.yon 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.
| Metric | Budget | Where |
|---|---|---|
| Dashboard cold render (12 widgets) p95 | < 3s | G5 |
| Live widget update lag p95 | < 2s after event | G5 |
| TV-mode rotation transition | < 250ms | G5 |
| War Room 4×12 widgets, 24h soak | < 1.5GB browser mem, no leak | G5 |
Mobile LCP (4G) on /, /work-orders, /dashboards/:id | < 3s | G8 |
| CLS (all routes) | < 0.1 | G8 |
| First-load JS per route (gzip) | < 250KB | G8 |
| Lighthouse mobile (Perf/A11y/BP/PWA) | ≥ 90 / 95 / 95 / installable | G8 |
| AI chat first-token p95 | < 2s | G6 |
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)
| Gate | Pattern checks |
|---|---|
| G2 | State matrix on shell/admin screens; a11y axe clean; command palette keyboard parity; EN/JA scaffold |
| G4 | State matrix on every SFMS screen; WO lifecycle interaction; a11y + i18n per screen |
| G5 | Widget state matrix; visual regression per theme; perf budgets; drag-drop keyboard alternative |
| G8 | Responsive matrix; touch targets; offline/queued states; Lighthouse; device pass |
| G9 | Full a11y manual SR pass; i18n coverage; all journey state-paths re-tested |