How to format lists with or in JavaScript
Use Intl.ListFormat with type disjunction to format alternatives correctly in any language
Introduction
Applications often present users with choices or alternatives. A file upload component accepts "PNG, JPEG, or SVG" files. A payment form allows "credit card, debit card, or PayPal" as payment methods. An error message suggests fixing "username, password, or email address" to resolve authentication failures.
These lists use "or" to indicate alternatives. Manually formatting them with string concatenation breaks in other languages because different languages have different punctuation rules, different words for "or", and different comma placement conventions. The Intl.ListFormat API with the disjunction type formats these alternative lists correctly for any language.
What disjunctive lists are
A disjunctive list presents alternatives where typically one option applies. The word "disjunction" means separation or alternatives. In English, disjunctive lists use "or" as the conjunction:
const paymentMethods = ["credit card", "debit card", "PayPal"];
// Desired output: "credit card, debit card, or PayPal"
This differs from conjunctive lists that use "and" to indicate all items apply together. Disjunctive lists communicate choice, conjunctive lists communicate combination.
Common contexts for disjunctive lists include payment options, file format restrictions, troubleshooting suggestions, search filter alternatives, and any interface where users select one option from multiple possibilities.
Why manual formatting fails
English speakers write disjunctive lists as "A, B, or C" with commas between items and "or" before the last item. This pattern breaks in other languages:
// Hardcoded English pattern
const items = ["apple", "orange", "banana"];
const text = items.slice(0, -1).join(", ") + ", or " + items[items.length - 1];
// "apple, orange, or banana"
This code produces incorrect output in Spanish, French, German, and most other languages. Each language has distinct formatting rules for disjunctive lists.
Spanish uses "o" without a comma before it:
Expected: "manzana, naranja o plátano"
English pattern produces: "manzana, naranja, or plátano"
French uses "ou" without a comma before it:
Expected: "pomme, orange ou banane"
English pattern produces: "pomme, orange, or banane"
German uses "oder" without a comma before it:
Expected: "Apfel, Orange oder Banane"
English pattern produces: "Apfel, Orange, or Banane"
Japanese uses the particle "か" (ka) with different punctuation:
Expected: "りんご、オレンジ、またはバナナ"
English pattern produces: "りんご、オレンジ、 or バナナ"
These differences go beyond simple word replacement. Punctuation placement, spacing rules, and grammatical particles all vary by language. Manual string concatenation cannot handle this complexity.
Using Intl.ListFormat with disjunction type
The Intl.ListFormat API formats lists according to language-specific rules. Set the type option to "disjunction" to format alternative lists:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const paymentMethods = ["credit card", "debit card", "PayPal"];
console.log(formatter.format(paymentMethods));
// "credit card, debit card, or PayPal"
The formatter handles any array length:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
console.log(formatter.format([]));
// ""
console.log(formatter.format(["credit card"]));
// "credit card"
console.log(formatter.format(["credit card", "PayPal"]));
// "credit card or PayPal"
console.log(formatter.format(["credit card", "debit card", "PayPal"]));
// "credit card, debit card, or PayPal"
The API automatically applies the correct punctuation and conjunction for each case.
Understanding disjunction styles
The style option controls formatting verbosity. Three styles exist: long, short, and narrow. The long style is the default.
const items = ["email", "phone", "SMS"];
const long = new Intl.ListFormat("en", {
type: "disjunction",
style: "long"
});
console.log(long.format(items));
// "email, phone, or SMS"
const short = new Intl.ListFormat("en", {
type: "disjunction",
style: "short"
});
console.log(short.format(items));
// "email, phone, or SMS"
const narrow = new Intl.ListFormat("en", {
type: "disjunction",
style: "narrow"
});
console.log(narrow.format(items));
// "email, phone, or SMS"
In English, all three styles produce identical output for disjunctive lists. Other languages show more variation. German uses "oder" in long style and may abbreviate in narrow style. The differences become more apparent in languages with multiple formality levels or longer conjunction words.
The narrow style typically removes spaces or uses shorter conjunctions to save space in constrained layouts. Use long style for standard text, short style for moderately compact displays, and narrow style for tight space constraints like mobile interfaces or compact tables.
How disjunctive lists appear in different languages
Each language formats disjunctive lists according to its own conventions. Intl.ListFormat handles these differences automatically.
English uses commas with "or":
const en = new Intl.ListFormat("en", { type: "disjunction" });
console.log(en.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG, or SVG"
Spanish uses commas with "o" and no comma before the final conjunction:
const es = new Intl.ListFormat("es", { type: "disjunction" });
console.log(es.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG o SVG"
French uses commas with "ou" and no comma before the final conjunction:
const fr = new Intl.ListFormat("fr", { type: "disjunction" });
console.log(fr.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG ou SVG"
German uses commas with "oder" and no comma before the final conjunction:
const de = new Intl.ListFormat("de", { type: "disjunction" });
console.log(de.format(["PNG", "JPEG", "SVG"]));
// "PNG, JPEG oder SVG"
Japanese uses different punctuation and particles:
const ja = new Intl.ListFormat("ja", { type: "disjunction" });
console.log(ja.format(["PNG", "JPEG", "SVG"]));
// "PNG、JPEG、またはSVG"
Chinese uses Chinese punctuation marks:
const zh = new Intl.ListFormat("zh", { type: "disjunction" });
console.log(zh.format(["PNG", "JPEG", "SVG"]));
// "PNG、JPEG或SVG"
These examples show how the API adapts to each language's grammatical and punctuation conventions. The same code works across all languages when you provide the appropriate locale.
Formatting payment options
Payment forms present multiple payment method choices. Format them with disjunctive lists:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getPaymentMessage(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return `Pay with ${formatter.format(methods)}.`;
}
const methods = ["credit card", "debit card", "PayPal", "Apple Pay"];
console.log(getPaymentMessage(methods));
// "Pay with credit card, debit card, PayPal, or Apple Pay."
For international applications, pass the user's locale:
const userLocale = navigator.language; // e.g., "fr-FR"
const formatter = new Intl.ListFormat(userLocale, { type: "disjunction" });
function getPaymentMessage(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return `Pay with ${formatter.format(methods)}.`;
}
This approach works in checkout flows, payment method selectors, and any interface where users choose how to pay.
Formatting file upload restrictions
File upload components specify which file types the system accepts:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getAcceptedFormatsMessage(formats) {
if (formats.length === 0) {
return "No file formats accepted";
}
if (formats.length === 1) {
return `Accepted format: ${formats[0]}`;
}
return `Accepted formats: ${formatter.format(formats)}`;
}
const imageFormats = ["PNG", "JPEG", "SVG", "WebP"];
console.log(getAcceptedFormatsMessage(imageFormats));
// "Accepted formats: PNG, JPEG, SVG, or WebP"
const documentFormats = ["PDF", "DOCX"];
console.log(getAcceptedFormatsMessage(documentFormats));
// "Accepted formats: PDF or DOCX"
This pattern works for image uploads, document submissions, and any file input with format restrictions.
Formatting troubleshooting suggestions
Error messages often suggest multiple ways to resolve an issue. Present these suggestions as disjunctive lists:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getAuthenticationError(missingFields) {
if (missingFields.length === 0) {
return "Authentication failed";
}
return `Please check your ${formatter.format(missingFields)} and try again.`;
}
console.log(getAuthenticationError(["username", "password"]));
// "Please check your username or password and try again."
console.log(getAuthenticationError(["email", "username", "password"]));
// "Please check your email, username, or password and try again."
The disjunctive list clarifies that users need to fix any of the mentioned fields, not necessarily all of them.
Formatting search filter alternatives
Search interfaces show active filters. When filters present alternatives, use disjunctive lists:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function getFilterSummary(filters) {
if (filters.length === 0) {
return "No filters applied";
}
if (filters.length === 1) {
return `Showing results for: ${filters[0]}`;
}
return `Showing results for: ${formatter.format(filters)}`;
}
const categories = ["Electronics", "Books", "Clothing"];
console.log(getFilterSummary(categories));
// "Showing results for: Electronics, Books, or Clothing"
This works for category filters, tag selections, and any filter interface where selected values represent alternatives rather than combinations.
Reusing formatters for performance
Creating Intl.ListFormat instances has overhead. Create formatters once and reuse them:
// Create once at module level
const disjunctionFormatter = new Intl.ListFormat("en", { type: "disjunction" });
// Reuse in multiple functions
function formatPaymentMethods(methods) {
return disjunctionFormatter.format(methods);
}
function formatFileTypes(types) {
return disjunctionFormatter.format(types);
}
function formatErrorSuggestions(suggestions) {
return disjunctionFormatter.format(suggestions);
}
For applications supporting multiple locales, store formatters in a cache:
const formatters = new Map();
function getDisjunctionFormatter(locale) {
if (!formatters.has(locale)) {
formatters.set(
locale,
new Intl.ListFormat(locale, { type: "disjunction" })
);
}
return formatters.get(locale);
}
const formatter = getDisjunctionFormatter("en");
console.log(formatter.format(["A", "B", "C"]));
// "A, B, or C"
This pattern reduces initialization costs while supporting multiple locales throughout the application.
Using formatToParts for custom rendering
The formatToParts() method returns an array of objects representing each piece of the formatted list. This enables custom styling:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const parts = formatter.formatToParts(["PNG", "JPEG", "SVG"]);
console.log(parts);
// [
// { type: "element", value: "PNG" },
// { type: "literal", value: ", " },
// { type: "element", value: "JPEG" },
// { type: "literal", value: ", or " },
// { type: "element", value: "SVG" }
// ]
Each part has a type and value. The type is either "element" for list items or "literal" for punctuation and conjunctions.
Use this to apply different styles to elements and literals:
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
const formats = ["PNG", "JPEG", "SVG"];
const html = formatter.formatToParts(formats)
.map(part => {
if (part.type === "element") {
return `<code>${part.value}</code>`;
}
return part.value;
})
.join("");
console.log(html);
// "<code>PNG</code>, <code>JPEG</code>, or <code>SVG</code>"
This approach maintains locale-correct punctuation and conjunctions while applying custom presentation to the actual items.
Browser support and compatibility
Intl.ListFormat works in all modern browsers since April 2021. Support includes Chrome 72+, Firefox 78+, Safari 14.1+, and Edge 79+.
Check support before using the API:
if (typeof Intl.ListFormat !== "undefined") {
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
return formatter.format(items);
} else {
// Fallback for older browsers
return items.join(", ");
}
For broader compatibility, use a polyfill like @formatjs/intl-listformat. Install it only where needed:
if (typeof Intl.ListFormat === "undefined") {
await import("@formatjs/intl-listformat/polyfill");
}
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
Given current browser support, most applications can use Intl.ListFormat directly without polyfills.
Common mistakes to avoid
Using conjunction type instead of disjunction produces incorrect meaning:
// Wrong: suggests all methods required
const wrong = new Intl.ListFormat("en", { type: "conjunction" });
console.log(`Pay with ${wrong.format(["credit card", "debit card"])}`);
// "Pay with credit card and debit card"
// Correct: suggests choosing one method
const correct = new Intl.ListFormat("en", { type: "disjunction" });
console.log(`Pay with ${correct.format(["credit card", "debit card"])}`);
// "Pay with credit card or debit card"
Creating new formatters repeatedly wastes resources:
// Inefficient
function formatOptions(options) {
return new Intl.ListFormat("en", { type: "disjunction" }).format(options);
}
// Efficient
const formatter = new Intl.ListFormat("en", { type: "disjunction" });
function formatOptions(options) {
return formatter.format(options);
}
Hardcoding "or" in strings prevents localization:
// Breaks in other languages
const text = items.join(", ") + ", or other options";
// Works across languages
const formatter = new Intl.ListFormat(userLocale, { type: "disjunction" });
const allItems = [...items, "other options"];
const text = formatter.format(allItems);
Not handling empty arrays can cause unexpected output:
// Defensive
function formatPaymentMethods(methods) {
if (methods.length === 0) {
return "No payment methods available";
}
return formatter.format(methods);
}
While format([]) returns an empty string, explicit empty state handling improves user experience.
When to use disjunctive lists
Use disjunctive lists when presenting alternatives or choices where typically one option applies. This includes payment method selection, file format restrictions, authentication error suggestions, search filter options, and account type choices.
Do not use disjunctive lists when all items must apply together. Use conjunction lists instead. For example, "Name, email, and password are required" uses conjunction because all fields must be provided, not just one.
Do not use disjunctive lists for neutral enumerations without choice implications. Measurements and technical specifications typically use unit lists instead of disjunction or conjunction.
The API replaces manual string concatenation patterns for alternatives. Any time you would write code that joins items with "or" for user-facing text, consider whether Intl.ListFormat with disjunction type provides better locale support.