Open for work Front-end engineer building fintech interfaces that convert — start a project
166 currencies · live mid-market rates

Currency conversion that feels instant.

A drop-in converter widget for money-transfer products. Type an amount and the rate, the fee and the recipient's total resolve in the same frame — no spinners, no round-trip per keystroke, no framework.

Zero dependencies ~19 KB minified Keyboard accessible Works offline
Transfer rate

1 USD = 0.0000 BDT

Fetching the live rate…
United States Dollar

USD

Bangladeshi Taka

BDT

  • Our fee (0.48%)
  • Amount we'll convert
  • Guaranteed rate
You could save versus a typical bank

Indicative mid-market rates. No sign-up, no card details.

Every corridor your product needs

0

Currencies, with flags and names

0

Runtime dependencies

0 KB

Minified widget payload

0 fetch

Network call per session, not per keystroke

What's inside

Everything a transfer flow actually needs

Not a toy converter. This is the quoting surface of a remittance product — the part customers stare at before they trust you with their money.

Live mid-market rates

Rates come from public mid-market feeds and are cross-calculated through a USD base, so any of the 166 currencies can quote against any other.

166 × 165 pairs

Quote from either side

Type what you want to send, or type what the recipient must receive. The other field solves for it instantly, with the spread applied in the correct direction.

bidirectional

Search by country, not code

People know "Philippines", not "PHP". Every currency is indexed against the countries that use it, so both spellings land on the same result.

country index

Real flags, everywhere

Vector flags for 161 currencies, lazy-loaded from a CDN, with a typographic monogram fallback for supranational codes like XOF and XDR.

SVG · lazy

Fees shown line by line

The margin, the amount actually converted and the guaranteed rate are printed above the button — the transparency pattern that wins fintech trust.

0.48% spread

It never breaks in front of a customer

Two independent rate providers, a six-hour local cache and a bundled offline snapshot. If every feed is down, the widget still quotes and says so.

3-tier failover

Configured from the markup

One data-currencies attribute restricts the picker to the corridors you are licensed for. Leave it off and you get all 166.

data-currencies

Keyboard and screen-reader ready

The pickers are proper comboboxes: arrow keys to move, Enter to pick, Escape to dismiss, with labelled inputs and live ARIA state throughout.

combobox · listbox

Drops into any stack

It is one <div> and two script tags. No bundler, no npm install, no React version to satisfy — WordPress, Webflow, Laravel or Next.js all the same.

framework-free
The mid-market rate

The rate you see on Google — then one honest margin.

Most transfer tools hide their profit inside a worse exchange rate, so the customer can never work out what they paid. This widget does the opposite: it quotes the real mid-market rate, then shows the margin as a separate line item.

  • 0.48% margin, printed on screen. Taken off the send amount and shown as a fee before the customer commits.
  • Four decimal places — more when the pair needs them. Thin-value pairs like BDT → USD earn extra places automatically, because two decimals rounds real money away.
  • Exact reverse quoting. The reverse spread is the true inverse of the forward one, so quoting from either field lands on the same number.
  • A savings figure that means something. Benchmarked against a 2.5% bank markup — the number is configurable, not decorative.
On a 2,500 transfer
This widget
Typical bank
Exchange rate used
Mid-market
Mid-market −2.5%
Margin applied
0.48%
~2.50%
Cost of the transfer
12.00
62.50
Fee visible before you commit
Every line
Buried in the rate
Rate precision
4 decimals
2 decimals
Quote from the receive side
Yes
Rarely

Illustrative comparison. The 0.48% column is this widget's configured spread; the bank column uses a 2.5% markup, a figure commonly published in retail FX research.

How it works

Live on your page in about ten minutes

1

Paste the markup

Copy the widget block and two script tags into your page. There is no build step and nothing to install — it renders the moment the parser reaches it.

2

Declare your corridors

List the currencies you are licensed to serve in data-currencies, and set the opening pair on the two currency buttons. That is the whole configuration.

3

Wire up the button

The quote button is yours: hand the send amount, receive amount and locked rate to your onboarding flow, your CRM, or a checkout — whatever comes next.

01 · input
Amount & pair

Keystrokes are parsed and normalised locally — no network on the typing path.

02 · engine
Rate resolution

Provider → cache → offline snapshot. The first tier that answers wins.

03 · pricing
Spread & fee

The 0.48% margin is applied and broken out as its own visible line.

04 · output
Quote rendered

Both amounts, the guaranteed rate and the savings estimate paint together.

For developers

One div. Two script tags. No opinions about your stack.

The whole widget is vanilla JavaScript in an IIFE — nothing leaks into the global scope but a single FXConverter handle for you to drive it from.

<!-- 1. drop the widget wherever the quote belongs -->
<div class="widget_container" id="widget_container"
     data-currencies="USD,EUR,GBP,CAD,AUD,BDT,INR,PKR,PHP,NGN">
  <!-- markup from snippet.html -->
</div>

<!-- 2. two scripts, no bundler, no npm install -->
<script src="assets/js/currency-data.js"></script>
<script src="assets/js/converter.js"></script>
/* Offer every currency the rate feed returns — omit the attribute. */
<div class="widget_container"></div>

/* …or restrict it to the corridors you are licensed for. */
<div class="widget_container"
     data-currencies="CAD,BDT,INR,PKR,PHP"></div>

