BESBanking Experience System
Docs Components Journey Showcase
Draft — not yet human-verified. Every gate passing here is automated. No screen-reader, Arabic native-speaker or usability testing has been carried out yet, and all Arabic UI copy is machine-drafted. What has and hasn't been verified
Core components

12 — Core Components: Feedback & Status

Banking Experience System (BES) · Core Components · v2.0.0-draft

Live example

We're checking your transfer status. Your money is safe — we'll confirm within 30 minutes.

All 13 canonical states (doc 07 §4). The markup names only the state — data-bes-state="unknown" — and colour, glyph and label all come from one registry, so two screens cannot drift. Completed and Refunded share green; Processing, Pending and Checking status share teal. That is deliberate: they are related states, and what tells them apart is the glyph and the word, never the colour alone.

Code
<div class="demo-stack" style="max-width:560px">
  <div class="bes-alert bes-alert--error" role="alert">
    <div><b>We couldn't complete this transfer.</b>
    Your bank declined the transaction. No money was deducted from your account.
    <div class="actions"><a href="#">Try again</a><a href="#">Contact support</a></div></div>
  </div>
  <div class="bes-alert bes-alert--info" role="status">
    <div><b>We're checking your transfer status.</b> Your money is safe — we'll confirm within 30 minutes.</div>
  </div>
  <div class="demo-row" style="flex-wrap:wrap;gap:8px">
    <span class="bes-badge" data-bes-state="draft"></span>
    <span class="bes-badge" data-bes-state="review"></span>
    <span class="bes-badge" data-bes-state="authentication"></span>
    <span class="bes-badge" data-bes-state="processing"></span>
    <span class="bes-badge" data-bes-state="pending"></span>
    <span class="bes-badge" data-bes-state="completed"></span>
    <span class="bes-badge" data-bes-state="failed"></span>
    <span class="bes-badge" data-bes-state="unknown"></span>
    <span class="bes-badge" data-bes-state="blocked"></span>
    <span class="bes-badge" data-bes-state="reversed"></span>
    <span class="bes-badge" data-bes-state="refunded"></span>
    <span class="bes-badge" data-bes-state="disputed"></span>
    <span class="bes-badge" data-bes-state="cancelled"></span>
  </div>
  <p style="font-size:13px;color:var(--bes-text-secondary);margin:10px 0 0;max-width:60ch">
    All 13 canonical states (doc 07 §4). The markup names only the state —
    <code>data-bes-state="unknown"</code> — and colour, glyph and label all come from one
    registry, so two screens cannot drift. <strong>Completed and Refunded share green;
    Processing, Pending and Checking status share teal.</strong> That is deliberate: they are
    related states, and what tells them apart is the glyph and the word, never the colour alone.
  </p>
  <div class="demo-row">
    <button class="bes-btn bes-btn--secondary bes-btn--sm" data-bes-toast="IBAN copied">Show toast</button>
  </div>
</div>

Components: Alert (inline/section) · Banner (page-level) · Toast · Status Badge · Progress (bar/circular) · Skeleton · Spinner · Empty State · Loading State · Error State.

Choosing the component — the disruption model. Pick by where it belongs × how long it persists × how much attention it may demand:

Level Attention Component Example
1 · Passive Ambient Status Badge "Pending" on a transaction row
2 · Notice Read when reached Inline Alert "This beneficiary was added today" on review
3 · Prominent See before acting Section Alert / Banner "International payments are unavailable until 02:00"
4 · Transient confirm Momentary Toast "IBAN copied"
5 · Blocking Must respond Dialog / Interruption pattern (doc 25) Fraud stop, session pause

One rule above all: never stack multiple alerts of the same level in one region — consolidate or escalate.


Alert (inline / section)

Anatomy: container tinted per status (status.* 50-tint surface + 600 border-start bar) · status icon (shape-coded) · title (optional) · body · optional actions (≤2, link-style or small buttons) · optional dismiss (only if the information is elsewhere recoverable).

