How to format percentages with percent sign

Use the percent style in Intl.NumberFormat to display numbers as percentages with locale-appropriate formatting

Introduction

Percentages appear in many applications, from displaying completion progress to showing interest rates and discount amounts. A simple approach like concatenating a number with a percent sign works for basic cases, but it fails to account for how different languages and regions format percentages. In Turkish, the percent sign appears before the number as %50. In French, a space separates the number from the percent sign as 50 %. Arabic uses a special percent character instead of the standard % symbol.

JavaScript's Intl.NumberFormat API handles these formatting differences automatically. By setting the style option to "percent", you get correctly formatted percentages for any locale without writing locale-specific code.

Why percentage formatting varies by locale

The percent sign position and spacing rules differ across locales. English places the percent sign directly after the number with no space. French adds a space before the percent sign. Turkish places the percent sign before the number. These variations reflect the natural reading patterns and conventions of each language.

The formatting differences extend beyond just sign placement. Some locales use different characters for the percent symbol. Arabic uses ٪ (U+066A) instead of the ASCII percent sign. The decimal separator and thousands separator also vary by locale, just as they do with regular number formatting.

When you hardcode percentage formatting with string concatenation, you force all users to see the format of a single locale. A French user seeing 50% instead of 50 % experiences an unnatural format. A Turkish user seeing 50% when they expect %50 faces the same issue. The Intl API solves this by applying the correct formatting rules for each locale.

Formatting numbers as percentages

The style option in Intl.NumberFormat controls whether a number formats as a plain number, currency, percentage, or unit. Set style to "percent" to format numbers as percentages.

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

console.log(formatter.format(0.75));
// Output: "75%"

console.log(formatter.format(0.05));
// Output: "5%"

console.log(formatter.format(1.5));
// Output: "150%"

The formatter automatically multiplies the input value by 100 and appends the percent sign. This means you pass fractional values to the formatter. A value of 0.75 becomes 75%. A value of 1.5 becomes 150%.

This fractional input convention aligns with how percentages work in mathematics and in most programming calculations. When you calculate a percentage like growth rate or completion ratio, the result is typically a decimal value between 0 and 1. You can pass this value directly to the formatter without converting it to a percentage first.

Understanding fractional input values

The percentage formatter expects fractional input where 1.0 represents 100%. This design choice matches how percentages are calculated in code.

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

// Calculating completion percentage
const completed = 75;
const total = 100;
const ratio = completed / total;

console.log(formatter.format(ratio));
// Output: "75%"

// Calculating growth rate
const oldValue = 1000;
const newValue = 1250;
const growth = (newValue - oldValue) / oldValue;

console.log(formatter.format(growth));
// Output: "25%"

When you divide completed by total, you get 0.75. Pass this directly to the formatter, and it displays as 75%. When you calculate growth as the ratio of change to original value, you get 0.25. The formatter displays this as 25%.

This approach prevents a common error where developers multiply by 100 before formatting, resulting in values like 7500% instead of 75%. The formatter handles the multiplication, so you work with the natural decimal representation in your code.

Handling values outside the 0-1 range

Percentages are not limited to values between 0% and 100%. Values below zero represent negative percentages. Values above 1.0 represent percentages greater than 100%.

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

console.log(formatter.format(-0.15));
// Output: "-15%"

console.log(formatter.format(2.5));
// Output: "250%"

console.log(formatter.format(0.0025));
// Output: "0%"

Negative values format with a minus sign. This is useful for displaying decreases, losses, or negative growth rates. Values greater than 1.0 format as percentages above 100%, which is common for displaying year-over-year growth or comparisons where the new value exceeds the baseline.

Very small values might round to 0% due to the default precision settings. The value 0.0025, which represents 0.25%, displays as 0% because the default maximum fraction digits for percentages is 0. The next lesson covers controlling decimal places to show these smaller percentages accurately.

Formatting percentages in different locales

Percentage formatting adapts to the locale you specify. Each locale applies its own rules for sign placement, spacing, and symbols.

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

console.log(enFormatter.format(0.5));
// Output: "50%"

const frFormatter = new Intl.NumberFormat("fr-FR", {
  style: "percent"
});

console.log(frFormatter.format(0.5));
// Output: "50 %"