/* The opening pair lives on the two currency buttons. */
<div class="input_holder_right" data-selected_currency="CAD">
<div class="input_holder_right" data-selected_currency="BDT">

/* Every extra is optional. Ship without them and the widget
   falls back to the original 2022 layout, untouched. */
<button data-swap>        // swap the pair
<b data-fee>               // margin line
<b data-quoted-rate>       // locked rate line
<span data-freshness>       // live / cached / offline badge
// The commercial numbers live in one object — change them here.
var CONFIG = {
  spread:         0.0048,      // 0.48% margin off the send amount
  inverse_spread: 0.00482315,  // exact inverse, for reverse quotes
  bank_markup:    0.025,       // benchmark for the savings estimate
  rate_decimals:  4,
  cache_ttl:      6 * 60 * 60 * 1000,
  popular:        ["USD", "EUR", "GBP", "CAD", "BDT"]
};

// Forward quote
receive = send * (1 - CONFIG.spread) * rate;

// Reverse quote — the inverse spread keeps both directions consistent
send    = receive * (1 + CONFIG.inverse_spread) / rate;
// Providers are tried in order. The widget never renders broken.
var providers = [
  { name: "exchangerate-api", url: "https://open.er-api.com/v6/latest/USD" },
  { name: "currency-api",     url: "https://cdn.jsdelivr.net/…/usd.json" }
];

// live provider  →  localStorage cache  →  bundled snapshot
FXConverter.engine.load().then(function (table) {
  console.log(table.source);    // "live" | "cache" | "offline"
  console.log(table.provider);  // which feed answered
});

// Any pair cross-calculates through the USD base
FXConverter.engine.rate("CAD", "BDT");   // => 88.12…

One request, not one per keystroke

The original build fetched two rate tables on every input event. The engine now resolves one USD-based table per session and cross-calculates locally.

Backwards compatible

The class names, data-role hooks and pricing maths from the 2022 deliverable are untouched, so the original markup still runs on the new engine.

Nothing on the global scope

Everything is wrapped in an IIFE. The only export is window.FXConverter, which exposes the rate engine and the pricing config.

Coverage

166 currencies, indexed by country

Try it the way your customers will — search "Philippines", "taka" or "NGN" and watch the same index the widget's picker uses do the work.

The project

From a one-paragraph brief to a shipped widget

One of the first serious front-end builds I took on as a freelancer — and still the one I reach for when a client asks what production-grade vanilla JavaScript looks like.

What the client asked for

  • A converter for their remittance site that felt like the one on Wise's homepage.
  • Embeddable in an existing CMS page — so no framework, no build pipeline, no npm.
  • Only the corridors they were licensed for, editable by a non-developer.
  • Their own margin and savings figures, changeable without touching the layout.
RoleSole front-end dev
StackVanilla JS · CSS
Shipped2022

What I shipped — and what I changed since

  • A self-contained widget in an IIFE module pattern, driven entirely by data-* attributes so the client could reconfigure it themselves.
  • A currency-to-country index so search works on the word customers actually type, backed by flag rendering for every supported market.
  • Rebuilt for this showcase: the single-vendor rate call became a three-tier engine, and the per-keystroke fetch became one request per session.
  • The original 2022 deliverable is still in the repo, unchanged — open the bare widget.
Rate feeds3 tiers
Payload~19 KB
DependenciesNone

ASM Asaduzzaman

Front-end engineer · fintech & product interfaces

Vanilla JSReactTypeScript CSS architectureAccessibilityDesign systems
Who built this

I build the screens where people decide to trust you.

Quote widgets, pricing tables, onboarding flows, dashboards — the interfaces that carry the money and the doubt. I care about the parts users feel but never name: the field that recalculates before you finish typing, the dropdown that finds "Philippines" when you meant PHP, the state that still renders when the API is down.

Questions

The things clients ask first

Public mid-market feeds — exchangerate-api first, a jsDelivr-hosted currency API as the backup. Both are keyless and CORS-enabled, so there is no secret to leak in front-end code. Swapping in a paid or in-house feed means editing one array.

The widget falls back to a six-hour local cache, and then to a rate snapshot bundled with the file itself. The badge above the fields changes from "live" to "cached" or "offline snapshot" so nobody is quoted a stale number without being told.

Yes — they are three numbers at the top of converter.js: the spread, its inverse for reverse quoting, and the bank markup used for the savings line. Nothing else in the file needs to change.

All three. It is a plain <div> plus two script tags with no global pollution and no framework assumptions. In React, render the markup and let the script initialise it on mount — there is no virtual-DOM conflict because the widget owns only its own subtree.

Put a comma-separated list in data-currencies on the widget container. Anything outside that list disappears from both pickers and from search. Leave the attribute off entirely and the widget offers every currency the feed returns.

No. FXKit is the front-end quoting surface only — a portfolio demonstration of the interface layer. It moves no money, holds no funds and is not a licensed payments provider. The rates shown are indicative and are not an offer to transact.

That is what this page is for. Quote widgets, pricing calculators, onboarding flows, dashboards — in vanilla JavaScript when the constraint calls for it, or React and TypeScript when the product is bigger. Tell me what the screen has to do and I will come back with an approach.

Let's build

Got an interface that has to earn trust?

Send me the brief — even a rough one. You will get a straight answer about scope, approach and whether I am the right person for it.