How to format numbers for different locales in TanStack Start v1

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 considering regional formatting conventions, they create confusion and erode trust. Users expect numbers to follow the patterns they learned, and deviations from those patterns force them to pause, reinterpret, and question whether the data is correct.

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 components and hooks that leverage the browser's built-in Intl.NumberFormat API to apply locale-specific formatting. By passing a numeric value and the current locale, these tools automatically select the correct separators, digit grouping, and other regional conventions. The result is a formatted string that matches user expectations without manual string manipulation or locale-specific logic in your components.

Steps

1. Create a number display component using FormattedNumber

Build a component that accepts a numeric value and renders it with locale-appropriate formatting using react-intl's FormattedNumber component.

import { FormattedNumber } from "react-intl";

interface PriceDisplayProps {
  value: number;
}

export function PriceDisplay({ value }: PriceDisplayProps) {
  return (
    <div>
      <FormattedNumber value={value} />
    </div>
  );
}

The FormattedNumber component automatically applies the locale's decimal and grouping separators. It reads the locale from the nearest IntlProvider in the component tree and formats the number accordingly.

2. Format numbers with specific styles

Customize number formatting by passing style options to FormattedNumber for currency, percentages, or units.

import { FormattedNumber } from "react-intl";

interface MetricsProps {
  revenue: number;
  growthRate: number;
  fileSize: number;
}

export function Metrics({ revenue, growthRate, fileSize }: MetricsProps) {
  return (
    <div>
      <p>
        Revenue:{" "}
        <FormattedNumber value={revenue} style="currency" currency="USD" />
      </p>
      <p>
        Growth: <FormattedNumber value={growthRate} style="percent" />
      </p>
      <p>
        Size:{" "}
        <FormattedNumber
          value={fileSize}
          style="unit"
          unit="megabyte"
          unitDisplay="short"
        />
      </p>
    </div>
  );
}

The style prop controls the formatting mode. Currency formatting requires a currency code, percentage formatting automatically multiplies by 100, and unit formatting requires a unit identifier. All styles respect the active locale's conventions.

3. Format numbers imperatively with useIntl

Use the useIntl hook to format numbers in contexts where components are not suitable, such as generating dynamic attributes or preparing data for non-React APIs.

import { useIntl } from "react-intl";

interface ChartTooltipProps {
  dataPoint: number;
}

export function ChartTooltip({ dataPoint }: ChartTooltipProps) {
  const intl = useIntl();
  const formattedValue = intl.formatNumber(dataPoint, {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });

  return <div title={formattedValue}>{formattedValue}</div>;
}

The formatNumber method returns a formatted string immediately, making it suitable for attributes, aria labels, or any context where you need the formatted value as a string rather than a React element.

4. Apply custom formatting options

Control precision, grouping, and notation by passing Intl.NumberFormatOptions to either FormattedNumber or formatNumber.

import { FormattedNumber } from "react-intl";

interface StatisticProps {
  value: number;
  compact?: boolean;
}

export function Statistic({ value, compact }: StatisticProps) {
  return (
    <div>
      <FormattedNumber
        value={value}
        notation={compact ? "compact" : "standard"}
        minimumFractionDigits={0}
        maximumFractionDigits={2}
      />
    </div>
  );
}

Options like notation, minimumFractionDigits, and maximumFractionDigits control how the number is displayed. Compact notation converts large numbers to abbreviated forms like "1.2M" while respecting locale-specific abbreviations.