Variants: info (teal) · success (green) · warning (amber) · error (red) — plus ✦ financial-hold (purple, for reserved/held explanations).

States: static · dismissible · with-actions · live-updating (polite announcement).

Usage: contextual, adjacent to what it describes. Error alerts follow the message framework: WHAT happened · WHY · what happened to the MONEY · what to DO. Warnings before actions ("Sending to a new beneficiary — added 2 minutes ago") sit directly above the action row.

A11y: role=status (info/success) vs role=alert (warning/error); icon + color + title redundancy; focus moved only by the error-summary pattern, not by every alert.

RTL: border-start bar flips to visual right; icon at inline-start.

Content example:

⚠ We couldn't complete this transfer. Your bank declined the transaction. No money was deducted from your account. [Try again] [Contact support]


Banner (page/app-level)

Anatomy: full-width strip above content · icon · single line + optional link · dismiss per policy.

Variants: system (maintenance: "Scheduled maintenance Fri 02:00–04:00 — cards keep working") · security campaign ("We'll never ask for your password — how to spot scams") · regulatory notice (fee changes effective date + "See what's changing") · degraded service (partial data: "Balances from other banks are delayed").

Usage: max one banner visible; priority order security > degraded > regulatory > system > campaign. Marketing content is never a Banner (marketing uses dashboard content slots) — the banner channel stays trustworthy.

A11y: landmark-region labeled; re-announced only on content change.


Toast

Anatomy: compact surface, bottom (mobile) / top-inline-end (desktop) · icon + ≤ 2 lines · optional single action ("Undo") · auto-dismiss 4–6s, pausable on hover/focus.

Usage: confirmations of small, completed, recoverable acts (copied, saved, preference changed). Never for money movement results — a transfer outcome is a screen, not a toast. Never for errors that need action.

A11y: polite live region; persists longer at high text-scaling; actions also achievable elsewhere (toast-only actions banned).


Status Badge

Anatomy: dot or pill · icon (shape-coded, doc 04) + label · tint per status.* / financial.*.

Variants: dot+label (tables, compact) · pill (cards, headers) · count (numeric, navigation).

Usage: the passive tier of the state machine everywhere a transaction/verification/consent state appears in collections. Labels come from the canonical state vocabulary — never invent per-screen synonyms ("Pending", not "In progress" here and "Processing" there).

A11y: label text always present (icon+color never alone); included in row's accessible name.


Progress (bar / circular)

