How to split formatted output into pieces for styling

Use formatToParts() to access individual components of formatted output for custom styling

Introduction

The format() method on JavaScript formatters returns complete strings like "$1,234.56" or "January 15, 2025". This works well for simple display, but you cannot style individual parts differently. You cannot make the currency symbol bold, color the month name 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 output. Each part has a type like currency, month, or element, and a value containing the actual string. You can then process these parts to apply custom styling, build complex layouts, or integrate formatted content into rich user interfaces.

The formatToParts() method is available on multiple Intl formatters, including NumberFormat, DateTimeFormat, ListFormat, RelativeTimeFormat, and DurationFormat. This makes it a consistent pattern across all internationalization formatting in JavaScript.

Why formatted strings cannot be styled easily

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 same problem exists for dates, lists, and other formatted output. You cannot reliably parse formatted strings to identify components without reimplementing the locale-specific formatting rules.

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.

How formatToParts works

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().

The method returns an array of objects. Each object contains two properties:

  • type: Identifies what the part represents, like currency, month, or literal
  • value: Contains the actual string for that part

The parts appear in the same order they would in the formatted output. You can verify this by joining all the values together, which produces the exact same output as calling format().

This pattern is consistent across all formatters that support formatToParts(). The specific part types vary by formatter, but the structure is always the same.

Splitting formatted numbers into parts

The NumberFormat formatter provides formatToParts() for breaking down formatted numbers. This works for basic numbers, currencies, percentages, and other numeric styles.

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 identifies what the part represents and provides its value. The currency type represents the currency symbol. The integer type represents whole number digits. The group type represents the thousands separator. The decimal type represents the decimal point. The fraction type represents digits after the decimal.

You can verify the parts match the formatted output:

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().

Styling currency symbols in formatted numbers

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.

Splitting formatted dates into parts

The DateTimeFormat formatter provides formatToParts() for breaking down formatted dates and times.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);

This outputs an array of objects:

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2025" }
]

The month type represents the month name or number. The day type represents the day of the month. The year type represents the year. The literal type represents spacing, punctuation, or other text inserted by the formatter.

Styling month names in formatted dates

You can apply custom styling to date components using the same pattern as numbers.

Making the month name bold:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
  .map(part => {
    if (part.type === "month") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>January</strong> 15, 2025"

Styling multiple date components:

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);

const html = parts
  .map(part => {
    switch (part.type) {
      case "weekday":
        return `<span class="font-bold">${part.value}</span>`;
      case "month":
        return `<span class="text-blue-600">${part.value}</span>`;
      case "year":
        return `<span class="text-gray-500">${part.value}</span>`;
      default:
        return part.value;
    }
  })
  .join("");

console.log(html);
// Output: "<span class="font-bold">Wednesday</span>, <span class="text-blue-600">January</span> 15, <span class="text-gray-500">2025</span>"

This granular control enables precise styling for each component.

Splitting formatted lists into parts

The ListFormat formatter provides formatToParts() for breaking down formatted lists.

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);
console.log(parts);

This outputs an array of objects:

[
  { type: "element", value: "apples" },
  { type: "literal", value: ", " },
  { type: "element", value: "oranges" },
  { type: "literal", value: ", and " },
  { type: "element", value: "bananas" }
]

The element type represents each item in the list. The literal type represents separators and conjunctions added by the formatter.

Styling list items individually

You can apply custom styling to list elements using the same pattern.

Making list items bold:

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);
const html = parts
  .map(part => {
    if (part.type === "element") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<strong>apples</strong>, <strong>oranges</strong>, and <strong>bananas</strong>"

Styling specific list items:

const formatter = new Intl.ListFormat("en-US", {
  style: "long",
  type: "conjunction"
});

const items = ["apples", "oranges", "bananas"];
const parts = formatter.formatToParts(items);

let itemIndex = 0;
const html = parts
  .map(part => {
    if (part.type === "element") {
      const currentIndex = itemIndex++;
      if (currentIndex === 0) {
        return `<span class="text-green-600">${part.value}</span>`;
      }
      return part.value;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Output: "<span class="text-green-600">apples</span>, oranges, and bananas"

This approach allows you to highlight specific items while maintaining proper locale-specific formatting.

Splitting formatted relative times into parts

The RelativeTimeFormat formatter provides formatToParts() for breaking down relative time expressions.

const formatter = new Intl.RelativeTimeFormat("en-US", {
  numeric: "auto"
});

const parts = formatter.formatToParts(-1, "day");
console.log(parts);

This outputs an array of objects:

[
  { type: "literal", value: "yesterday" }
]

For numeric relative times:

const formatter = new Intl.RelativeTimeFormat("en-US", {
  numeric: "always"
});

const parts = formatter.formatToParts(-3, "day");
console.log(parts);
// [
//   { type: "integer", value: "3" },
//   { type: "literal", value: " days ago" }
// ]

The integer type represents the numeric value. The literal type represents the relative time unit and direction.

Splitting formatted durations into parts

The DurationFormat formatter provides formatToParts() for breaking down formatted durations.

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

const parts = formatter.formatToParts({
  hours: 2,
  minutes: 30,
  seconds: 15
});
console.log(parts);

This outputs an array of objects similar to:

[
  { type: "integer", value: "2" },
  { type: "literal", value: " hours, " },
  { type: "integer", value: "30" },
  { type: "literal", value: " minutes, " },
  { type: "integer", value: "15" },
  { type: "literal", value: " seconds" }
]

The integer type represents numeric values. The literal type represents unit names and separators.

Building HTML from formatted parts

You can create a reusable function that processes parts and applies styling rules consistently.

function formatWithStyles(parts, styleMap) {
  return parts
    .map(part => {
      const style = styleMap[part.type];
      if (style) {
        return `<span class="${style}">${part.value}</span>`;
      }
      return part.value;
    })
    .join("");
}

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

const parts = numberFormatter.formatToParts(1234.56);
const html = formatWithStyles(parts, {
  currency: "font-bold text-gray-700",
  integer: "text-2xl",
  fraction: "text-sm text-gray-500"
});

console.log(html);
// Output: "<span class="font-bold text-gray-700">$</span><span class="text-2xl">1</span>,<span class="text-2xl">234</span>.<span class="text-sm text-gray-500">56</span>"

This pattern separates the styling rules from the formatting logic, making it easier to maintain and reuse.

Understanding locale-specific part ordering

The parts array maintains locale-specific formatting rules automatically. Different locales place components in different orders and use different formats, 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.

Creating accessible formatted displays

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

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

function formatAccessible(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(formatAccessible(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.

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.

When to use formatToParts

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

Use formatToParts() when you need to:

  • Apply different styling to different parts of the formatted output
  • Build HTML or JSX with formatted content
  • Add attributes or metadata to specific components
  • Integrate formatted output into complex layouts
  • Process formatted output programmatically
  • Create custom visual designs that require granular control

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 values 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().

Common part types across formatters

Different formatters produce different part types, but some types appear across multiple formatters:

  • literal: Spacing, punctuation, or other text added by formatting. Appears in dates, numbers, lists, and durations.
  • integer: Whole number digits. Appears in numbers, relative times, and durations.
  • decimal: Decimal separator. Appears in numbers.
  • fraction: Decimal digits. Appears in numbers.

Formatter-specific types include:

  • Numbers: currency, group, percentSign, minusSign, plusSign, unit, compact, exponentInteger
  • Dates: weekday, era, year, month, day, hour, minute, second, dayPeriod, timeZoneName
  • Lists: element
  • Relative times: Numeric values appear as integer, text appears as literal

Understanding these types helps you write styling code that handles any formatter output correctly.