How to get individual parts of formatted number for custom display

Break formatted numbers into components to apply custom styling and build complex interfaces

Introduction

The format() method returns a complete formatted string like "$1,234.56" or "1.5M". This works well for simple display, but you cannot style individual parts differently. You cannot make the currency symbol bold, color the decimal portion differently, or apply custom markup to specific components.

JavaScript provides the formatToParts() method to solve this problem. Instead of returning a single string, it returns an array of objects, each representing one part of the formatted number. Each part has a type like currency, integer, or decimal, and a value like $, 1234, or .. You can then process these parts to apply custom styling, build complex layouts, or integrate formatted numbers into rich user interfaces.

Why formatted strings are difficult to customize

When you receive a formatted string like "$1,234.56", you cannot easily identify where the currency symbol ends and the number begins. Different locales place symbols in different positions. Some locales use different separators. Parsing these strings reliably requires complex logic that duplicates the formatting rules already implemented in the Intl API.

Consider a dashboard that displays monetary amounts with the currency symbol in a different color. With format(), you would need to:

  1. Detect which characters are the currency symbol
  2. Account for spaces between the symbol and number
  3. Handle different symbol positions across locales
  4. Parse the string carefully to avoid breaking the number

This approach is fragile and error-prone. Any change to locale formatting rules breaks your parsing logic.

The formatToParts() method eliminates this problem by providing the components separately. You receive structured data that tells you exactly which part is which, regardless of locale.

Using formatToParts to get number components

The formatToParts() method works identically to format() except for its return value. You create a formatter with the same options, then call formatToParts() instead of format().

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

const parts = formatter.formatToParts(1234.56);
console.log(parts);

This outputs an array of objects:

[
  { type: "currency", value: "$" },
  { type: "integer", value: "1" },
  { type: "group", value: "," },
  { type: "integer", value: "234" },
  { type: "decimal", value: "." },
  { type: "fraction", value: "56" }
]

Each object contains a type property identifying what the part represents and a value property containing the actual string. The parts appear in the same order they would in the formatted output.

You can verify this by joining all the values together:

const formatted = parts.map(part => part.value).join("");
console.log(formatted);
// Output: "$1,234.56"

The concatenated parts produce the exact same output as calling format().

Understanding part types

The type property identifies each component. Different formatting options produce different part types.

For basic number formatting:

const formatter = new Intl.NumberFormat("en-US");
const parts = formatter.formatToParts(1234.56);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

The integer type represents the whole number portion. Multiple integer parts appear when group separators split the number. The group type represents the thousands separator. The decimal type represents the decimal point. The fraction type represents digits after the decimal.

For currency formatting:

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "EUR"
});

const parts = formatter.formatToParts(1234.56);
console.log(parts);
// [
//   { type: "currency", value: "€" },
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

The currency type appears before or after the number depending on locale conventions.

For percentages:

const formatter = new Intl.NumberFormat("en-US", {
  style: "percent"
});

const parts = formatter.formatToParts(0.1234);
console.log(parts);
// [
//   { type: "integer", value: "12" },
//   { type: "percentSign", value: "%" }
// ]

The percentSign type represents the percent symbol.

For compact notation:

const formatter = new Intl.NumberFormat("en-US", {
  notation: "compact"
});

const parts = formatter.formatToParts(1500000);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "5" },
//   { type: "compact", value: "M" }
// ]

The compact type represents the magnitude indicator like K, M, or B.

Applying custom styling to number parts

The primary use case for formatToParts() is applying different styles to different components. You can process the parts array to wrap specific types in HTML elements.

Making the currency symbol bold:

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

const parts = formatter.formatToParts(1234.56);
const html = parts
  .map(part => {
    if (part.type === "currency") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>$</strong>1,234.56"

This approach works for any markup language. You can generate HTML, JSX, or any other format by processing the parts array.

Styling decimal portions differently:

const formatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2
});