Anatomy: track + indicator · label (what's progressing) · value text (% or step) when determinate.

Variants: determinate (uploads, application completeness "Profile 80% complete") · indeterminate (short unknown waits) · step progress (see Stepper, doc 10).

Usage rules: determinate only with a real basis — fake progress percentages on opaque payment rails are banned; use the processing state's calm pulse + honest copy instead. Indeterminate > 10s escalates to a status message with expectation ("This is taking longer than usual — we'll notify you when it's done").

A11y: progressbar semantics with value; updates announced at sensible intervals (25/50/75/100), not continuously.

RTL: bar fills from inline-start (visual right in Arabic); circular rotation unmirrored.


Skeleton

Anatomy: grey shapes matching the real layout (text lines, amount block, avatar) with subtle shimmer (reduced-motion: static).

Usage: structure-known loads ≥ 300ms: dashboards, lists, detail screens. Skeletons for amounts use the amount block shape but never placeholder digits (no "0.00 flash" — a wrong number seen for 200ms is still a wrong number seen).

A11y: container announced busy; real content announced on arrival.


Spinner

Small inline waits ≤ ~3s inside components (button loading, search). Appears only after a 400ms delay to avoid flicker. Always with text for waits users notice ("Checking payee name…").


Empty State

Anatomy: illustration slot (brand-tier, optional) · headline · explanation · primary action · optional secondary link.

Variants: first-use ("No beneficiaries yet — add someone to send money to") · filtered-empty ("No transactions match your filters" + Clear filters) · permission-empty (SME: "You don't have access to approvals — ask an administrator") · ✦ zero-balance treatments (an empty savings goal encourages; an empty account does not shame).

Usage: every collection defines all its empty variants at design time; a blank region shipped without one is a defect.

Content: explain + enable, never blame; no "Oops".


Loading State (screen-level)

Composition rules: skeleton for structure + staged reveal (balance may resolve before transactions — render independently, no all-or-nothing gate); slow-path messaging at 5s; timeout at 15s → inline Error State, not an infinite shimmer.


Error State (screen/section-level)

Anatomy: icon (octagon) · headline (what failed) · body (why, if known + money reassurance where relevant) · retry action · support path · reference code (short, human-readable, for support — internal codes stay internal).

Variants: connection ("You're offline — showing balances from 14:32" with cached-data timestamping) · service ("We can't load transactions right now") · partial (section-level failure inside an otherwise healthy screen — prefer partial over full-page).

Usage: cached financial data shown during errors is always timestamped; never silently stale. Retry is idempotent-safe (a retry never risks double-sending — the money-movement patterns own that guarantee).

A11y: focus moves to the error headline on full-screen errors; retry reachable first.


All components in this family — live

Every component from this specification, rendered by the coded library. Expand "Code" to copy the markup.

Banner tiers

We'll never ask for your password or card PIN. Spot the scams
Scheduled maintenance Fri 02:00–04:00 — cards keep working.

One banner at a time; security > degraded > regulatory > system. Marketing never uses this channel.

Code
<div class="demo-stack" style="max-width:560px">
  <div class="bes-banner bes-banner--security" style="border-radius:10px;border:1px solid var(--bes-border-default)">
    <svg class="ic" width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 1.5l5.5 2v4c0 3.2-2.3 5.6-5.5 7-3.2-1.4-5.5-3.8-5.5-7v-4l5.5-2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
    We'll never ask for your password or card PIN. <a href="#" style="font-weight:600">Spot the scams</a>
  </div>
  <div class="bes-banner" style="border-radius:10px;border:1px solid var(--bes-border-default)">
    <svg class="ic" width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.4"/><path d="M8 7.4v3.2M8 5v.4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
    Scheduled maintenance Fri 02:00–04:00 — cards keep working.
  </div>
</div>
<p style="font-size:13px;color:var(--bes-text-secondary);margin:10px 0 0">One banner at a time; security &gt; degraded &gt; regulatory &gt; system. Marketing never uses this channel.</p>

Progress, skeleton & spinner

Profile completeness — determinate only when real
80% complete
Code
<div class="demo-stack" style="max-width:420px">
  <div>
    <div style="font-size:13px;font-weight:600;margin-bottom:6px">Profile completeness — determinate only when real</div>
    <div class="bes-progress"><div class="fill" style="inline-size:80%"></div></div>
    <div style="font-size:12.5px;color:var(--bes-text-secondary);margin-top:4px">80% complete</div>
  </div>
  <div class="demo-row" style="align-items:center">
    <div class="bes-skel" style="inline-size:160px;block-size:14px"></div>
    <div class="bes-skel" style="inline-size:80px;block-size:24px"></div>
    <button class="bes-btn bes-btn--secondary bes-btn--sm bes-btn--loading">Checking…</button>
  </div>
</div>

Empty & error states

No beneficiaries yet
Add someone to send money to — it takes a minute.
We can't load transactions right now
Your money is safe. Showing balances from 14:32.
Code
<div class="demo-row" style="align-items:stretch">
  <div class="bes-card bes-empty" style="inline-size:280px">
    <div class="t">No beneficiaries yet</div>
    <div class="s">Add someone to send money to — it takes a minute.</div>
    <button class="bes-btn bes-btn--primary bes-btn--sm">Add beneficiary</button>
  </div>
  <div class="bes-card bes-empty" style="inline-size:280px">
    <div class="t">We can't load transactions right now</div>
    <div class="s">Your money is safe. Showing balances from 14:32.</div>
    <button class="bes-btn bes-btn--secondary bes-btn--sm">Try again</button>
  </div>
</div>