UX States Spec - every screen has four states, not one

Who fills this in: UI/UX (fast, so devs can build against it). Why: the “happy path with data” is the easy 20%. Loading, empty, and error are where real users live. Specifying all four before the build is Way #1 for the front end - and it’s how you avoid a demo that white-screens when the API hiccups.

Fill the state tables for each endpoint, make the data-viz call, then the accessibility note. Delete the quote-blocks before handing it over.


The four states - define each, every time

For each consumer screen, specify what the user sees in Loading, Populated, Empty, and Error. “Empty” and “Error” are different: empty = the call succeeded but there’s nothing to show; error = the call failed (renders from the Problem envelope, REQ-API-5).

Screen A - Compare (GET /api/compare)

StateTriggerWhat the user sees
LoadingRequest in flightSkeleton radar + skeleton bars; no layout shift when data lands
Populated200 with user[] + comparison[]Radar overlay: the user’s scores vs the scope average (see decision below)
Empty200 but every dimension is responded:falseFriendly “No comparison yet - complete your assessment to see how you stack up”, not a blank chart
Errornon-2xx / Problem bodyInline error card showing Problem.title; a Retry action; the rest of the dashboard stays usable

Edge case to spec explicitly: Alice has two N/A dimensions (governance, business_impact = 0.0, responded:false). Decide how the radar renders those - a visible gap/dashed segment beats plotting a misleading 0.

Screen B - Recommend (GET /api/recommend)

StateTriggerWhat the user sees
LoadingRequest in flight5 placeholder list rows
Populated200 with up to 5 recommendationsRanked list: title + one-line reason + gap indicator (see decision below)
Empty200 with [] (e.g. user completed everything relevant)“You’re all caught up on your weak spots” - a win, framed as one
Errornon-2xx / Problem bodyInline error row + Retry; never silently show an empty rail on failure

Data-visualisation decision (record the why - Way #5)

ScreenChosen vizWhyRejected
CompareRadar overlay - two series on one radar (you vs scope average)One shape, instant “am I inside or outside the team?” read across all 6 dimensions at onceSide-by-side bars (harder to compare 6 dims at a glance)
RecommendRanked list - each row: lesson title + reason + a gap indicator (bar/pip sized to gapScore)Order is the message; the gap indicator shows why this lesson ranks where it does, tying the UI back to the contract’s gapScoreA chart (recommendations are a to-do list, not a trend)

Capture this in a one-line ADR if it was contested - that’s where the trade-off reasoning lives (adr-template.md).


Accessibility note (non-optional - QUAL-PRINC-SDLC)

  • Don’t rely on colour alone. The compare overlay must distinguish “you” from “average” by shape/pattern/label, not just hue - and the recommend gap indicator needs a text/numeric value beside the bar.
  • Contrast & text: meet WCAG AA contrast; never hard-code an unreadable colour pair for the two radar series.
  • Screen readers: charts need a text alternative - e.g. the populated radar exposes the same numbers as a visually-hidden table; each recommendation row reads as “[title], gap [n], because [reason]”.
  • Keyboard & focus: Retry actions and any list interactions are reachable and operable by keyboard; visible focus states.
  • Motion: skeleton/loading shimmer respects prefers-reduced-motion.

Hand this to your devs alongside the contract in ../../../../stacks/nextjs/openapi.yaml. The five ways: ways-of-working.md.

Downloads for this session

Grab the templates and sample files used here.