PDFPipe

Formatting and locale / Numbers and money

Which side the currency symbol goes on, and why it matters

The symbol goes before the amount in some conventions and after it in others, with different spacing, and the choice affects both meaning and column alignment.

The defect

Placement is a convention of the locale rather than of the currency, so the same currency is written with the symbol leading in one place and trailing in another, sometimes with a space and sometimes without. Getting it wrong is not usually a misreading, it is a signal: an amount written in a form nobody local writes reads as machine output from a system that does not know where it is sending the document. The bigger practical problem is that a leading symbol and a trailing symbol want opposite alignment treatments in a column, so a system that switches placement by locale and keeps one column layout produces a ragged column in half its markets.

What it does to the document

A trailing symbol pushes the rightmost edge of the figure, so right-aligning the whole string aligns the symbols and leaves the digits ragged, which is the opposite of what a column of money needs. Splitting symbol and digits into separate cells fixes it for both placements at once.

The decisions

Reasons rather than a description of the code. Formatting rules are data that changes, so most of these are about taking the rule from a maintained source rather than encoding today's answer.

  • Take placement from the locale, not from the currency, and take it from a library rather than a table you maintain.
  • Give the symbol its own fixed-width box in any column of figures, so the digits align on their own edge regardless of which side the symbol sits.
  • Use the currency code rather than the symbol wherever the document might be read outside the issuing country, since several currencies share a symbol.
  • Keep the symbol and the amount on one line. A symbol separated from its figure by a line break is a figure with no currency.
  • Do not hardcode the space. Some conventions have one, some do not, and it is part of the locale data.
  • Print the code once prominently rather than repeating it on every row. A column of forty rows each carrying a three-letter code is noise.

In code

The formatting happens before the markup exists, so this is mostly code rather than CSS. The wrong version is kept in a comment where the wrong version is the thing people write.

js
const amount = 4180.5;

// Placement, spacing and symbol all come from the locale.
new Intl.NumberFormat("en-GB", { style: "currency", currency: "GBP" })
  .format(amount);   // symbol leads

new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR" })
  .format(amount);   // symbol trails, with a space

// Where the document may be read outside the issuing country, prefer the
// code, because several currencies share a symbol.
new Intl.NumberFormat("en-GB", {
  style: "currency",
  currency: "USD",
  currencyDisplay: "code",
}).format(amount);

/* In a column, keep the digits on their own alignment edge:
   .amount { display: grid; grid-template-columns: 28pt 1fr; }
   .amount .sym { text-align: left; }
   .amount .num { text-align: right; font-variant-numeric: tabular-nums; }  */

What people do instead

Using the symbol everywhere because it is shorter. Several currencies share the dollar sign and several share the pound sign, so a document that travels carries an amount whose currency is a guess. The code costs two characters and removes the guess.

How to find out rather than assume

Render a column of amounts under a leading-symbol locale and a trailing-symbol locale. In both, the digits should form a straight right edge. Then read an amount aloud to somebody in the destination market and see whether it sounds like something they would write.

Where the API sits in this

Nowhere, and that is worth saying plainly. This API renders HTML to PDF. By the time markup reaches it, every value in it is already a string that somebody's code produced, so there is no locale option on the render because there would be nothing for one to do. What the render controls is the page: the size, the margins, and whether the formatted value still fits the column it landed in. That is why each of these pages has a paragraph about the layout consequence, because that is the half a document adds to the general problem.

Frequently asked

Should the document use the sender's conventions or the reader's?

That is a decision rather than a fact, and the only wrong answer is not making it. The issuer's conventions are defensible for a document that is a record of what the issuer did. The recipient's are defensible for a document meant to be acted on. What fails is a document that mixes them, because a reader who sees two conventions on one page cannot tell which one any given figure is in.

Is it enough to set the document language?

No. A language attribute helps with hyphenation, with the assistive technology reading order and with anything that needs to know what language the text is in, and it changes none of the values. A date formatted as 03/04/2026 is exactly as ambiguous in a document declared as English as in one declared as anything else. The values are formatted upstream.

Why does this belong in a document reference rather than a general one?

Because a document cannot be re-rendered by its reader. On a screen a wrong format is annoying and a user can often change a setting. On paper the format is final, the layout was tuned to the string lengths of one language, and a value that grew by a few characters can push a table onto another page. The formatting problem and the pagination problem are the same problem here.

Related formatting topics

The defects that travel together, then the rest of the same group.

Values are formatted by your code before they reach the markup, so every fix on this page is upstream of the render. What the render decides is whether the result still fits.