const parts = formatter.formatToParts(1234.5);
const html = parts
  .map(part => {
    if (part.type === "decimal" || part.type === "fraction") {
      return `<span class="text-gray-500">${part.value}</span>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "1,234<span class="text-gray-500">.50</span>"

This pattern is common in pricing displays where the decimal portion appears smaller or lighter.

Color-coding negative numbers

Financial applications often display negative numbers in red. With formatToParts(), you can detect the minus sign and apply styling accordingly.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

function formatWithColor(number) {
  const parts = formatter.formatToParts(number);
  const hasMinusSign = parts.some(part => part.type === "minusSign");

  const html = parts
    .map(part => part.value)
    .join("");

  if (hasMinusSign) {
    return `<span class="text-red-600">${html}</span>`;
  }

  return html;
}

console.log(formatWithColor(-1234.56));
// Output: "<span class="text-red-600">-$1,234.56</span>"

console.log(formatWithColor(1234.56));
// Output: "$1,234.56"

This approach detects negative numbers reliably across all locales, even those that use different symbols or positions for negative indicators.

Building custom number displays with multiple styles

Complex interfaces often combine multiple styling rules. You can apply different classes or elements to different part types simultaneously.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

function formatCurrency(number) {
  const parts = formatter.formatToParts(number);

  return parts
    .map(part => {
      switch (part.type) {
        case "currency":
          return `<span class="currency-symbol">${part.value}</span>`;
        case "integer":
          return `<span class="integer">${part.value}</span>`;
        case "group":
          return `<span class="group">${part.value}</span>`;
        case "decimal":
          return `<span class="decimal">${part.value}</span>`;
        case "fraction":
          return `<span class="fraction">${part.value}</span>`;
        case "minusSign":
          return `<span class="minus">${part.value}</span>`;
        default:
          return part.value;
      }
    })
    .join("");
}

console.log(formatCurrency(1234.56));
// Output: "<span class="currency-symbol">$</span><span class="integer">1</span><span class="group">,</span><span class="integer">234</span><span class="decimal">.</span><span class="fraction">56</span>"

This granular control enables precise styling for each component. You can then use CSS to style each class differently.

All available part types

The type property can have these values depending on the formatting options used:

  • integer: Whole number digits
  • fraction: Decimal digits
  • decimal: Decimal separator
  • group: Thousands separator
  • currency: Currency symbol
  • literal: Spacing or other literal text added by formatting
  • percentSign: Percent symbol
  • minusSign: Negative number indicator
  • plusSign: Positive number indicator (when signDisplay is set)
  • unit: Unit string for unit formatting
  • compact: Magnitude indicator in compact notation (K, M, B)
  • exponentInteger: Exponent value in scientific notation
  • exponentMinusSign: Negative sign in exponent
  • exponentSeparator: Symbol separating mantissa from exponent
  • infinity: Infinity representation
  • nan: Not-a-number representation
  • unknown: Unrecognized tokens

Not every formatting option produces every part type. The parts you receive depend on the number value and the formatter configuration.

Scientific notation produces exponent-related parts:

const formatter = new Intl.NumberFormat("en-US", {
  notation: "scientific"
});

const parts = formatter.formatToParts(1234);
console.log(parts);
// [
//   { type: "integer", value: "1" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "234" },
//   { type: "exponentSeparator", value: "E" },
//   { type: "exponentInteger", value: "3" }
// ]

Special values produce specific part types:

const formatter = new Intl.NumberFormat("en-US");

console.log(formatter.formatToParts(Infinity));
// [{ type: "infinity", value: "∞" }]

console.log(formatter.formatToParts(NaN));
// [{ type: "nan", value: "NaN" }]

Creating accessible number displays

You can use formatToParts() to add accessibility attributes to formatted numbers. This helps screen readers announce values correctly.

const formatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

function formatAccessibleCurrency(number) {
  const parts = formatter.formatToParts(number);
  const formatted = parts.map(part => part.value).join("");

  return `<span aria-label="${number} US dollars">${formatted}</span>`;
}

console.log(formatAccessibleCurrency(1234.56));
// Output: "<span aria-label="1234.56 US dollars">$1,234.56</span>"

This ensures screen readers announce both the formatted display value and the underlying numeric value with proper context.

Highlighting specific number ranges

Some applications highlight numbers that fall within certain ranges. With formatToParts(), you can apply styling based on the value while maintaining proper formatting.

const formatter = new Intl.NumberFormat("en-US");

function formatWithThreshold(number, threshold) {
  const parts = formatter.formatToParts(number);
  const formatted = parts.map(part => part.value).join("");

  if (number >= threshold) {
    return `<span class="text-green-600 font-bold">${formatted}</span>`;
  }

  return formatted;
}

console.log(formatWithThreshold(1500, 1000));
// Output: "<span class="text-green-600 font-bold">1,500</span>"

console.log(formatWithThreshold(500, 1000));
// Output: "500"

The number receives proper formatting for the locale while conditional styling applies based on business logic.

When to use formatToParts versus format

Use format() when you need a simple formatted string without any customization. This is the common case for most number display.

Use formatToParts() when you need to:

  • Apply different styling to different parts of the number
  • Build HTML or JSX with formatted numbers
  • Add attributes or metadata to specific components
  • Integrate formatted numbers into complex layouts
  • Process formatted output programmatically

The formatToParts() method has slightly more overhead than format() because it creates an array of objects instead of a single string. This difference is negligible for typical applications, but if you format thousands of numbers per second, format() performs better.

For most applications, choose based on your styling needs rather than performance concerns. If you do not need to customize the output, use format(). If you need custom styling or markup, use formatToParts().

How parts preserve locale-specific formatting

The parts array maintains locale-specific formatting rules automatically. Different locales place symbols in different positions and use different separators, but formatToParts() handles these differences.

const usdFormatter = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD"
});

console.log(usdFormatter.formatToParts(1234.56));
// [
//   { type: "currency", value: "$" },
//   { type: "integer", value: "1" },
//   { type: "group", value: "," },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "." },
//   { type: "fraction", value: "56" }
// ]

const eurFormatter = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR"
});

console.log(eurFormatter.formatToParts(1234.56));
// [
//   { type: "integer", value: "1" },
//   { type: "group", value: "." },
//   { type: "integer", value: "234" },
//   { type: "decimal", value: "," },
//   { type: "fraction", value: "56" },
//   { type: "literal", value: " " },
//   { type: "currency", value: "€" }
// ]

German formatting places the currency after the number with a space. The group separator is a period, and the decimal separator is a comma. Your styling code processes the parts array the same way regardless of locale, and the formatting adapts automatically.

The literal type represents any spacing or text inserted by the formatter that does not fit other categories. In German currency formatting, it represents the space between the number and currency symbol.

Combining formatToParts with framework components

Modern frameworks like React can use formatToParts() to build components efficiently.

function CurrencyDisplay({ value, locale, currency }) {
  const formatter = new Intl.NumberFormat(locale, {
    style: "currency",
    currency: currency
  });

  const parts = formatter.formatToParts(value);

  return (
    <span className="currency-display">
      {parts.map((part, index) => {
        if (part.type === "currency") {
          return <strong key={index}>{part.value}</strong>;
        }
        if (part.type === "fraction" || part.type === "decimal") {
          return <span key={index} className="text-sm text-gray-500">{part.value}</span>;
        }
        return <span key={index}>{part.value}</span>;
      })}
    </span>
  );
}

This component applies different styling to different parts while maintaining proper formatting for any locale and currency.