currency-fomatter
The currency toolkit for React — format, parse, and input currencies with zero config.
Unlike other formatting libraries that only give you a component, currency-fomatter gives you 3 layers you can use independently:
| Layer | What | Use case |
|---|---|---|
formatCurrency() / parseCurrency() |
Standalone functions | Node.js, SSR, any JS — no React needed |
useCurrencyInput() / useCurrencyFormat() |
React hooks | Headless — bring your own UI |
<CurrencyFormat /> / <PatternFormat /> |
React components | Drop-in input with full formatting |
npm install currency-fomatter
Why currency-fomatter?
| Feature | currency-fomatter | react-number-format | react-currency-input-field |
|---|---|---|---|
| Standalone utils (no React) | Yes | No | No |
| Format + Parse symmetry | Yes | No | No |
| Compact format (1K, 1M, 1B) | Yes | No | No |
| ISO 4217 currency database | Yes | No | No |
| Auto locale detection (Intl) | Yes | No | Partial |
| React hook (headless) | Yes | No | No |
| IME support (CJK input) | Yes | No | No |
Mobile keyboard (inputMode) |
Auto | Manual | Manual |
| Accessibility (ARIA) | Built-in | No | No |
| Pattern format (phone, card) | Yes | Yes | No |
| Custom input component | Yes | Yes | Yes |
| TypeScript | Yes | Yes | Yes |
| Bundle size | ~8KB | ~7KB | ~10KB |
Quick Start
Just format a number (no React)
import { formatCurrency, parseCurrency } from "currency-fomatter";
formatCurrency(1234567.89, { prefix: "$", thousandSeparator: "," });
// → "$1,234,567.89"
parseCurrency("$1,234,567.89", { prefix: "$", thousandSeparator: "," });
// → { value: "1234567.89", floatValue: 1234567.89 }
Auto-config from currency code (ISO 4217)
import { getCurrencyConfig, formatCurrency } from "currency-fomatter";
const usd = getCurrencyConfig("USD"); // { prefix: "$", decimalScale: 2, ... }
const jpy = getCurrencyConfig("JPY"); // { prefix: "¥", decimalScale: 0, ... }
const vnd = getCurrencyConfig("VND"); // { suffix: " ₫", decimalScale: 0, ... }
formatCurrency(1234567, getCurrencyConfig("EUR"));
// → "1,234,567.00 €"
50+ currencies built-in. Unknown currencies fall back to Intl.NumberFormat.
React component
import { CurrencyFormat } from "currency-fomatter";
<CurrencyFormat
value={1234.56}
prefix="$"
thousandSeparator=","
decimalScale={2}
fixedDecimalScale
onValueChange={(values) => {
console.log(values.floatValue); // 1234.56
}}
/>
React hook — headless, works with ANY input
import { useCurrencyInput } from "currency-fomatter";
function PriceInput() {
const { value, formattedValue, getInputProps } = useCurrencyInput({
currency: "USD", // Auto-configures $, 2 decimals
initialValue: 1234.56,
});
// Works with plain <input>, Material UI, Chakra, Ant Design — anything
return <input {...getInputProps()} />;
}
Pattern format (phone, card, date)
import { PatternFormat } from "currency-fomatter";
<PatternFormat format="+1 (###) ###-####" mask="_" />
// → +1 (555) 123-4567
<PatternFormat format="#### #### #### ####" mask="_" />
// → 4111 1111 1111 1111
Standalone Utilities
formatCurrency
import { formatCurrency } from "currency-fomatter";
// Basic
formatCurrency(1234567.89);
// → "1,234,567.89"
// With options
formatCurrency(1234567.89, {
prefix: "$",
decimalScale: 2,
fixedDecimalScale: true,
thousandSeparator: ",",
});
// → "$1,234,567.89"
// Indian number system
formatCurrency(1234567, { prefix: "₹", thousandSpacing: "2s" });
// → "₹12,34,567"
parseCurrency
Round-trip parsing — the inverse of formatCurrency:
import { parseCurrency } from "currency-fomatter";
parseCurrency("$1,234.56", { prefix: "$", thousandSeparator: "," });
// → { value: "1234.56", floatValue: 1234.56, formattedValue: "$1,234.56" }
// Euro format
parseCurrency("1.234,56€", {
suffix: "€",
thousandSeparator: ".",
decimalSeparator: ",",
});
// → { floatValue: 1234.56 }
formatCompact / parseCompact
Compact number notation (1K, 1M, 1B) with full round-trip support:
import { formatCompact, parseCompact } from "currency-fomatter";
formatCompact(1234567); // → "1.23M"
formatCompact(2500000000); // → "2.5B"
formatCompact(1500000, { prefix: "$", decimalScale: 1 }); // → "$1.5M"
// Vietnamese
formatCompact(1500000000, {
compactDisplay: { thousand: " nghìn", million: " triệu", billion: " tỷ" },
suffix: " ₫",
});
// → "1.5 tỷ ₫"
// Parse back
parseCompact("2.5M"); // → { value: "2500000", floatValue: 2500000 }
getCurrencyConfig
Auto-configure formatting from ISO 4217 currency codes:
import { getCurrencyConfig } from "currency-fomatter";
getCurrencyConfig("USD");
// → { prefix: "$", decimalScale: 2, fixedDecimalScale: true, thousandSeparator: ",", decimalSeparator: "." }
getCurrencyConfig("JPY");
// → { prefix: "¥", decimalScale: 0, ... }
getCurrencyConfig("VND");
// → { suffix: " ₫", decimalScale: 0, ... }
// Use with component
<CurrencyFormat value={1234.56} {...getCurrencyConfig("GBP")} />
// → £1,234.56
Locale Support
Auto-detect from browser
import { getAutoLocaleConfig } from "currency-fomatter";
// Detects user's browser locale automatically
const config = getAutoLocaleConfig("USD");
<CurrencyFormat value={1234.56} {...config} />
Any locale via Intl.NumberFormat
import { detectLocaleFormat, createLocaleConfig, formatWithIntl } from "currency-fomatter";
// Thai
detectLocaleFormat("th-TH", "THB");
// → { thousandSeparator: ",", decimalSeparator: ".", prefix: "฿", ... }
// Arabic
detectLocaleFormat("ar-SA", "SAR");
// Direct Intl formatting
formatWithIntl(1234567.89, "de-DE", { style: "currency", currency: "EUR" });
// → "1.234.567,89 €"
Static presets
Pre-configured: en-US, vi-VN, de-DE, ja-JP, en-IN, fr-FR, zh-CN, ko-KR, pt-BR, en-GB
import { getLocaleConfig, getFormatOptionsFromLocale } from "currency-fomatter";
<CurrencyFormat value={1234567} {...getFormatOptionsFromLocale("vi-VN")} />
// → 1.234.567 ₫
Custom locale registry
import { registerLocale, unregisterLocale } from "currency-fomatter";
registerLocale("bitcoin", {
locale: "bitcoin",
prefix: "₿ ",
thousandSeparator: " ",
decimalSeparator: ".",
decimalScale: 8,
});
// Use it anywhere
<CurrencyFormat value={0.00123456} {...getFormatOptionsFromLocale("bitcoin")} />
unregisterLocale("bitcoin");
Hooks
useCurrencyInput (Headless)
A truly headless hook that works with any <input> element. No dependency on <CurrencyFormat />.
import { useCurrencyInput } from "currency-fomatter";
function PriceInput() {
const { value, formattedValue, getInputProps, setValue, reset, clear } = useCurrencyInput({
currency: "USD", // Auto-configures prefix, decimals from ISO 4217
initialValue: 1000,
onValueChange: (values) => console.log(values.floatValue),
});
return (
<div>
<input {...getInputProps()} />
<p>Raw: {value} | Display: {formattedValue}</p>
<button onClick={() => setValue(2000)}>Set $2000</button>
<button onClick={reset}>Reset</button>
<button onClick={clear}>Clear</button>
</div>
);
}
Works with any UI library:
// Material UI
<TextField {...getInputProps()} label="Price" />
// Chakra UI
<Input {...getInputProps()} />
// Ant Design
<AntInput {...getInputProps()} />
Options: currency, locale, initialValue, onValueChange, plus all FormatCurrencyOptions (prefix, suffix, decimalScale, etc.). Direct props override currency/locale defaults.
useCurrencyFormat
Higher-level hook that returns props for the <CurrencyFormat /> component:
import { useCurrencyFormat, CurrencyFormat } from "currency-fomatter";
function PriceInput() {
const { value, formattedValue, inputProps, reset, clear } = useCurrencyFormat({
locale: "en-US",
initialValue: 1000,
});
return (
<div>
<CurrencyFormat {...inputProps} />
<p>Raw: {value} | Display: {formattedValue}</p>
</div>
);
}
PatternFormat
A focused component for pattern-based formatting (phone, card, date). Cleaner API than using CurrencyFormat with format prop.
import { PatternFormat } from "currency-fomatter";
// Phone number
<PatternFormat format="+1 (###) ###-####" mask="_" />
// Credit card
<PatternFormat format="#### #### #### ####" mask="_" />
// Date with per-position masks
<PatternFormat format="##/##/####" mask={["D","D","M","M","Y","Y","Y","Y"]} />
// Show mask on empty
<PatternFormat format="+1 (###) ###-####" mask="_" allowEmptyFormatting />
// → +1 (___) ___-____
PatternFormat accepts: format, mask, allowEmptyFormatting, value, defaultValue, onValueChange, displayType, customInput, and all standard input props.
Component API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value |
string | number |
— | Controlled input value |
defaultValue |
string | number |
— | Uncontrolled initial value |
format |
string | Function |
— | Pattern ("+1 (###) ###-####") or custom format function |
decimalScale |
number |
— | Max decimal places |
decimalSeparator |
string |
"." |
Decimal separator |
thousandSeparator |
string | boolean |
"," |
Thousand separator (true = ",") |
thousandSpacing |
"2" | "2s" | "3" | "4" |
"3" |
Grouping pattern |
thousandsGroupStyle |
"thousand" | "lakh" | "wan" | "none" |
— | Human-readable grouping alias |
mask |
string | string[] |
" " |
Mask for empty format positions |
prefix |
string |
"" |
Text before number ("$") |
suffix |
string |
"" |
Text after number ("%") |
allowNegative |
boolean |
true |
Allow negative values |
allowEmptyFormatting |
boolean |
false |
Show prefix/suffix when empty |
allowedDecimalSeparators |
string[] |
— | Extra keys treated as decimal separator |
fixedDecimalScale |
boolean |
false |
Always show decimal places |
isNumericString |
boolean |
false |
Treat value as numeric string |
isAllowed |
(values) => boolean |
— | Custom validation |
onValueChange |
(values, sourceInfo) => void |
— | Value change callback |
inputMode |
"numeric" | "decimal" | "text" |
auto | Mobile keyboard type (auto-detected) |
displayType |
"input" | "text" |
"input" |
Render mode |
customInput |
ComponentType |
— | Custom input component |
renderText |
(value, props) => ReactNode |
— | Custom text renderer |
type |
"text" | "tel" |
"text" |
Input type |
name |
string |
— | Field name |
getInputRef |
(el) => void |
— | Get input element ref |
thousandsGroupStyle
Human-readable alias for thousandSpacing:
| Style | Equivalent | Example |
|---|---|---|
"thousand" |
"3" |
1,234,567 |
"lakh" |
"2s" |
12,34,567 |
"wan" |
"4" |
123,4567 |
"none" |
— | 1234567 |
allowedDecimalSeparators
Accept multiple keys as decimal input (useful for European keyboards):
<CurrencyFormat
decimalSeparator=","
allowedDecimalSeparators={[",", "."]}
// User can press either . or , to type a decimal
/>
ValueObject
interface ValueObject {
formattedValue: string; // "$1,234.56"
value: string; // "1234.56"
floatValue: number; // 1234.56
name?: string; // Field name if provided
}
onValueChange sourceInfo
onValueChange={(values, sourceInfo) => {
// sourceInfo.source: "event" (user typed) | "prop" (value prop changed)
// sourceInfo.event: the original DOM event (when source is "event")
if (sourceInfo?.source === "event") {
// Only react to user input, not programmatic changes
}
}}
Examples
Phone number
<PatternFormat format="+1 (###) ###-####" mask="_" />
// → +1 (555) 123-4567
Credit card
<PatternFormat format="#### #### #### ####" mask="_" />
// → 4111 1111 1111 1111
Percentage with max
<CurrencyFormat
suffix="%"
decimalScale={2}
isAllowed={(values) => (values.floatValue ?? 0) <= 100}
/>
Display as text
<CurrencyFormat
value={9999.99}
displayType="text"
thousandSeparator=","
prefix="$"
/>
// Renders: <span role="status" aria-live="polite">$9,999.99</span>
Custom input (Material UI, etc.)
<CurrencyFormat
value={1000}
customInput={TextField}
thousandSeparator=","
prefix="$"
/>
Form Library Integration
react-hook-form
import { useForm } from "react-hook-form";
import { CurrencyFormat } from "currency-fomatter";
function MyForm() {
const { register, handleSubmit, setValue } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<CurrencyFormat
{...register("price")}
thousandSeparator=","
prefix="$"
onValueChange={(values, sourceInfo) => {
if (sourceInfo?.source === "event") {
setValue("price", values.floatValue);
}
}}
/>
</form>
);
}
Accessibility
inputModeauto-detected for mobile keyboards (numeric/decimal)displayType="text"renders withrole="status"andaria-live="polite"- All
aria-*props passed through to the input element - IME composition support for CJK (Chinese, Japanese, Korean) input
- Home/End keys respect prefix/suffix boundaries
- Disabled/readOnly states properly handled
TypeScript
Full type exports available:
import type {
CurrencyFormatProps,
PatternFormatProps,
ThousandsGroupStyle,
ValueObject,
ThousandSpacing,
FormatCurrencyOptions,
ParseCurrencyOptions,
CompactDisplayOptions,
FormatCompactOptions,
UseCurrencyFormatOptions,
UseCurrencyFormatReturn,
UseCurrencyInputOptions,
UseCurrencyInputReturn,
LocaleConfig,
CurrencyInfo,
FormatFunction,
RemoveFormattingFunction,
IsAllowedFunction,
OnValueChangeFunction,
RenderTextFunction,
} from "currency-fomatter";
License
MIT