Skip to main content

C3 — Empty, Loading & Error State Catalog

Scope: Named UI patterns for every empty, loading, and error state across all Ghasi Melmastoon surfaces. Each pattern entry defines: when to use it, anatomy, variants, component props, accessibility notes, and an illustration slot for design handoff.

Rule: Every data-driven surface MUST render one of these patterns — never a blank white screen or unhandled promise rejection. Pick the closest match; if none fits, add a new entry here before shipping.


1. Loading patterns

L-01 Skeleton screen

When: Initial page/screen load where content shape is known and predictable. Always prefer over a full-page spinner.

Anatomy:

  • Shimmering rectangle placeholders that match the shape of the real content (card, table row, text block).
  • Shimmer gradient: from: surface → surfaceMuted → surface, animated 1.4 s linear infinite.
  • No text inside skeleton elements.

Variants:

VariantUse case
cardProperty card, reservation card, task card
table-rowArrivals board, housekeeping board table
detail-panelProperty detail, folio panel, room detail
list-itemStaff list, rate plan list, notification list
kpi-tileDashboard KPI tiles

Props (component <Skeleton>):

<Skeleton variant="card" count={3} animate />
<Skeleton variant="table-row" count={10} animate />

A11y: Wrap skeleton group in <div role="status" aria-label="Loading…" aria-live="polite">. Remove once data loads.

Illustration slot: [Design handoff: Figma → Skeletons page]


L-02 Inline spinner

When: Short in-place loading (button submit, inline section refresh). Duration expected < 1.5 s.

Anatomy:

  • 20px circular indeterminate spinner using border-top: currentColor.
  • Placed at the centroid of the element it replaces.
  • Label: screen-reader-only aria-label="Loading".

Variants: sm (16 px), md (20 px), lg (32 px).

Do / Don't:

  • ✅ Use inside a button to replace the label while submitting.
  • ❌ Do not use for full-page or large section loads — use L-01.

L-03 Progress bar (determinate)

When: File upload, report export, sync progress, onboarding multi-step with known total.

Anatomy:

  • Thin bar (4 px height) at the top of the panel or full viewport width.
  • Fills left-to-right (LTR) / right-to-left (RTL) using direction-aware inset-inline-start.
  • Color: primary token.
  • Accessible: role="progressbar" aria-valuenow={pct} aria-valuemin={0} aria-valuemax={100} aria-label="Upload progress".

L-04 Skeleton + stale data indicator

When: Background refresh on data that is already showing (e.g., housekeeping board polling every 30 s). Show existing data; overlay a subtle refresh indicator in the corner.

Anatomy:

  • Existing content remains visible.
  • Small pulsing dot or spinner in the top-right of the panel.
  • aria-live="polite" region announces "Refreshing…" then "Updated" when done.

2. Empty state patterns

When: Search returns 0 results.

Anatomy:

  • Illustration: hospitality-themed (magnifying glass over a guesthouse, custom SVG from illustration set).
  • Headline (bold): "No properties found"
  • Sub-text: "Try adjusting your dates, location, or filters."
  • CTA: "Clear all filters" (if filters are active).

Variants:

VariantHeadlineSub-textCTA
search-no-results"No properties found""Try adjusting…""Clear filters"
map-no-results"No properties in this area""Zoom out or search by name""Reset map"
shortlist-empty"Your shortlist is empty""Save properties to compare later""Discover properties"

A11y: <section aria-label="No results">.


E-02 Empty list / board

When: A data list (arrivals board, housekeeping board, folio charges, maintenance queue) has no items.

Anatomy:

  • Small illustration or icon.
  • Headline: contextual ("No arrivals today", "All rooms clean", "No charges yet").
  • Optional CTA: only if an action is directly possible from here.

Variants by context:

ContextHeadlineCTA
Arrivals board"No arrivals today"
Housekeeping board"All rooms are clean"
Folio charges"No charges added yet""Add charge"
Maintenance queue"No open tickets""Log issue"
Notifications"You're all caught up"

E-03 Offline-unavailable data

When: Data exists but cannot be loaded because the desktop is offline and the data is not in the local SQLite cache.

