All documents

States and feedback

"Feedback" here means the SYSTEM telling the reader what happened: empty, loading, refused, pending. The reader telling the system — Shift+Click anything to report it, and the queue an agent drains — is 24-feedback-and-triage.md.

Loading

Skeletons shaped like what is coming. A title bar, then a block the size of the table or the card. Never a centred spinner on an empty page — it says "something is happening" and nothing about what.

While a role-critical fact is still resolving (status === "loading"), render nothing rather than a guess. A control that appears and then vanishes is worse than one that arrives a beat late.

Empty

EmptyState: a dashed-border card, a 40px medallion holding a 20px icon, a title, one sentence, and at most one action. Distinguish the three empties:

SituationWhat it says
Nothing yet
What would put something here
Nothing matches
What is filtered, and how to clear it
Refused
Who the reader is, what the screen decides, who to ask

Errors

Alert with three variants: default (neutral), warning (the caution semantic, with its 3px left accent bar — the one place a coloured left border is allowed, because it is an alert, not a callout), and destructive.

An error that stops a write gets a title naming the failure ("Not saved") and a description carrying the real message. On screen, not in a toast: a toast that disappears takes the only evidence with it.

Disabled

Three real changes — muted fill, muted ink, plain border — plus cursor-not-allowed. Never opacity alone.

If a control is disabled for a reason, the reason must be reachable: a tooltip on the control, or a badge that replaces it. A disabled control with no explanation is a question the reader cannot answer.

Pending and unknown

A declared-but-unfilled value is not zero and not blank. Use an explicit pending device (an em dash with a tooltip, or a Pending badge) so the reader can tell "we have not measured this" from "this is zero".

Tooltips and hints

  • Tooltip for interactive elements — a badge with a reason, a truncated label.
  • InfoHint (the (?)) for column headers and stat labels: one sentence saying what the figure counts.
  • title for extra precision on a figure (the full ISO date under "2h ago").

Never put a tooltip on something a reader cannot reach with a keyboard, and never hide a fact they need to act in one.

Motion

Transitions are colour and transform, ~150ms. prefers-reduced-motion is honoured globally in globals.css. Motion confirms an action; it does not announce one. One well-orchestrated reveal beats a dozen micro-interactions.