const trFormatter = new Intl.NumberFormat("tr-TR", {
  style: "percent"
});

console.log(trFormatter.format(0.5));
// Output: "%50"

const arFormatter = new Intl.NumberFormat("ar-SA", {
  style: "percent"
});

console.log(arFormatter.format(0.5));
// Output: "٪50"

English places the percent sign immediately after the number. French adds a space before the percent sign. Turkish places the percent sign before the number. Arabic uses the Arabic percent sign and follows right-to-left text direction.

These differences happen automatically based on the locale parameter. You do not need to write conditional logic to handle different locales. Create a formatter with the user's locale, and it produces the correct output for that locale.

Controlling decimal places in percentages

By default, percentage formatting shows no decimal places. A value like 0.755 formats as 76% after rounding. You can control the number of decimal places using the minimumFractionDigits and maximumFractionDigits options.

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

console.log(defaultFormatter.format(0.755));
// Output: "76%"

const oneDecimalFormatter = new Intl.NumberFormat("en-US", {
  style: "percent",
  minimumFractionDigits: 1,
  maximumFractionDigits: 1
});

console.log(oneDecimalFormatter.format(0.755));
// Output: "75.5%"

const twoDecimalFormatter = new Intl.NumberFormat("en-US", {
  style: "percent",
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(twoDecimalFormatter.format(0.755));
// Output: "75.50%"

The minimumFractionDigits option ensures the formatter displays at least that many decimal places, adding trailing zeros if needed. The maximumFractionDigits option limits the number of decimal places, rounding the value if necessary.

Setting both to the same value ensures consistent formatting across all percentages. This is useful in tables and charts where you want all percentages to align visually.

The next lesson covers decimal place control in greater depth, including how to handle trailing zeros and display very small percentages accurately.

Combining percentages with sign display

Percentage formatting works with the signDisplay option to explicitly show plus signs for positive percentages. This is useful when displaying changes or growth rates where the sign indicates direction.

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

console.log(formatter.format(0.0523));
// Output: "+5.2%"

console.log(formatter.format(-0.0341));
// Output: "-3.4%"

console.log(formatter.format(0));
// Output: "+0.0%"

The signDisplay option was covered in a previous lesson. When combined with percentage formatting, it clarifies whether a percentage represents an increase or decrease. Without the explicit plus sign, positive percentages could be ambiguous in contexts where both gains and losses are displayed.

Formatting zero as a percentage

Zero formats as 0% by default. How you handle zero depends on whether zero represents no change, no value, or a meaningful measurement.

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

console.log(formatter.format(0));
// Output: "0%"

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

console.log(withSignFormatter.format(0.05));
// Output: "+5%"

console.log(withSignFormatter.format(0));
// Output: "0%"

Using signDisplay: "exceptZero" shows signs for positive and negative percentages but omits the sign for zero. This makes zero visually distinct when it represents a neutral state like no change.

When to use percentage formatting

Use percentage formatting when displaying ratios, rates, or proportions that are naturally expressed as percentages. Common use cases include completion progress, interest rates, discount amounts, growth rates, success rates, and probability values.

Do not use percentage formatting when you need the percent sign to appear in a specific position regardless of locale. If your design requires the percent sign to always appear on the right for visual consistency, use string concatenation instead. However, this sacrifices internationalization correctness.

Also avoid percentage formatting when working with values that are already multiplied by 100. If your data stores percentages as whole numbers where 75 means 75%, divide by 100 before formatting or use plain number formatting with a percent sign appended manually.

Understanding percentage formatter precision

The default precision for percentage formatting is 0 decimal places. This differs from plain number formatting, which adapts precision based on the input value. The percentage formatter rounds values to whole percentages unless you explicitly set fraction digit options.

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

console.log(formatter.format(0.755));
// Output: "76%"

console.log(formatter.format(0.754));
// Output: "75%"

console.log(formatter.format(0.001));
// Output: "0%"

Values round to the nearest whole percentage. The value 0.755 rounds to 76%. The value 0.754 rounds to 75%. Very small values like 0.001 round to 0%.

If you need to display small percentages or fractional percentages, increase the maximum fraction digits. The next lesson covers precision control in detail.