Numbers and Currencies
The @internationalized/number package provides tools for formatting numbers,
currencies, and units in a locale-aware way. It builds on the native Intl.NumberFormat
API while adding additional features and fixing inconsistencies across browsers.
Installation
npm install @internationalized/number
npm
yarn
pnpm
bun
NumberFormatter
The NumberFormatter
class is the primary way to format numbers, currencies, and units.
Basic Number Formatting
import { NumberFormatter } from "@internationalized/number";
const formatter = new NumberFormatter("en-US", {
style: "decimal", // "decimal" | "percent" | "currency" | "unit"
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
console.log(formatter.format(1234.567)); // "1,234.57"
Currency Formatting
const currencyFormatter = new NumberFormatter("en-US", {
style: "currency",
currency: "USD",
});
console.log(currencyFormatter.format(1234.56)); // "$1,234.56"
Percentage Formatting
const percentFormatter = new NumberFormatter("en-US", {
style: "percent",
maximumFractionDigits: 1,
});
console.log(percentFormatter.format(0.456)); // "45.6%"
Unit Formatting
const unitFormatter = new NumberFormatter("en-US", {
style: "unit",
unit: "kilometer-per-hour",
});
console.log(unitFormatter.format(60)); // "60 km/h"
NumberParser
The NumberParser class helps parse user input into numbers while respecting locale-specific formatting (e.g., decimal separators).
import { NumberParser } from "@internationalized/number";
const parser = new NumberParser("en-US");
console.log(parser.parse("1,234.56")); // 1234.56
const germanParser = new NumberParser("de-DE");
console.log(germanParser.parse("1.234,56")); // 1234.56 (in German, comma is the decimal separator)
Number Utilities
NumberFormatOptions
The package provides TypeScript types for number formatting options:
import type { NumberFormatOptions } from "@internationalized/number";
const options: NumberFormatOptions = {
style: "currency",
currency: "EUR",
currencyDisplay: "symbol", // "symbol" | "narrowSymbol" | "code" | "name"
};
Currency & Unit Types
Predefined types for supported currencies and units:
import type { Currency, Unit } from "@internationalized/number";
const currency: Currency = "JPY"; // Autocomplete for supported currencies
const unit: Unit = "kilogram"; // Autocomplete for supported units
Common Use Cases
Formatting User Input
const formatter = new NumberFormatter("en-US", { style: "decimal" });
const parser = new NumberParser("en-US");
function formatInput(value: string) {
const number = parser.parse(value);
return isNaN(number) ? "" : formatter.format(number);
}
console.log(formatInput("1234.567")); // "1,234.567"
Dynamic Currency Display
function formatCurrency(value: number, currency: string, locale: string) {
const formatter = new NumberFormatter(locale, {
style: "currency",
currency,
});
return formatter.format(value);
}
console.log(formatCurrency(99.99, "EUR", "fr-FR")); // "99,99 €"
console.log(formatCurrency(99.99, "JPY", "ja-JP")); // "¥100" (Yen has no decimals)
Localized Unit Conversion
function formatSpeed(speedKmh: number, locale: string) {
const formatter = new NumberFormatter(locale, {
style: "unit",
unit: "kilometer-per-hour",
});
return formatter.format(speedKmh);
}
console.log(formatSpeed(60, "en-US")); // "60 km/h"
console.log(formatSpeed(60, "de-DE")); // "60 km/h" (but formatted with German conventions)
Common Gotchas
- Locale-Specific Formatting:
- Decimal and grouping separators vary by locale (1,234.56 vs 1.234,56).
- Always use NumberParser when reading user input.
- Currency Rounding:
- Some currencies (like JPY) don’t use decimals.
- Performance:
- Reuse NumberFormatter instances instead of creating new ones in render loops.
- Unit Variations:
- Some units may display differently in certain locales (e.g., “L” vs “ℓ” for liters).
- Fallback Locales:
- If a locale isn’t supported, the browser may fall back to a default.
Advanced Usage
Custom Formatting with NumberFormatOptions
const customFormatter = new NumberFormatter("en-US", {
style: "currency",
currency: "USD",
currencyDisplay: "narrowSymbol", // "$" instead of "US$"
notation: "compact", // "compact" | "scientific" | "engineering"
compactDisplay: "short", // "short" | "long"
});
console.log(customFormatter.format(1_000_000)); // "$1M"
Sign Display Options
const accountingFormatter = new NumberFormatter("en-US", {
style: "currency",
currency: "USD",
signDisplay: "exceptZero", // "auto" | "never" | "always" | "exceptZero"
});
console.log(accountingFormatter.format(100)); // "+$100.00"
console.log(accountingFormatter.format(0)); // "$0.00"
Range Formatting
const rangeFormatter = new NumberFormatter("en-US", {
style: "currency",
currency: "EUR",
});
console.log(rangeFormatter.formatRange(100, 200)); // "€100.00 – €200.00"