How do you format lists of measurements with units?

Display multiple measurements like 5 km, 10 km, 15 km with locale-appropriate list formatting using JavaScript's Intl APIs

Introduction

Applications that display measurements often need to show multiple values together. A fitness app might show split times as "5 km, 10 km, 15 km". A weather app might display temperatures across the week as "20°C, 22°C, 25°C, 23°C". A recipe might list ingredient amounts as "2 cups, 1 tablespoon, 3 teaspoons".

These lists combine two internationalization challenges. First, each measurement needs locale-appropriate unit formatting. Second, the list itself needs proper punctuation and separators for the target language. English uses commas and sometimes conjunctions like "and". Other languages use different separators and follow different grammatical rules.

JavaScript provides two APIs to solve this problem. Intl.NumberFormat formats individual measurements with units. Intl.ListFormat combines multiple values into a grammatically correct list. This lesson explains how to use both APIs together to format lists of measurements that match user expectations in any locale.

Lists of measurements require two formatting steps

When you format a list of measurements, you cannot skip either formatting step. If you format the list without formatting the measurements, you get locale-appropriate separators but incorrect unit display. If you format the measurements without formatting the list, you get correct units but incorrect separators.

const distances = [5, 10, 15];

// Wrong: list formatted but not measurements
console.log(distances.join(', '));
// Output: "5, 10, 15" (missing units)

// Wrong: measurements formatted but not list
const formatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});
console.log(distances.map(d => formatter.format(d)).join(', '));
// Output: "5 km, 10 km, 15 km" (hardcoded comma might be wrong for some locales)

The correct approach formats measurements first, then formats the resulting array of strings as a list.

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedMeasurements = distances.map(d => numberFormatter.format(d));
// Result: ["5 km", "10 km", "15 km"]

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedMeasurements));
// Output: "5 km, 10 km, 15 km"

This pattern works for any measurement type and any locale. You format each measurement with its unit, then format the array of formatted strings as a list.

Use type unit for measurement lists

The Intl.ListFormat constructor accepts a type option that controls how list items are combined. The type: 'unit' option formats lists following conventions for technical and scientific data.

const measurements = ['5 km', '10 km', '15 km'];

const unitList = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(unitList.format(measurements));
// Output: "5 km, 10 km, 15 km"

Lists with type: 'unit' omit conjunctions like "and" or "or". They use simple separators between items. This matches how measurements are typically written in technical contexts.

Compare this to type: 'conjunction', which adds "and" before the final item.

const measurements = ['5 km', '10 km', '15 km'];

const conjunctionList = new Intl.ListFormat('en-US', {
  type: 'conjunction'
});

console.log(conjunctionList.format(measurements));
// Output: "5 km, 10 km, and 15 km"

The conjunction form reads naturally in prose but looks incorrect in technical contexts. When displaying multiple measurements, use type: 'unit' to follow standard conventions for scientific and technical writing.

Format distance measurements in lists

Distance measurements use unit identifiers like kilometer, meter, mile, and foot. After formatting each distance with its unit, combine them into a list.

const distances = [5, 10, 15, 20];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// Output: "5 km, 10 km, 15 km, 20 km"

The same pattern works for miles.

const distances = [3, 6, 9];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'mile'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// Output: "3 mi, 6 mi, 9 mi"

You can format distances with decimal places by setting number formatting options.

const distances = [5.2, 10.7, 15.3];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  maximumFractionDigits: 1
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// Output: "5.2 km, 10.7 km, 15.3 km"

The number formatter handles rounding and decimal places before the list formatter combines the values.

Format weight measurements in lists

Weight measurements follow the same pattern using unit identifiers like kilogram, pound, ounce, and gram.

const weights = [50, 75, 100];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilogram'
});

const formattedWeights = weights.map(w => numberFormatter.format(w));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedWeights));
// Output: "50 kg, 75 kg, 100 kg"

You can display weight in pounds instead.

const weights = [110, 165, 220];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'pound'
});

const formattedWeights = weights.map(w => numberFormatter.format(w));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedWeights));
// Output: "110 lb, 165 lb, 220 lb"

The number formatter automatically uses the correct abbreviation for each unit.

Format temperature measurements in lists

Temperature measurements use unit identifiers like celsius and fahrenheit.

const temperatures = [20, 22, 25, 23, 21];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'celsius'
});

const formattedTemperatures = temperatures.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedTemperatures));
// Output: "20°C, 22°C, 25°C, 23°C, 21°C"

Temperature formatters automatically include degree symbols in the output.

Fahrenheit works the same way.

