# Consistent Currency Formatting

**MUST** · **ID:** `content-currency-formatting` · **Category:** content
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/content-currency-formatting

> MUST: Pick 0 or 2 decimal places per context and format every amount that way, including round ones — never mix `$12` with `$8.50` in one column. Pair with tabular figures and right alignment so decimal separators stack.

Pick 0 or 2 decimal places for a given context and apply it to every amount, never mixing the two

A column that reads $12, $8.50, $1,204, $99.00 forces the eye to re-parse each row: the decimal point lands in a different place every time, so the digits no longer line up and the magnitudes stop being comparable at a glance. Choose the precision the context needs — whole dollars for a pricing page, cents for an invoice — and format every amount that way, including the round ones. Pair the choice with tabular figures and right alignment so the decimal separators stack into a single vertical line.

## Bad — do not do this

`content-currency-formatting-bad`

```tsx
const ROWS = [
  { item: 'Pro seats (3)', amount: '$60' },
  { item: 'Bandwidth overage', amount: '$8.50' },
  { item: 'Edge function invocations', amount: '$1,204' },
  { item: 'Image optimization', amount: '$99.00' },
  { item: 'Support add-on', amount: '$12' },
];

const decimalsUsed = new Set(
  ROWS.map((row) => {
    const fraction = row.amount.split('.')[1];
    return fraction ? fraction.length : 0;
  }),
);

export function CurrencyFormattingBad() {
  return (
    <div className="w-full max-w-md space-y-3">
      <div className="rounded-lg border border-border bg-card p-4">
        <h4 className="mb-3 font-medium text-foreground">Invoice · June</h4>
        <table className="w-full text-sm">
          <tbody>
            {ROWS.map((row) => (
              <tr key={row.item} className="border-b border-border last:border-0">
                <td className="py-2 text-muted-foreground">{row.item}</td>
                <td className="py-2 text-right">
                  <mark className="rounded bg-error/15 px-1 font-semibold text-error">
                    {row.amount}
                  </mark>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <div className="flex flex-wrap items-center gap-2 text-xs">
        <span className="rounded border border-border bg-muted px-2 py-1 font-mono text-error">
          decimal places used: {[...decimalsUsed].sort().join(' and ')}
        </span>
        <span className="text-muted-foreground">in one table</span>
      </div>

      <p className="text-xs text-error">
        $60, $8.50, $1,204, $99.00 — the decimal point lands in a different column on every row, so
        the digits never line up and no two amounts can be compared at a glance.
      </p>
    </div>
  );
}
```

## Good — do this

`content-currency-formatting-good`

```tsx
const ROWS = [
  { item: 'Pro seats (3)', cents: 6000 },
  { item: 'Bandwidth overage', cents: 850 },
  { item: 'Edge function invocations', cents: 120400 },
  { item: 'Image optimization', cents: 9900 },
  { item: 'Support add-on', cents: 1200 },
];

// One context, one precision: an invoice needs cents, so every row gets cents.
const usd = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
});

const total = ROWS.reduce((sum, row) => sum + row.cents, 0);

export function CurrencyFormattingGood() {
  return (
    <div className="w-full max-w-md space-y-3">
      <div className="rounded-lg border border-border bg-card p-4">
        <h4 className="mb-3 font-medium text-foreground">Invoice · June</h4>
        <table className="w-full text-sm">
          <tbody>
            {ROWS.map((row) => (
              <tr key={row.item} className="border-b border-border last:border-0">
                <td className="py-2 text-muted-foreground">{row.item}</td>
                <td className="py-2 text-right font-medium tabular-nums text-foreground">
                  {usd.format(row.cents / 100)}
                </td>
              </tr>
            ))}
            <tr>
              <td className="pt-3 font-medium text-foreground">Total</td>
              <td className="pt-3 text-right font-medium tabular-nums text-foreground">
                {usd.format(total / 100)}
              </td>
            </tr>
          </tbody>
        </table>
      </div>

      <div className="flex flex-wrap items-center gap-2 text-xs">
        <span className="rounded border border-border bg-muted px-2 py-1 font-mono text-success">
          decimal places used: 2 · everywhere
        </span>
        <span className="text-muted-foreground">tabular-nums + right aligned</span>
      </div>

      <p className="text-xs text-success">
        One precision for the whole context, including the round amounts. With tabular figures the
        decimal separators stack into a single vertical line, so magnitudes read off the column.
      </p>
    </div>
  );
}
```

## References

- [Vercel Web Interface Guidelines](https://github.com/vercel-labs/web-interface-guidelines)
- [Microsoft Style: Numbers](https://learn.microsoft.com/en-us/style-guide/numbers)
- [Google Style: Numbers](https://developers.google.com/style/numbers)
