Skip to content
Devix Open Source

Guide

Getting started

npm install @devix-labs/amount-input
import { createAmountInput } from '@devix-labs/amount-input';

const total = createAmountInput(document.querySelector('#total'), { currency: 'AED' });

total.getValue();   // '1234.56'  — a string, always
total.getMinor();   // '123456'   — whole fils
<script type="module" src="https://devix.pk/cdn/oss/amount-input@1.0.0/amount-input.min.js"></script>

<dx-amount-input currency="SAR" name="total"></dx-amount-input>

The stylesheet is optional. This only ever changes the value in your field.

It is never a float

total.getValue();    // '1234.56'   store this
total.getMinor();    // '123456'    or this
total.getNumber();   // 1234.56     only when you know it is small enough

An amount that has been through a double can be wrong by a fils, and money that is wrong by a fils is the thing an accountant notices. Nothing here becomes a number unless you ask: the state is the digits that were typed, and every value handed back is a string.

getMinor() is exactly what Devix VAT Calculator takes, so the amount on the page and the tax computed from it cannot drift apart:

import { vat } from '@devix-labs/vat-calculator';

vat({ amount: total.getValue(), country: 'AE' });

The decimals come from the currency, not from Intl

Two for the dirham, riyal and rupee. Three for the Bahraini, Kuwaiti, Omani and Jordanian dinar. None for the yen.

createAmountInput(field, { currency: 'KWD' });   // 1.234 — a thousand fils
createAmountInput(field, { currency: 'JPY' });   // 1,234 — and no point at all

This is read from ISO 4217, not from Intl. They disagree for eight currencies, and one of them matters here:

new Intl.NumberFormat('en', { style: 'currency', currency: 'PKR' })
  .resolvedOptions().maximumFractionDigits;   // 0

CLDR says the Pakistani rupee has no decimal places. ISO 4217 says two, and paisa exist. A field built on Intl silently refuses the fraction someone just typed — so the minor unit comes from the standard, with Intl as the fallback for codes it does not list. Everything else — the separators, the digit shapes, the grouping, where the currency sits — comes from Intl, which is authoritative on all of it.

The clean value posts itself

<form method="post">
  <input name="total" id="total">
</form>

The widget moves the field's name onto a hidden input carrying 1234.56, so an ordinary form submit never sends AED 1,234.56 to your server. There is no JavaScript of yours involved, and nothing to strip on the way in.

Pass postRaw: false to turn it off, or rawName to post both.

Typing the cents first

createAmountInput(field, { currency: 'AED', typing: 'cents' });

1 is 0.01, 12 is 0.12, 1234 is 12.34 — the way every card terminal and point-of-sale keypad works, and the way backspace works too. It is react-number-format #366 (+17) and react-input-mask #120 (+25), asked for and never shipped.

Both separators, and any digits

A German typing 1.234,56 and an Emirati typing 1,234.56 mean the same amount, and on a phone the keyboard decides which key you get. Both are read correctly in either locale, and so are Arabic-Indic and Persian digits:

const field = createAmountInput(el, { locale: 'ar-EG', currency: 'EGP' });
// typed: ١٢٣٤٫٥٦   shown: ١٬٢٣٤٫٥٦   stored: '1234.56'

Where two different separator characters appear, the last one is the decimal point — that is true in every locale. Where only one appears it is genuinely ambiguous, and the locale decides: 1,234 in Dubai is a thousand, 1,23 cannot be grouping and so is one and a bit.

The caret

Typing the fourth digit turns 1234 into 1,234 and moves every character after it. The caret here is counted in digits, not characters, so it stays where you are typing. Type into the middle of an amount, or backspace over a separator, and nothing jumps — the same architecture as Devix Masked Input.

The fraction is not padded while you are in the field, because a field that turns 12.5 into 12.50 under the caret is one you cannot type 12.55 into. It is padded when you leave.

Where to go next

Updated 15 Sep 2026