const temperatures = [68, 72, 77, 73, 70];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'fahrenheit'
});

const formattedTemperatures = temperatures.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedTemperatures));
// Output: "68°F, 72°F, 77°F, 73°F, 70°F"

The pattern remains identical across different measurement types. Only the unit identifier changes.

Format volume measurements in lists

Volume measurements use unit identifiers like liter, gallon, milliliter, and fluid-ounce.

const volumes = [1, 2, 3];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'liter'
});

const formattedVolumes = volumes.map(v => numberFormatter.format(v));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedVolumes));
// Output: "1 L, 2 L, 3 L"

Volume measurements work with decimal values.

const volumes = [0.5, 1.5, 2.5];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'liter',
  maximumFractionDigits: 1
});

const formattedVolumes = volumes.map(v => numberFormatter.format(v));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedVolumes));
// Output: "0.5 L, 1.5 L, 2.5 L"

The number formatter handles decimal precision before the list formatter processes the values.

Format speed measurements in lists

Speed measurements use compound units like kilometer-per-hour and mile-per-hour.

const speeds = [50, 75, 100];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer-per-hour'
});

const formattedSpeeds = speeds.map(s => numberFormatter.format(s));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedSpeeds));
// Output: "50 km/h, 75 km/h, 100 km/h"

Miles per hour works the same way.

const speeds = [30, 45, 60];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'mile-per-hour'
});

const formattedSpeeds = speeds.map(s => numberFormatter.format(s));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedSpeeds));
// Output: "30 mph, 45 mph, 60 mph"

Compound units format automatically with the correct abbreviations and separators.

Locale determines list separator format

The locale parameter controls how list items are separated and punctuated. Different languages use different conventions for list formatting.

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const enList = new Intl.ListFormat('en-US', {
  type: 'unit'
});

const frList = new Intl.ListFormat('fr-FR', {
  type: 'unit'
});

const deList = new Intl.ListFormat('de-DE', {
  type: 'unit'
});

console.log(enList.format(formattedDistances));
// Output: "5 km, 10 km, 15 km"

console.log(frList.format(formattedDistances));
// Output: "5 km, 10 km, 15 km"

console.log(deList.format(formattedDistances));
// Output: "5 km, 10 km, 15 km"

While kilometer abbreviations remain similar across these locales, spacing and separator conventions can vary. The Intl.ListFormat API handles these locale-specific formatting rules automatically.

Some languages use different separators or punctuation patterns for lists. The API ensures your lists follow the correct conventions for each locale without requiring you to know the specific rules.

Match number locale to list locale

When formatting lists of measurements, use the same locale for both the number formatter and the list formatter. This ensures consistent formatting throughout the output.

const distances = [1000, 2000, 3000];

const locale = 'de-DE';

const numberFormatter = new Intl.NumberFormat(locale, {
  style: 'unit',
  unit: 'meter'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat(locale, {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// Output: "1.000 m, 2.000 m, 3.000 m"

German formatting uses periods as thousands separators. Both the number formatter and list formatter use German conventions because they share the same locale.

Using different locales for number and list formatting creates inconsistent output.

const distances = [1000, 2000, 3000];

const numberFormatter = new Intl.NumberFormat('de-DE', {
  style: 'unit',
  unit: 'meter'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// Output: "1.000 m, 2.000 m, 3.000 m"

This creates mixed formatting where numbers use German conventions but the list uses English conventions. Always use the same locale for both formatters.

Control list display style

The style option controls how verbose the list formatting appears. The option accepts three values: "long", "short", and "narrow".

const measurements = ['5 km', '10 km', '15 km'];

const longList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'long'
});

const shortList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'short'
});

const narrowList = new Intl.ListFormat('en-US', {
  type: 'unit',
  style: 'narrow'
});

console.log(longList.format(measurements));
// Output: "5 km, 10 km, 15 km"

console.log(shortList.format(measurements));
// Output: "5 km, 10 km, 15 km"

console.log(narrowList.format(measurements));
// Output: "5 km 10 km 15 km"

The long and short styles produce similar output for unit lists in English. The narrow style uses minimal spacing and omits separators between items.

Different locales show more variation between styles. The locale determines the exact formatting for each style level.

Combine with long unit names

You can format measurements with full unit names instead of abbreviations by setting unitDisplay: 'long' in the number formatter.

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'long'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// Output: "5 kilometers, 10 kilometers, 15 kilometers"

The number formatter handles singular and plural forms automatically. The list formatter combines the formatted strings regardless of whether they use abbreviations or full names.

const distances = [1, 5];

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer',
  unitDisplay: 'long'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// Output: "1 kilometer, 5 kilometers"

The number formatter uses "kilometer" for 1 and "kilometers" for 5. The list formatter combines them with appropriate separators.

Reuse formatters for better performance

Creating Intl.NumberFormat and Intl.ListFormat instances involves loading locale data and processing options. When you format multiple lists of measurements, create the formatters once and reuse them.

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'unit',
  unit: 'kilometer'
});

