The Intl.NumberFormat API provides locale-aware number formatting for currencies, percentages, and more. Here's how to use it.
Basic Usage#
1// Default formatting
2const formatter = new Intl.NumberFormat();
3console.log(formatter.format(1234567.89)); // '1,234,567.89' (US English)
4
5// Specific locale
6const deFormatter = new Intl.NumberFormat('de-DE');
7console.log(deFormatter.format(1234567.89)); // '1.234.567,89'
8
9const frFormatter = new Intl.NumberFormat('fr-FR');
10console.log(frFormatter.format(1234567.89)); // '1 234 567,89'
11
12// Multiple locales (fallback)
13const formatter = new Intl.NumberFormat(['ban', 'id']);
14// Uses Indonesian if Balinese not availableCurrency Formatting#
1// Basic currency
2const usdFormatter = new Intl.NumberFormat('en-US', {
3 style: 'currency',
4 currency: 'USD',
5});
6console.log(usdFormatter.format(1234.56)); // '$1,234.56'
7
8// Different currencies
9const eurFormatter = new Intl.NumberFormat('de-DE', {
10 style: 'currency',
11 currency: 'EUR',
12});
13console.log(eurFormatter.format(1234.56)); // '1.234,56 €'
14
15const jpyFormatter = new Intl.NumberFormat('ja-JP', {
16 style: 'currency',
17 currency: 'JPY',
18});
19console.log(jpyFormatter.format(1234)); // '¥1,234'
20
21// Currency display options
22const options = {
23 style: 'currency',
24 currency: 'USD',
25 currencyDisplay: 'code', // 'symbol' | 'narrowSymbol' | 'code' | 'name'
26};
27
28new Intl.NumberFormat('en-US', { ...options, currencyDisplay: 'symbol' })
29 .format(1234); // '$1,234.00'
30new Intl.NumberFormat('en-US', { ...options, currencyDisplay: 'code' })
31 .format(1234); // 'USD 1,234.00'
32new Intl.NumberFormat('en-US', { ...options, currencyDisplay: 'name' })
33 .format(1234); // '1,234.00 US dollars'Percentage Formatting#
1// Basic percentage
2const percentFormatter = new Intl.NumberFormat('en-US', {
3 style: 'percent',
4});
5console.log(percentFormatter.format(0.25)); // '25%'
6
7// With decimals
8const precisePercent = new Intl.NumberFormat('en-US', {
9 style: 'percent',
10 minimumFractionDigits: 2,
11 maximumFractionDigits: 2,
12});
13console.log(precisePercent.format(0.1234)); // '12.34%'
14
15// Different locales
16const dePercent = new Intl.NumberFormat('de-DE', {
17 style: 'percent',
18});
19console.log(dePercent.format(0.25)); // '25 %'Unit Formatting#
1// Basic unit
2const meterFormatter = new Intl.NumberFormat('en-US', {
3 style: 'unit',
4 unit: 'meter',
5});
6console.log(meterFormatter.format(100)); // '100 m'
7
8// Compound units
9const speedFormatter = new Intl.NumberFormat('en-US', {
10 style: 'unit',
11 unit: 'kilometer-per-hour',
12});
13console.log(speedFormatter.format(100)); // '100 km/h'
14
15// Unit display options
16const options = {
17 style: 'unit',
18 unit: 'liter',
19};
20
21new Intl.NumberFormat('en-US', { ...options, unitDisplay: 'short' })
22 .format(5); // '5 L'
23new Intl.NumberFormat('en-US', { ...options, unitDisplay: 'long' })
24 .format(5); // '5 liters'
25new Intl.NumberFormat('en-US', { ...options, unitDisplay: 'narrow' })
26 .format(5); // '5L'
27
28// Common units
29const units = [
30 'byte', 'kilobyte', 'megabyte', 'gigabyte',
31 'celsius', 'fahrenheit',
32 'meter', 'kilometer', 'mile',
33 'gram', 'kilogram', 'pound',
34 'liter', 'gallon',
35 'second', 'minute', 'hour', 'day',
36];Decimal Options#
1// Minimum/maximum integer digits
2const intFormatter = new Intl.NumberFormat('en-US', {
3 minimumIntegerDigits: 4,
4});
5console.log(intFormatter.format(42)); // '0,042'
6
7// Minimum/maximum fraction digits
8const fractionFormatter = new Intl.NumberFormat('en-US', {
9 minimumFractionDigits: 2,
10 maximumFractionDigits: 4,
11});
12console.log(fractionFormatter.format(1.5)); // '1.50'
13console.log(fractionFormatter.format(1.12345)); // '1.1235'
14
15// Significant digits
16const sigFormatter = new Intl.NumberFormat('en-US', {
17 minimumSignificantDigits: 3,
18 maximumSignificantDigits: 5,
19});
20console.log(sigFormatter.format(1.2)); // '1.20'
21console.log(sigFormatter.format(12345.6)); // '12,346'Notation Options#
1// Scientific notation
2const sciFormatter = new Intl.NumberFormat('en-US', {
3 notation: 'scientific',
4});
5console.log(sciFormatter.format(1234567)); // '1.235E6'
6
7// Engineering notation
8const engFormatter = new Intl.NumberFormat('en-US', {
9 notation: 'engineering',
10});
11console.log(engFormatter.format(1234567)); // '1.235E6'
12
13// Compact notation (K, M, B)
14const compactFormatter = new Intl.NumberFormat('en-US', {
15 notation: 'compact',
16});
17console.log(compactFormatter.format(1234)); // '1.2K'
18console.log(compactFormatter.format(1234567)); // '1.2M'
19console.log(compactFormatter.format(1234567890)); // '1.2B'
20
21// Compact with display option
22const longCompact = new Intl.NumberFormat('en-US', {
23 notation: 'compact',
24 compactDisplay: 'long',
25});
26console.log(longCompact.format(1234567)); // '1.2 million'
27
28// Compact in different locales
29const jaCompact = new Intl.NumberFormat('ja-JP', {
30 notation: 'compact',
31});
32console.log(jaCompact.format(10000)); // '1万'Sign Display#
1// Always show sign
2const alwaysSign = new Intl.NumberFormat('en-US', {
3 signDisplay: 'always',
4});
5console.log(alwaysSign.format(42)); // '+42'
6console.log(alwaysSign.format(-42)); // '-42'
7console.log(alwaysSign.format(0)); // '+0'
8
9// Sign for non-zero only
10const exceptZero = new Intl.NumberFormat('en-US', {
11 signDisplay: 'exceptZero',
12});
13console.log(exceptZero.format(42)); // '+42'
14console.log(exceptZero.format(-42)); // '-42'
15console.log(exceptZero.format(0)); // '0'
16
17// Accounting format for negative
18const accounting = new Intl.NumberFormat('en-US', {
19 style: 'currency',
20 currency: 'USD',
21 currencySign: 'accounting',
22});
23console.log(accounting.format(-42)); // '($42.00)'Format to Parts#
1// Get formatted parts as array
2const formatter = new Intl.NumberFormat('en-US', {
3 style: 'currency',
4 currency: 'USD',
5});
6
7const parts = formatter.formatToParts(1234.56);
8console.log(parts);
9// [
10// { type: 'currency', value: '$' },
11// { type: 'integer', value: '1' },
12// { type: 'group', value: ',' },
13// { type: 'integer', value: '234' },
14// { type: 'decimal', value: '.' },
15// { type: 'fraction', value: '56' }
16// ]
17
18// Custom styling based on parts
19function formatWithStyles(number, formatter) {
20 return formatter.formatToParts(number).map(part => {
21 switch (part.type) {
22 case 'currency':
23 return `<span class="currency">${part.value}</span>`;
24 case 'integer':
25 return `<span class="integer">${part.value}</span>`;
26 case 'fraction':
27 return `<span class="fraction">${part.value}</span>`;
28 default:
29 return part.value;
30 }
31 }).join('');
32}Range Formatting#
1// Format number range
2const rangeFormatter = new Intl.NumberFormat('en-US', {
3 style: 'currency',
4 currency: 'USD',
5});
6
7console.log(rangeFormatter.formatRange(100, 200));
8// '$100.00 – $200.00'
9
10// Range with compact notation
11const compactRange = new Intl.NumberFormat('en-US', {
12 notation: 'compact',
13});
14console.log(compactRange.formatRange(1000, 5000));
15// '1K – 5K'Rounding Options#
1// Rounding mode
2const ceilFormatter = new Intl.NumberFormat('en-US', {
3 maximumFractionDigits: 2,
4 roundingMode: 'ceil',
5});
6console.log(ceilFormatter.format(1.234)); // '1.24'
7
8const floorFormatter = new Intl.NumberFormat('en-US', {
9 maximumFractionDigits: 2,
10 roundingMode: 'floor',
11});
12console.log(floorFormatter.format(1.236)); // '1.23'
13
14// Rounding increment
15const nickelFormatter = new Intl.NumberFormat('en-US', {
16 style: 'currency',
17 currency: 'USD',
18 roundingIncrement: 5,
19 maximumFractionDigits: 2,
20});
21console.log(nickelFormatter.format(1.22)); // '$1.20'
22console.log(nickelFormatter.format(1.23)); // '$1.25'Practical Examples#
1// Price formatter
2function formatPrice(amount, currency = 'USD', locale = 'en-US') {
3 return new Intl.NumberFormat(locale, {
4 style: 'currency',
5 currency,
6 }).format(amount);
7}
8
9// File size formatter
10function formatFileSize(bytes) {
11 const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte'];
12 let unitIndex = 0;
13 let size = bytes;
14
15 while (size >= 1024 && unitIndex < units.length - 1) {
16 size /= 1024;
17 unitIndex++;
18 }
19
20 return new Intl.NumberFormat('en-US', {
21 style: 'unit',
22 unit: units[unitIndex],
23 maximumFractionDigits: 2,
24 }).format(size);
25}
26
27console.log(formatFileSize(1234567890)); // '1.15 GB'
28
29// Compact number with threshold
30function formatNumber(num, compact = true) {
31 const options = compact && Math.abs(num) >= 1000
32 ? { notation: 'compact', maximumFractionDigits: 1 }
33 : { maximumFractionDigits: 0 };
34
35 return new Intl.NumberFormat('en-US', options).format(num);
36}Caching Formatters#
1// Cache formatters for performance
2const formatters = new Map();
3
4function getCurrencyFormatter(currency, locale = 'en-US') {
5 const key = `${locale}-${currency}`;
6
7 if (!formatters.has(key)) {
8 formatters.set(
9 key,
10 new Intl.NumberFormat(locale, {
11 style: 'currency',
12 currency,
13 })
14 );
15 }
16
17 return formatters.get(key);
18}
19
20// Usage
21const usd = getCurrencyFormatter('USD');
22console.log(usd.format(100)); // '$100.00'Best Practices#
Locale Handling:
✓ Use user's locale when possible
✓ Provide locale fallbacks
✓ Test with multiple locales
✓ Consider right-to-left locales
Currency:
✓ Use ISO 4217 currency codes
✓ Match locale to currency region
✓ Consider currencyDisplay option
✓ Handle accounting format
Performance:
✓ Cache formatter instances
✓ Reuse formatters
✓ Avoid creating in loops
✓ Use formatToParts for custom display
Avoid:
✗ Manual number formatting
✗ String concatenation for currency
✗ Ignoring locale differences
✗ Creating formatters repeatedly
Conclusion#
Intl.NumberFormat provides comprehensive locale-aware number formatting for currencies, percentages, units, and general numbers. Use it instead of manual string manipulation to ensure correct formatting across different locales. Cache formatter instances for performance and use formatToParts when you need fine-grained control over the output display.