Anatomy:

  • Icon: cloud with slash.
  • Headline: "Not available offline"
  • Sub-text: "This data will load when you reconnect. Last sync: <time>."
  • No retry CTA (reconnect is the fix, not user action).

E-04 Permission-gated empty

When: User lands on a section they can access the URL for but lacks the role to see the content.

Anatomy:

  • Lock icon illustration.
  • Headline: "You don't have access to this section"
  • Sub-text: "Ask your manager to update your permissions."
  • No action CTA.

3. Error state patterns

ER-01 Toast / snackbar

When: Transient recoverable error; does not block workflow.

Anatomy:

  • Fixed-position bottom-left (LTR) / bottom-right (RTL), 16 px from edge.
  • Max-width 360 px.
  • Icon: error (red) or warning (amber) from @ghasi/icons.
  • Text: userMessageKey string, max 2 lines.
  • Close button: always present.
  • Optional action button (e.g., "Retry", "Undo").
  • Auto-dismiss: 5 s (informational), 8 s (with action). Paused on hover/focus.

Stacking: Max 3 toasts; oldest dismissed first.

A11y: role="alert" for error, role="status" for info. Focus stays on triggering element.


ER-02 Inline field error

When: Form validation (client-side or server-side via errors[] array).

Anatomy:

  • Red text below the field input, 12 px.
  • Icon: alert-circle 14 px before the text.
  • Field border: --color-error token.
  • Field ARIA: aria-invalid="true" + aria-describedby="<error-id>".

ER-03 Inline section banner

When: A panel or section has a recoverable non-blocking issue (e.g., sync warning, outdated pricing, stop-sell notice).

Anatomy:

  • Full-width banner inside the section, surfaceMuted background with left border --color-warning.
  • Icon + text + optional CTA.
  • Dismissible via ✕ button if not critical.

Variants: info, warning, error.


ER-04 Skeleton + retry button

When: A panel/card failed to load its data; content shape is known.

Anatomy:

  • Skeleton placeholder (L-01) with a "Could not load — Retry" overlay or inline button after skeleton.
  • After 2 auto-retries, show retry button.

ER-05 Modal error dialog

When: Action failed in a way that requires user decision (e.g., hold expired, folio locked).

Anatomy:

  • Standard <Dialog> from Radix UI.
  • Icon: alert-triangle (error severity).
  • Title: error title.
  • Body: human-readable explanation + what the user can do.
  • Actions: primary CTA ("Try again", "Start over") + optional "Cancel".
  • Dismissible only via explicit CTA.

A11y: role="alertdialog", focus trapped inside, ESC closes if Cancel is present.


ER-06 Full-screen error page

When: Unrecoverable state: session revoked, tenant suspended, device not bound, state corrupt.

Anatomy:

  • Centered layout, max-width 480 px.
  • Illustration: relevant (lock, cloud-offline, warning).
  • Headline (H1).
  • Body text (1-3 sentences).
  • Primary CTA ("Log in again", "Contact support").
  • Trace ID in collapsed <details> for support.

Navigation: All nav is hidden or disabled; user cannot browse away from this page to other app areas.


ER-07 Offline indicator

When: Desktop app loses network connectivity.

Anatomy:

  • Sticky banner at the top of the app shell: amber background.
  • Icon: wifi-off.
  • Text: "You're offline — working from local data. Last synced <relative time>."
  • Auto-dismissed when connectivity resumes; replaced by "Back online — syncing…" toast for 3 s.

4. Decision tree

Request completes?
├─ No (network/timeout) → retriable? → yes → Silent retry (L-02 spinner) → after cap → ER-01 toast
│ → no → offline? → ER-07 + E-03 | ER-01 toast
└─ Yes
├─ 2xx → has data?
│ ├─ yes → render content
│ └─ no → pick E-01/E-02/E-03/E-04
└─ 4xx/5xx → look up C2 → pick ER-01/ER-02/ER-03/ER-04/ER-05/ER-06

5. Open Questions

  • Should "All rooms clean" (E-02 housekeeping board) include a motivational message (hospitality warmth) or keep it factual?
  • Define exact illustration set for E-01/E-02/E-04/ER-06 — blocked on design-ops/C11-iconography completion.
  • Desktop offline indicator: should it show a live counter of outbox items waiting to sync?

References