const listFormatter = new Intl.ListFormat('en-US', {
  type: 'unit'
});

const distanceLists = [
  [5, 10, 15],
  [20, 25, 30],
  [35, 40, 45]
];

distanceLists.forEach(distances => {
  const formattedDistances = distances.map(d => numberFormatter.format(d));
  console.log(listFormatter.format(formattedDistances));
});
// Output:
// "5 km, 10 km, 15 km"
// "20 km, 25 km, 30 km"
// "35 km, 40 km, 45 km"

This pattern creates each formatter once and uses it multiple times. The performance difference becomes significant when formatting many lists.

Create a reusable formatter function

You can encapsulate the two-step formatting pattern in a reusable function.

function formatMeasurementList(values, locale, unit) {
  const numberFormatter = new Intl.NumberFormat(locale, {
    style: 'unit',
    unit: unit
  });

  const formattedValues = values.map(v => numberFormatter.format(v));

  const listFormatter = new Intl.ListFormat(locale, {
    type: 'unit'
  });

  return listFormatter.format(formattedValues);
}

console.log(formatMeasurementList([5, 10, 15], 'en-US', 'kilometer'));
// Output: "5 km, 10 km, 15 km"

console.log(formatMeasurementList([50, 75, 100], 'en-US', 'kilogram'));
// Output: "50 kg, 75 kg, 100 kg"

console.log(formatMeasurementList([20, 22, 25], 'en-US', 'celsius'));
// Output: "20°C, 22°C, 25°C"

This function handles any measurement type and locale. You can extend it to accept additional formatting options.

function formatMeasurementList(values, locale, unit, options = {}) {
  const numberFormatter = new Intl.NumberFormat(locale, {
    style: 'unit',
    unit: unit,
    ...options
  });

  const formattedValues = values.map(v => numberFormatter.format(v));

  const listFormatter = new Intl.ListFormat(locale, {
    type: 'unit'
  });

  return listFormatter.format(formattedValues);
}

console.log(formatMeasurementList(
  [5.123, 10.789, 15.456],
  'en-US',
  'kilometer',
  { maximumFractionDigits: 1 }
));
// Output: "5.1 km, 10.8 km, 15.5 km"

console.log(formatMeasurementList(
  [1, 5, 10],
  'en-US',
  'kilometer',
  { unitDisplay: 'long' }
));
// Output: "1 kilometer, 5 kilometers, 10 kilometers"

The function passes additional options to the number formatter, allowing control over decimal places, unit display, and other formatting settings.

Format lists for user's locale

Instead of hardcoding a specific locale, you can use the user's browser language preferences. The navigator.language property returns the user's preferred locale.

const userLocale = navigator.language;

const distances = [5, 10, 15];

const numberFormatter = new Intl.NumberFormat(userLocale, {
  style: 'unit',
  unit: 'kilometer'
});

const formattedDistances = distances.map(d => numberFormatter.format(d));

const listFormatter = new Intl.ListFormat(userLocale, {
  type: 'unit'
});

console.log(listFormatter.format(formattedDistances));
// Output varies by user's locale

This approach displays measurement lists according to each user's formatting expectations. Different users see the same data formatted according to their locale conventions.

Display measurement lists in applications

You can use this pattern anywhere you display multiple measurements to users. This includes fitness applications showing split times, weather applications showing temperature forecasts, recipe applications showing ingredient amounts, and scientific applications showing experimental data.

const splitTimes = [5, 10, 15, 20];

const numberFormatter = new Intl.NumberFormat(navigator.language, {
  style: 'unit',
  unit: 'kilometer',
  maximumFractionDigits: 1
});

const formattedTimes = splitTimes.map(t => numberFormatter.format(t));

const listFormatter = new Intl.ListFormat(navigator.language, {
  type: 'unit'
});

const result = listFormatter.format(formattedTimes);

document.getElementById('split-times').textContent = result;
// Displays: "5 km, 10 km, 15 km, 20 km" (or locale equivalent)

The formatted strings work like any other string value. You can insert them into text content, attributes, or any context where you display information to users.