How to format numbers for different locales in Next.js (Pages Router) v16
Display numbers with locale-specific separators
Problem
Numbers are written differently around the world. What appears as 10,000.5 in the United States becomes 10.000,5 in Germany—commas and periods swap roles entirely. This is not a matter of preference or style, but of legibility. A German user seeing 10,000.5 might read it as ten, ignoring the grouping separators. An American user seeing 10.000,5 might read it as ten thousand, ignoring the decimal separator. The same digits, opposite meanings.
When applications display numbers without locale-aware formatting, they risk confusing users or conveying incorrect information. Currency amounts, percentages, measurements, and statistics all depend on regional conventions that users have learned since childhood.
Solution
Format numbers based on the user's locale, using regional rules for decimal and grouping separators. This turns numeric values into strings that follow the formatting rules familiar to users in their region.
react-intl provides two approaches: the <FormattedNumber> component for declarative formatting in JSX, and the formatNumber method from the useIntl hook for imperative formatting. Both rely on the browser's Intl.NumberFormat API and automatically apply the locale configured in your IntlProvider. The formatted output respects regional conventions for thousands separators, decimal points, and digit grouping.
Steps
1. Create a component that formats numbers declaratively
The <FormattedNumber> component uses the formatNumber and Intl.NumberFormat APIs and accepts a value prop along with options that correspond to Intl.NumberFormatOptions.
import { FormattedNumber } from "react-intl";
export default function ProductPrice({ price }: { price: number }) {
return (
<div>
<FormattedNumber value={price} />
</div>
);
}
By default, <FormattedNumber> renders the formatted number into a React.Fragment. The component reads the locale from the nearest IntlProvider and applies the appropriate formatting rules.
2. Format numbers with specific options
Pass formatting options to control how numbers appear. Common options include minimumFractionDigits, maximumFractionDigits, and style.
import { FormattedNumber } from "react-intl";
export default function Statistics({ value }: { value: number }) {
return (
<dl>
<dt>Total Users</dt>
<dd>
<FormattedNumber
value={value}
minimumFractionDigits={0}
maximumFractionDigits={0}
/>
</dd>
<dt>Conversion Rate</dt>
<dd>
<FormattedNumber
value={value / 100}
style="percent"
minimumFractionDigits={2}
/>
</dd>
</dl>
);
}
These props correspond to Intl.NumberFormatOptions, giving you control over precision and presentation while maintaining locale-specific separators.
3. Format numbers imperatively with the useIntl hook
When a component can be expressed as a function component, the useIntl hook provides access to the intl object, which includes a formatNumber method.
import { useIntl } from "react-intl";
export default function DataTable({ rows }: { rows: number[] }) {
const intl = useIntl();
return (
<table>
<tbody>
{rows.map((value, index) => (
<tr key={index}>
<td>{intl.formatNumber(value)}</td>
</tr>
))}
</tbody>
</table>
);
}
The formatNumber function returns a formatted number string and accepts a value which can be parsed as a number, along with options that conform to NumberFormatOptions.
4. Format numbers in non-JSX contexts
Use formatNumber when you need formatted numbers in attributes, variables, or other contexts where components cannot be used.
import { useIntl } from "react-intl";
export default function Chart({ dataPoints }: { dataPoints: number[] }) {
const intl = useIntl();
const formattedLabel = intl.formatNumber(dataPoints[0], {
notation: "compact",
maximumFractionDigits: 1,
});
return (
<div>
<img
src="/chart.png"
alt={`Chart showing ${formattedLabel} items`}
title={formattedLabel}
/>
</div>
);
}
The imperative API is essential for setting text attributes like title, aria-label, or alt, where React components cannot be rendered.