Intl.NumberFormat

Published on

You reach for a library to format 1234.5 as $1,234.50. You don't need one. Intl.NumberFormat ships in every browser and Node, handles currencies, units, percentages, and compact notation — and respects the user's locale for free.

Intl.NumberFormat helpers

type FormatOptions = Intl.NumberFormatOptions; const format = (value: number, options: FormatOptions, locale = 'en-US') => new Intl.NumberFormat(locale, options).format(value); // Currency — respects locale-specific symbol placement and decimals export const formatCurrency = (value: number, currency = 'USD', locale?: string) => format(value, { style: 'currency', currency }, locale); // Units — kilograms, miles, liters, bytes, and dozens more export const formatUnit = (value: number, unit: string, locale?: string) => format(value, { style: 'unit', unit, unitDisplay: 'short' }, locale); // Percent — expects 0.15, not 15 export const formatPercent = (value: number, fractionDigits = 0, locale?: string) => format( value, { style: 'percent', minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits, }, locale, ); // Compact — "1.2K", "3.4M", "1.2B" export const formatCompact = (value: number, locale?: string) => format(value, { notation: 'compact', maximumFractionDigits: 1 }, locale);

Usage

formatCurrency(1234.5); // "$1,234.50" formatCurrency(1234.5, 'EUR', 'de'); // "1.234,50 €" formatCurrency(1234.5, 'JPY', 'ja'); // "¥1,235" formatUnit(72, 'kilogram'); // "72 kg" formatUnit(150, 'mile-per-hour'); // "150 mph" formatUnit(1_500_000, 'byte', 'en'); // "1,500,000 byte" formatPercent(0.1523, 1); // "15.2%" formatPercent(0.5); // "50%" formatCompact(1234); // "1.2K" formatCompact(3_400_000); // "3.4M" formatCompact(9_800_000_000, 'de'); // "9,8 Mrd."

When to use Intl.NumberFormat

One API, four jobs. Every number formatting task in most apps — prices, stats, dashboards, charts — collapses into Intl.NumberFormat options. For most UI formatting, you can skip numbro, currency.js, or d3-format entirely. Reach for them only when you need their specific extras: arithmetic on money (dinero.js), arbitrary precision (big.js), or parsing user input back into numbers.

Locale-aware by default. Pass "de" and commas become periods, currency symbols move, decimal conventions flip. A library would need a separate plugin for each locale; the browser already has them.

style: "unit" is the hidden gem. It supports 40+ simple units — kilogram, mile-per-hour, liter, byte, celsius — plus X-per-Y compounds like meter-per-second. Unit names must come from the sanctioned list; arbitrary strings throw.

Reuse the formatter in hot paths. new Intl.NumberFormat(...) isn't free. If you format thousands of values (table cells, chart ticks), hoist one instance and call .format() in the loop — skip the helper.

const priceFmt = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }); rows.forEach((r) => (r.display = priceFmt.format(r.amount)));

Gotchas

  • Percent expects a ratio. formatPercent(15) gives "1,500%". Divide by 100 first if your input is already a percent.
  • Currency code is required with style: 'currency'. Omitting it throws TypeError.
  • Unit names are case-sensitive and must match the sanctioned list exactly. "Kilogram" throws RangeError.

Use Intl.NumberFormat whenever you format numbers for humans — prices, metrics, durations, file sizes. Reach for a library only when you need parsing (Intl parses nothing) or exotic formats it doesn't cover. For everything else, the browser already did the work.

Pair with Group Array by Key when building tables, or Copy to Clipboard to let users copy formatted values.