GH GambleHub

Switching currencies in the interface

1) Principles

1. First the meaning, then the UI. Separate account currency (accounting truth) from display currency (convenience) and transaction currency (actual money conversion).
2. Zero ambiguity. Show code + symbol at risk of confusion ('US $', 'CA $', 'MXN', 'R $'). For ₴/₸/₼, always add code in the details.
3. Course integrity. You can see: the source of the course, the moment of the last update, whether commissions/spread are included.
4. Input stability. Currency switching should not "jump" entry values without explicit consent (especially in rate/deposit forms).
5. Localization of formats. Delimiters, spaces, currency sign - by user locale; accuracy - by currency.


2) Switching models

Display-only - All calculations remain in the account currency, UI shows the equivalent in the selected currency. Use for catalog, profile, dimension.
Hybrid (soft convert): display in the selected currency + confirmation of the transaction in the account currency (show both).
Operating (hard convert): the user changes the transaction currency (deposit/withdrawal/rate). We need explicit courses, commissions, fixation time.

Rule: by default - display-only, and turn on "hard" conversion only in the corresponding flows (cash, withdrawal, transfer of funds).


3) Controls and placement

Currency switch in the header/profile panel ("₴/€/$" icon or currency code).
Selector: search by code/name/symbol; selected/frequent currencies - top.

Inside the forms (deposit/rate): compact selector to the right of the amount field, next to the hint "≈ equivalent in XXX."

Mobile pattern: bottom sheet with a list and input for filtering.

html
<button aria-haspopup="listbox" aria-expanded="false" class="currency-switch">UAH</button>
<ul role="listbox" class="currency-menu" hidden>
<li role="option" aria-selected="true">UAH — ₴</li>
<li role="option">USD — US$</li>
<li role="option">EUR — €</li>
<li role="option">TRY — ₺</li>
</ul>

4) Formatting and accuracy

Minor units: Store amounts in whole minimum units (pennies, cents, satoshi).

Decimal places by currency:
  • 0: JPY, KRW, CLP
  • 2: USD, EUR, UAH, TRY
  • 3 +: some ZAR (2), KWD (3), crypto (4-8) currencies
  • Cryptocurrencies: Show up to 8 characters (dynamic accuracy, but with a lower bound for readability).
  • Table digits: 'font-variant-numeric: tabular-nums;' for column alignment.
Intl snippet:
js const fmt = (amountMinor, currency, locale) => {
const fraction = { JPY:0, KRW:0, KWD:3 }[currency]?? 2;
return new Intl.NumberFormat(locale, { style:'currency', currency, minimumFractionDigits:fraction, maximumFractionDigits:fraction })
.format(amountMinor / 10fraction);
};
fmt(200000, 'UAH', 'uk-UA'); // 2 000,00 ₴

5) Courses and updates

Source: fix the course provider (internal pricing/bank/FX-API).
Cache: Update courses with reasonable frequency (e.g. every 60-300 seconds) + incremental updates on demand.
Fixation time: Display 'updated N minutes ago' and fixation time at checkout.
Spread/Commission: Show an explicit line: "Rate 1 USD = 36.60 UAH (Spread 1 included. 5%)».
Rounding: bank or regular - select one and fix it in the policy.


6) UX text and explanations

Equivalent: "≈ 52.10 €" - next to the amount, muted color, updated in real time.

Legal caveats: "The actual rate and commission will be recorded in the confirmation step."

Long codes: use tooltips/secondary string: "US $ - US dollar."

Conversion in the basket: do not change the "total" without explanation; show the recount line.


7) Availability (A11y)

'role = "listbox/option" 'at the currency selector.
Keyboard support: arrows, Enter, Type-ahead by code/name.

Reading for SR: "Display currency: UAH - Ukrainian hryvnia."

Color ≠ the only medium of meaning (there is always code/text).
RTL: numbers/codes in 'dir = "ltr"' inside Arabic strings.


8) Performance and caching

Courses - in memory + localStorage with TTL (for example, 5 minutes).
Batch updates: recalculate equivalents in batches (requestAnimationFrame, debunks 100-200 ms).
Do not trigger an extra list rerender when the course fluctuates <threshold (for example, 0.1%).


9) The specifics of iGaming

Account currency - basic reporting (deposits, balance, history).

Rate currency: usually = account currency; if another is specified, show a double block: "Debited X XXX in USD (≈ Y YYY in UAH)."

Settlement Fixing: Winnings are converted at the rate at the time of settlement, not bets - this should be seen in the coupon/history details.
Deposit/withdrawal: rate and commission of PSP/bank - in a separate line; ETA by method.
Responsible play limits: defined in the account currency; If the UI is in a different currency, show both values.
Tournaments and prizes: the prize fund currency is fixed; when displayed, the equivalent is approximate, marked.


10) Antipatterns

"Magic" value change in the input field when switching currency - without explicit consent.
Use of one "$" character without country code.
Hidden commission in the know (no line about spread).
Mixing locale and currency (format by 'en-US' for 'UAH').
Hard precision of "2 characters" for JPY/KRW or "8 characters" for all cryptocurrencies.

Recalculation of historical transactions "retroactively" at the current exchange rate - without the mark "recalculation."


11) Design system tokens (example)

