17 — Financial Components: Cards Family
Banking Experience System (BES) · Financial Components · v2.0.0-draft
Live example
Code
<div class="bes-card" style="max-width:420px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
<div style="font-weight:600">Debit card <bdi class="bes-amount">••8834</bdi></div>
<span class="bes-badge bes-badge--info">Frozen</span>
</div>
<label class="bes-switch" style="margin-bottom:10px">
<input type="checkbox" checked>
<span class="track" aria-hidden="true"></span>
<span>Freeze card <span class="state">On — blocks new payments, ATM and online</span></span>
</label>
<label class="bes-switch" style="margin-bottom:10px">
<input type="checkbox">
<span class="track" aria-hidden="true"></span>
<span>Online payments <span class="state">Off</span></span>
</label>
<label class="bes-switch">
<input type="checkbox" checked>
<span class="track" aria-hidden="true"></span>
<span>Contactless <span class="state">On</span></span>
</label>
<div class="bes-limit" style="margin-top:16px">
<div style="font-size:13.5px;font-weight:600;margin-bottom:6px">Daily spend limit</div>
<div class="bes-progress bes-progress--warn"><div class="fill" style="inline-size:82%"></div></div>
<div class="figures"><span class="used"><bdi class="bes-amount">AED 2,460.00</bdi> used</span><span>of <bdi class="bes-amount">AED 3,000.00</bdi></span></div>
</div>
</div>Components: Card Art · Card Overview · Card Controls Panel · Freeze Control · Spending Limit Control · Channel Controls · PIN Management · Card Details Reveal · Replacement Flow entry · Card Transaction Item · Dispute entry · Travel Notice · Card Carousel.
Card Art
Purpose: visual representation of a physical/virtual card.
Anatomy: brand card face (theme slot) · scheme mark(s) — co-badged domestic+international schemes render both marks per scheme brand rules, never mirrored, never recolored · masked PAN (•••• 4521) · cardholder name · virtual badge where applicable · frozen overlay state (frost treatment + "Frozen" label).
Variants: physical · virtual (distinct visual marker — users must never confuse which number they're sharing) · single-use virtual (countdown/remaining-uses) · supplementary (holder name highlighted).
States: active · frozen (visual + label, reversible affordance adjacent) · blocked (bank-initiated: distinct from frozen, with reason class + support) · expired (renewal state) · in-production ("Arriving in 3–5 days" + activate CTA when delivered) · closed.
A11y: decorative face; state carried by adjacent text ("Debit card ending 4521, frozen").
Card Overview
Purpose: one card's home.
Anatomy: Card Art · status line · quick controls row (Freeze · Details · PIN · Settings) · spending summary (this month vs limit, Limit Indicator) · recent transactions (Transaction Items scoped to card) · linked account line.
States: inherits card states; every non-active state shows its recovery path here.
Card Controls Panel
Purpose: all security/spend controls in one place, honest about what each does.
Anatomy: grouped Switches with per-control explanation: - Channels: online payments · contactless · ATM withdrawals · international usage (geo-scope selector: "Everywhere / UAE + chosen countries") - Limits: per-transaction cap · daily spend cap (Currency Inputs with policy bounds shown) - Merchant categories (optional tier): gambling-block etc., with regulatory notes where locks apply (some blocks carry cooling-off periods before they can be disabled — stated at the moment of enabling: "If you turn this off later, it takes effect after 72 hours").
States per control: on · off · pending (async, doc 09 Switch) · locked-by-policy (with why) · locked-by-parent (supplementary cards: "Set by the primary cardholder").
Security: loosening controls (enabling international, raising limits) may require step-up auth; tightening never does — safety is always one tap, risk requires proof.
Freeze Control
Purpose: instant reversible kill-switch.
Anatomy: prominent switch/button ("Freeze card") · effect summary ("Blocks new payments, ATM and online. Recurring payments you've approved may still go through — review them.") · unfreeze mirror.
States: active → freezing (pending) → frozen (confirmation + what still works) → unfreezing.
Rules: freeze requires no step-up (panic action); freeze is not loss-reporting — the panel links "Lost or stolen? Report it" into the replacement flow with card-block semantics explained (freeze = reversible; report = permanent block + replacement).
Spending Limit Control
Currency Input + policy bounds + effect preview ("New daily limit: AED 3,000.00 — resets midnight"). Rises above bank thresholds require step-up + may carry cooling-in delay (stated). Lowering is instant.
PIN Management
Anatomy: view-PIN (step-up auth → time-limited masked reveal with auto-hide, screenshot-blocked surface) · change-PIN (PIN Entry ×2 with weak-PIN rejection: sequences, birth year, repeats) · wrong-PIN counter state ("2 attempts left at ATMs — unblocks after successful app verification").
A11y: reveal announces countdown; keypad accessible; no haptic-only.
Card Details Reveal (virtual card usage)
Anatomy: step-up auth → PAN/expiry/CVV revealed with per-field copy buttons · auto-mask after 60s · "who are you sharing this with" caution line for single-use cards.
Security: clipboard auto-clears after 90s where platform allows (stated); reveal events are notification-logged.
Replacement Flow (entry component)
Anatomy: reason picker (lost / stolen / damaged / never-arrived) — reason drives consequence copy: lost/stolen = immediate permanent block + "your current card stops working now"; damaged = old card works until activation · delivery address confirmation (masked, change path re-verifies) · fee line where applicable · virtual-card-continuity note ("Your card number changes; update saved subscriptions — here are merchants that charged this card recently" → list).
States: reported → blocked → replacement-ordered → shipped → delivered → activated.
Card Transaction Item
Transaction Item specialization: adds card ••suffix, channel icon (online/contactless/ATM), FX pair for foreign spend (Amount Pair), decline reason class on declined rows ("Declined — over daily limit" with the fix path: "Raise limit").
Dispute Entry ("Report a problem")
Anatomy: launched from Transaction Detail · triage question set ("I don't recognize this" / "Charged twice" / "Wrong amount" / "Item not received" / "Subscription I cancelled") · route: recognition issues → fraud path (immediate freeze offer + secure contact); merchant issues → dispute case with evidence upload (File Upload) · expectation setting ("Provisional credit within X days where eligible; investigations take up to 90 days") · case tracker (Timeline: submitted → under review → resolved with outcome).
Rules: the fraud fork acts first, asks questions second; dispute status changes are notification events; outcomes state the money movement explicitly ("AED 89.50 returned to your account today").
Travel Notice
Largely obsolete with smart authorization, but retained for institutions requiring it: date range + destinations → confirmation of coverage; the component self-documents when a bank should not deploy it.
Card Carousel
Multi-card navigation: swipeable Card Art stack with dots, active card drives the overview beneath; order user-arrangeable; RTL swipe direction mirrors; a11y: cards as tabs with full names ("Card 2 of 3, virtual card ending 8834").
All components in this family — live
Every component from this specification, rendered by the coded library. Expand "Code" to copy the markup.
Card art — virtual, co-badged, frozen
Code
<div class="demo-row" style="align-items:flex-start">
<div class="bes-cardart bes-cardart--virtual">
<div class="bank">AL NOOR BANK</div>
<div class="pan">•••• •••• •••• 8834</div>
<div class="row"><span class="holder">S. KUMAR</span><span class="schemes">JAYWAN · INTL</span></div>
</div>
<div class="bes-cardart bes-cardart--frozen">
<div class="bank">AL NOOR BANK</div>
<div class="pan">•••• •••• •••• 4521</div>
<div class="row"><span class="holder">S. KUMAR</span><span class="schemes">JAYWAN · INTL</span></div>
</div>
</div>Details reveal — step-up gated
Auto-masks after 60s · clipboard clears after 90s · every reveal is notification-logged.
Code
<div class="bes-card" style="max-width:400px">
<div class="bes-rows">
<div class="bes-row"><span class="k">Card number</span><span class="v"><bdi class="bes-amount">5312 7789 0021 8834</bdi> <button class="bes-whyfee" type="button" data-bes-copy="5312778900218834" data-bes-copied="Card number copied">copy</button></span></div>
<div class="bes-row"><span class="k">Expiry</span><span class="v"><bdi class="bes-amount">09/29</bdi></span></div>
<div class="bes-row"><span class="k">CVV</span><span class="v"><bdi class="bes-amount">•••</bdi> <button class="bes-whyfee" type="button">reveal</button></span></div>
</div>
<p style="font-size:12.5px;color:var(--bes-text-secondary);margin:8px 0 0">Auto-masks after 60s · clipboard clears after 90s · every reveal is notification-logged.</p>
</div>Dispute triage — the fraud fork acts first
Code
<div class="demo-stack" style="max-width:440px" role="radiogroup" aria-label="What's wrong with this payment?">
<label class="bes-check"><input type="radio" name="dcard-disp"><span><b>I don't recognize this</b><span class="desc">→ fraud path: freeze offered immediately, questions after</span></span></label>
<label class="bes-check"><input type="radio" name="dcard-disp"><span><b>Charged twice</b><span class="desc">→ dispute case with evidence upload</span></span></label>
<label class="bes-check"><input type="radio" name="dcard-disp"><span><b>Subscription I cancelled</b><span class="desc">→ merchant dispute + block future charges</span></span></label>
</div>Replacement — reason drives consequence
Code
<div class="bes-alert bes-alert--warning" style="max-width:460px">
<div><b>Lost or stolen?</b> Your current card stops working immediately and can't be reactivated. Your card number changes — we'll list merchants that charged this card so you can update subscriptions. Replacement arrives in 3–5 days; your virtual card keeps working.</div>
</div>