A polyfill for Intl.ListFormat tested by the official ECMAScript Conformance test suite
ECMA-402 Spec Compliance#
This package implements Intl.ListFormat. See the conformance report for tested coverage and known limitations.
✅ Implemented Features#
Core Methods#
format(list)- Format a list of strings as a localized stringformatToParts(list)- Format a list and return an array of partsresolvedOptions()- Returns resolved formatting optionssupportedLocalesOf(locales)- Returns supported locales
List Types#
'conjunction'(default) - "and" lists (e.g., "A, B, and C")'disjunction'- "or" lists (e.g., "A, B, or C")'unit'- Unit lists (e.g., "5 pounds, 12 ounces")
List Styles#
'long'(default) - Full format (e.g., "A, B, and C")'short'- Abbreviated format (e.g., "A, B, & C")'narrow'- Most compact format (e.g., "A, B, C")
Example Usage#
Global import#
import '@formatjs/intl-listformat/polyfill.js'
// Conjunction (and)
const conjunctionFormatter = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction',
})
conjunctionFormatter.format(['Apple', 'Banana', 'Orange'])
// "Apple, Banana, and Orange"
// Disjunction (or)
const disjunctionFormatter = new Intl.ListFormat('en', {
type: 'disjunction',
})
disjunctionFormatter.format(['Red', 'Green', 'Blue'])
// "Red, Green, or Blue"
// Unit lists
const unitFormatter = new Intl.ListFormat('en', {
style: 'short',
type: 'unit',
})
unitFormatter.format(['5 lb', '12 oz'])
// "5 lb, 12 oz"
// formatToParts
const parts = conjunctionFormatter.formatToParts(['A', 'B', 'C'])
// [
// {type: "element", value: "A"},
// {type: "literal", value: ", "},
// {type: "element", value: "B"},
// {type: "literal", value: ", and "},
// {type: "element", value: "C"}
// ]
Info
The global import does not include TypeScript type declarations. For TypeScript projects, we recommend using ES module imports instead.
If you choose to use the global import, in order to prevent type errors, you must manually include the corresponding type declaration files (.d.ts) in your project.
ES Modules#
import {ListFormat} from '@formatjs/intl-listformat'
// Conjunction (and)
const conjunctionFormatter = new ListFormat('en', {
style: 'long',
type: 'conjunction',
})
conjunctionFormatter.format(['Apple', 'Banana', 'Orange'])
// "Apple, Banana, and Orange"
Installation#
npm i @formatjs/intl-listformat
Requirements#
Usage#
Via polyfill-fastly.io#
You can use polyfill-fastly.io URL Builder to create a polyfill script tag for Intl.ListFormat. By default the created URL does not come with any locale data. In order to add locale data, append Intl.ListFormat.~locale.<locale> to your list of features. For example:
<!-- Polyfill Intl.ListFormat, its dependencies & `en` locale data -->
<script src="https://polyfill-fastly.io/v3/polyfill.min.js?features=Intl.ListFormat,Intl.ListFormat.~locale.en"></script>
Simple#
import '@formatjs/intl-listformat/polyfill.js'
import '@formatjs/intl-listformat/locale-data/en.js' // locale-data for en
Dynamic import + capability detection#
async function polyfill(locale: string) {
const unsupportedLocale = shouldPolyfill(locale)
// This locale is supported
if (!unsupportedLocale) {
return
}
// Load the polyfill 1st BEFORE loading data
await import('@formatjs/intl-listformat/polyfill-force.js')
await import(`@formatjs/intl-listformat/locale-data/${unsupportedLocale}.js`)
}
Tests#
The Bazel :test262 target runs the upstream suite against a reviewed failure baseline. A green baseline check does not mean every test passed. See the conformance report; run bazel test //packages/intl-listformat:test262-strict to require zero failures.
Iterable inputs#
format and formatToParts accept iterables of strings, including a string's
individual code points. undefined produces an empty list. Non-iterables and
non-string elements throw TypeError; an invalid element also closes the iterator.
Loading und locale data preserves existing English data. Root patterns remain
separate from language-specific patterns, regardless of registration order.
Loading a locale also registers its CLDR default-content variants. For example,
English data supports en-US. Explicitly loaded regional data remains distinct
from its language's data, regardless of load order.
Rust on Hermes#
The Hermes Intl SDK includes Rust ListFormat and
source/bytecode installers. Locale data is compiled into the archive; use the
build-time locale flag to choose a custom set. Individual consumers can link
:listformat and call formatjs::installListFormat(runtime) after canonicalization.
Measured performance#
TypeScript and native JavaScript#
Measured 2026-10-31 on Apple M4 Max, macOS arm64, Node 24.14.0 / ICU 78.2; source 56d15e997. Median of seven warmed rounds.
| Operation | TS ops/s | TS full helpers ops/s | Native JS ops/s |
|---|---|---|---|
| format | 3,400,324 | 3,216,479 | 2,220,136 |
| formatToParts | 3,097,416 | 3,041,058 | 972,724 |
| construct + resolvedOptions | 917,700 | 826,297 | 823,913 |
TS uses host Intl helpers; TS full helpers installs all FormatJS Intl APIs.
Module loading and data registration are excluded. Construction rows include
resolvedOptions(). These are desktop microbenchmarks; native locale data may
differ. † marks noisy rows (round max/min > 1.5). See workloads, commands, and raw results.
Rust core#
| Rust core operation | µs/call | Rust core ops/s |
|---|---|---|
| format | 0.038 | 26,195,586 |
| formatToParts | 0.159 | 6,294,216 |
| construct + resolvedOptions | 0.045 | 22,265,695 |
Same machine and source revision, optimized Rust build, seven warmed rounds. Rust core timings exclude Hermes/JSI and JS boundary work; inputs are prepared Rust strings/UTF-16. Data and API-boundary differences prevent direct speedup claims against TS/native JS. † marks round max/min > 1.5. Workload details and raw results.