Payments
24. AmountInput
The hardest input in fintech. Get this wrong and nothing else matters.
<AmountInput
currency="USD"
value={amount}
onChange={setAmount}
max={availableBalance}
onMaxClick={() => setAmount(availableBalance)}
/>
The rules
type="text"+inputMode="decimal". Nevertype="number"— it has scroll-wheel increment (which will silently change someone's payment amount), inconsistent decimal parsing across locales, and unstyleable spinners.- 16px minimum font size or iOS Safari zooms the viewport on focus and never zooms back.
- Parse permissively, format on blur. Accept
1,234.56,1234,56,1 234.56,$1234.56,1.2k. Reject nothing a human would type. Format canonically when they leave the field. - Never reformat mid-typing. Inserting a thousands separator while someone is typing moves the caret and is the single most infuriating input bug in fintech. Format on blur only.
- Caret preservation if you must transform during input — measure the caret offset from the right, not the left.
- Never block paste.
- Locale-aware decimal separator. A German user types
1234,56. Accept it. - Currency-aware precision. JPY rejects decimals entirely; KWD accepts three.
- Max is soft by default. Show "Exceeds available balance of $4,120.00" as an inline error, but let them type it — an overdraft may be legitimate, and blocking input to prevent an invalid state prevents valid ones too.
- Big.
figure-lg(28/32) or larger. This is the most important number on the screen and it should look like it. - The symbol is part of the field, at
--text-tertiary, not a floating adornment that misaligns as the value grows.
Accessibility
aria-label="Amount in US dollars" · parsed value announced on blur ("one thousand two hundred thirty-four dollars and fifty-six cents") via aria-describedby · errors specific and actionable · aria-invalid on error.
Motion
None on the value. Error message: 140ms opacity + 4px translate. Shake on invalid submit is forbidden — it's a stress signal in a context where the user is already anxious about money.
25. CurrencySelect
Searchable by code, name, and symbol. Recents pinned at the top. Flag optional, code always. Virtualized (180+ currencies). Keyboard typeahead — typing eu selects EUR.
26. PayeeSelect
Fuzzy search across name, account, and reference. Shows last-paid amount and date inline — the single most useful piece of context when choosing a recipient. New-payee flow inline, not a separate route.
27. PaymentForm
amount → payee → method → date → memo. That order — the amount first, because it's what the user came to do and it contextualizes everything after.
- Progressive disclosure: memo, reference, and attachments behind "Add details".
- Validation on blur, never per-keystroke.
- Submit is bottom-anchored on phone, above the safe area and above the keyboard (
visualViewport), always visible. - The submit button states the amount:
Send $12,400.00. - Fee and arrival estimate update live below the amount, with a value crossfade — never a count-up.
28. PaymentMethodList
Radio cards with EntityMark, masked number, and any per-method fee/timing difference stated inline. Selected state: 2px --accent border and an --accent-subtle background — and a check glyph, because border color alone fails forced-colors mode.
29. CheckoutSummary
Line items → subtotal (single rule) → fees → tax → total (double rule). Uses the same Rule grammar as the statements. Fees are itemized and named, never a lump "Fees". Multi-currency lines show conversion with the rate and its timestamp.
30. ConfirmPaymentDialog
Amount, destination, timing. In that order. Every time.
Send $12,400.00 to Northwind Ltd?
To account ••••4291 · Arrives 8 Sep
This can't be undone once submitted.
[Cancel] [Hold to send →]
AlertDialog. Focus lands on Cancel, never on the destructive action. Escape cancels.
For irreversible movements — wires, ACH past cutoff — the confirm is hold-to-confirm (1500ms linear fill, 200ms release). Slow where the user is deciding. Full spec including the keyboard and typed-confirmation fallbacks in motion catalog — this can never be the only path, because gating money behind a motor-skill requirement is an accessibility failure.
31. PaymentStatusTimeline
Submitted → Sent → In transit → Settled, each with a real timestamp and timezone. Failed states show the reason and the remedy. Pending is a state, not a load — a clock icon, never a spinner.
32. PaymentSuccess
The sanctioned delight moment. Circle scale → checkmark stroke draw → details fade up, staggered. Total ≤ 700ms. No confetti, no bounce, no sound. Spec.
Shows the amount, the destination, and the arrival estimate — the three things the user needs before they close the screen.
33. ReceiptSheet
Printable and shareable. Entity, amount, payee, method, reference, timestamp with timezone. Print styles are first-class — this is a document people keep.
34. PayoutSchedule
Upcoming payouts on a calendar or list, with cutoff times stated in the user's timezone. Business days respected, holidays flagged.
35. CardInput
PAN / expiry / CVC. autocomplete="cc-number" | "cc-exp" | "cc-csc", Luhn validation, network detection from the IIN with the mark appearing inline (140ms crossfade, blur-masked). Auto-advance between fields, with backspace returning to the previous field — the detail everyone forgets.
36. BankAccountInput
Routing + account number. ABA checksum validation with the institution name resolved and displayed once valid — instant confirmation that they typed the right bank. IBAN mod-97 validation for international, with country-aware length and formatting.
37. RecurringSchedule
Frequency, start, end condition. Always previews the next three occurrences with resolved dates. "Monthly on the 31st" is ambiguous in February; the preview makes the resolution visible before it surprises anyone.