json
{
"currency": {
"default": "UAH",
"displayList": ["UAH","USD","EUR","TRY","PLN","BRL","MXN"],
"fractions": { "JPY":0, "KRW":0, "KWD":3, "BTC":8 },
"showCodeWithSymbol": ["USD","CAD","AUD","NZD"],
"approxPrefix": "≈ "
},
"format": {
"tabularNums": true,
"grouping": "locale",
"negative": "−"
},
"fx": {
"ttlSec": 300,
"changeThresholdPct": 0.1,
"showSpread": true
}
}

12) Snippets

Currency switch (React, context + Intl)

tsx import { createContext, useContext, useState, useMemo } from 'react';

type Cur = 'UAH'    'USD'    'EUR'    'TRY';
const CurrencyCtx = createContext<{cur:Cur,set:(c:Cur)=>void, rate:(from:Cur,to:Cur)=>number}>({cur:'UAH',set:()=>{},rate:()=>1});

export function CurrencyProvider({children}:{children:React.ReactNode}){
const [cur, set] = useState<Cur>('UAH');
// fx: получить из кэша/апи; здесь — заглушка const table = { UAH:{USD:0.027,EUR:0.025,TRY:0.89,UAH:1}, USD:{UAH:36.6,EUR:0.93,TRY:33.0,USD:1}, EUR:{UAH:39.2,USD:1.07,TRY:35.4,EUR:1}, TRY:{UAH:1.12,USD:0.030,EUR:0.028,TRY:1} };
const rate = (from:Cur,to:Cur)=> table[from][to];
const value = useMemo(()=>({cur, set, rate}),[cur]);
return <CurrencyCtx.Provider value={value}>{children}</CurrencyCtx.Provider>;
}

export function useCurrency(){ return useContext(CurrencyCtx); }

export function Money({minor, iso}:{minor:number, iso:Cur}){
const { cur, rate } = useCurrency();
const fraction = { JPY:0, KRW:0, KWD:3 }[cur as any]?? 2;
const v = (minor/10fraction) rate(iso, cur);
return <span style={{fontVariantNumeric:'tabular-nums'}}>{new Intl.NumberFormat(undefined,{style:'currency',currency:cur, minimumFractionDigits:fraction, maximumFractionDigits:fraction}).format(v)}</span>;
}

Dual Display (Operational Conversion)

html
<div class="amount">
<label>Сумма депозита</label>
<div class="row">
<input type="number" inputmode="decimal" aria-describedby="fxnote">
<select aria-label="Валюта операции">
<option>USD</option><option>EUR</option><option>UAH</option>
</select>
</div>
<small id="fxnote">≈ 2 000,00 ₴ · Курс будет зафиксирован на следующем шаге</small>
</div>

13) Metrics

FX latency: time from currency switch to update of all fields (target ≤ 150 ms).
Correctness rate: the share of calls to support for "incorrect amounts" (<0.2%).
Display vs account mismatch: events where the user confuses currencies (lower hints).

CTR course tips: clicks on "More about the course/commission."

Cash discount on conversion: the share of failures associated with a "sudden" change in the amount.


14) QA checklist

Meaning and transparency

  • The currency of the account and/or transaction is visible everywhere.
  • $ shows the country code (US $, CA $, etc.).
  • There is a line about the rate, update date and spread/commission.

Format and accuracy

  • Decimal places by currency (JPY = 0, KWD = 3, crypto = up to 8).
  • The number/currency locale corresponds to the UI language.
  • Historical transactions are not recalculated "at current exchange rate" without marking.

Behavior

  • Currency changeover does not change the entry without confirmation.
  • The "≈" equivalent is updated smoothly and quickly.
  • The currency selector is keyboard-accessible, Type-ahead works.

iGaming-specifics

  • In coupon: charge/win and their currency are signed, fixation rate is indicated.
  • At the till: PSP/bank fees are seen separately.
  • In limits: both values ​ ​ are shown (account and displayed).

RTL/A11y

  • Codes/amounts are read correctly in RTL ('dir = "ltr"' for numbers).
  • Contrast and focus indicators correspond to AA.

15) Documentation in the design system

Components: 'CurrencySwitch', 'Money', 'FxNote', 'DualAmount'.
Precision/rounding policy and single formatting function.

Rules: "when display-only," "when hard-convert," "how to show spread."

Currency reference: code, symbol, digits, regional character collisions.
Gallery Do/Don't: "$ without code," auto jump input, hidden commissions.


Brief summary

Switching currencies is not just a ₴/€/$ selector. This is a clear model of money (account currency vs display vs operation), fair rate with commission, correct formatting by locale and careful behavior of input fields. Fix the rules in the design system, automate the formatting and caching of courses - and users will confidently work with amounts, not doubting the numbers and not losing money on "invisible" spreads.

Contact

Get in Touch

Reach out with any questions or support needs.We are always ready to help!

Start Integration

Email is required. Telegram or WhatsApp — optional.

Your Name optional
Email optional
Subject optional
Message optional
Telegram optional
@
If you include Telegram — we will reply there as well, in addition to Email.
WhatsApp optional
Format: +country code and number (e.g., +380XXXXXXXXX).

By clicking this button, you agree to data processing.