Amount Input

A money field that groups digits as you type, spells the amount out and flags an amount about ten times off the usual one.

StopsExtra zero on a transfer

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/amount-input.json

The files land in your project. They're yours to edit.

Usage

amount-input-usage.tsx
import { AmountInput } from "@/components/ui/amount-input"

<AmountInput
  label="Monthly rent"
  value={rent}
  onValueChange={(next, detail) => {
    setRent(next)
    setExact(detail?.decimal ?? "")
  }}
  typical={25000}
/>

What it catches

  • An extra or missing zero, when the amount is `threshold`× (default 10) or more off the `typical` value you pass, with a one-tap fix that moves the zeros exactly
  • A paste in another locale's format ("1.250,50", "1.250.000", "12,5" in an en-IN field), by asking which reading you meant
  • Hard-to-read long numbers, by grouping digits as you type (Indian grouping for en-IN)
  • Misread amounts, by spelling the amount out in words
  • Leading zeros, and digits beyond `maxDigits` or the currency's precision, with a visible hint when digits are dropped

Props

AmountInput props
PropTypeDefault
label

Visible label.

string"Amount"
value

Controlled amount: a number or an exact decimal string like "1250.50". `null` means empty.

number | string | null—
defaultValue

Initial amount when uncontrolled (number or decimal string).

number | string | nullnull
onValueChange

Called with the parsed number (or `null` when empty) and, when not empty, the exact decimal and minor-unit strings.

(value: number | null, detail?: AmountDetail) => void—
locale

BCP 47 locale used for grouping, e.g. `en-IN` for 12,34,567. Defaults to India.

string"en-IN"
currency

ISO 4217 currency code. Defaults to INR.

string"INR"
words

Number system for the amount in words, or `false` to hide it. Defaults to `indian` for `-IN` locales.

"indian" | "international" | false—
wordsFormatter

Builds the amount in words; defaults to English cheque style. Use it to translate.

(parts: AmountWordsParts) => stringdefaultAmountWords
typical

The usual amount for this field. Amounts `threshold`× above or below it get a warning.

number—
threshold

How many times off `typical` an amount must be to warn. Default 10.

number10
tooHighMessage

Warning text; `ratio` is like "16×", `usual` the formatted typical amount, `zeros` how many zeros it looks off by (0 when not a zero slip).

(ratio: string, usual: string, zeros: number) => string(ratio, usual, zeros) => `That's ${ratio} the usual ${usu…
tooLowMessage

Warning text when the amount is far below the usual one (`zeros` is negative or 0).

(ratio: string, usual: string, zeros: number) => string(ratio, usual, zeros) => `That's ${ratio} the usual ${usu…
fixLabel

Label of the one-tap fix; `amount` is the formatted fixed amount.

(amount: string) => string(amount) => `Use ${amount}`
maxDigits

Most digits accepted before the decimal point. Default 15.

number15
truncatedMessage

Hint shown when typed digits were dropped.

(kind: "digits" | "decimals", limit: number) => string(kind, limit) => kind === "digits" ? `Only ${limit} digit…
pasteMessage

Question shown when a paste could be read two ways.

(pasted: string) => string(pasted) => `You pasted "${pasted}". Which did you mean?`
showCurrencyCode

Show the ISO code after the field. Default true.

booleantrue
readback

Show the amount as an animated figure above the words, so a fix visibly drops or adds the zero. Default true.

booleantrue

* required. Also accepts the native props of <input> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Visible <label> tied to the input
  • aria-describedby lists only the words and notice elements that are currently rendered, plus any id you pass
  • Warnings, the truncation hint and the paste question are in an aria-live="polite" region; the fix and paste buttons return focus to the input
  • The animated read-back respects prefers-reduced-motion (motion's useReducedMotion)

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Validate amount and currency on the server, derive payable amounts from trusted data and use idempotency keys; this warning is a review prompt, not a correctness check.

  • The warning needs a `typical` amount; there is no fallback to a balance, limit or last amount
  • The number passed to onValueChange is a JavaScript float; use the second argument's `decimal` or `minor` string (or the hidden input's decimal string) for exact money maths
  • The paste check runs only when the paste replaces the whole field, and only for '.' and ',' separators
  • Default words are English ("Rupees … only", "paise", English unit names); pass `wordsFormatter` for other languages
  • No negative amounts
  • Defaults to en-IN and INR; set locale and currency for other markets

What it stops

Extra zero on a transfer

Also known as

amount · money input · currency · rupees · ₹ · lakh crore · transfer · send money

Field type

Dependencies

Pairs well with