anyamount API reference

Overview

anyamount is a tiny number formatter built entirely on the native Intl.NumberFormat browser API. One function, one options object, three modes — plus a helper for bare currency symbols. The 1.x API is stable: new options arrive in minors, breaking changes only in majors.

The browser already knows how to format numbers, money, and units in 200+ languages. anyamount just makes that API pleasant to use.

import { anyamount } from 'anyamount'

anyamount(1234567)
// "1.2M"  — smart mode (default)

anyamount(1999, { mode: 'currency', currency: 'EUR' })
// "€1,999.00"

anyamount(3.2, { mode: 'unit', unit: 'gigabyte' })
// "3.2 GB"

Install

npm install anyamount
# or
pnpm add anyamount
# or
yarn add anyamount

Or take the whole family at once with npm install anyfamily.

anyamount()

The single entry point. Pass a number, optionally pass options.

anyamount(value)
anyamount(value, options?)

anyamount(1234567)
// runtime locale, smart mode

anyamount(9999, { locale: 'en' })
// "9,999"  — below the compact cutoff

anyamount(1234567, { locale: 'en', style: 'long' })
// "1.2 million"

anyamount(1999.99, { mode: 'currency', currency: 'EUR', locale: 'en', digits: 0 })
// "€2,000"

Migrating from 1.x

2.0 removed the separate anyamountPartsand the other extra exports — they are the same functions and values, reached through the one name the package exports.

- import { anyamount, anyamountParts, anyamountSymbol } from 'anyamount'
+ import { anyamount } from 'anyamount'

- anyamountParts(1999, opts)
+ anyamount.parts(1999, opts)

- anyamountSymbol('USD')
+ anyamount.symbol('USD')

Arguments, return values and throwing behaviour are unchanged, and nothing else in the API moved. Every any* package follows this shape from 2.0 on: the bare call does the job, everything else hangs off the same name.

anyamount.parts()

Same arguments as anyamount(), but returns the Intl.NumberFormat.formatToParts output unchanged — style the number apart from the currency symbol or unit, or rebuild the output your own way.

import { anyamount } from 'anyamount'

anyamount.parts(1999, { mode: 'currency', currency: 'EUR', locale: 'en' })
// [
//   { type: 'currency', value: '€' },
//   { type: 'integer', value: '1' },
//   { type: 'group', value: ',' },
//   { type: 'integer', value: '999' },
//   { type: 'decimal', value: '.' },
//   { type: 'fraction', value: '00' },
// ]

// React: shrink the currency symbol
anyamount.parts(price, { mode: 'currency', currency: 'EUR' }).map((p, i) =>
  p.type === 'currency' ? <small key={i}>{p.value}</small> : p.value,
)

Note: part values keep the original Intl characters — the space between number and unit can be U+00A0 or U+202F (no-break spaces) depending on locale and ICU version.

anyamount.symbol()

Resolves an ISO 4217 code to its localized symbol, with no number attached — for labels, currency pickers, and input affixes, where the amount is rendered separately (or not at all).

import { anyamount } from 'anyamount'

anyamount.symbol('USD', { locale: 'en' })   // "$"
anyamount.symbol('EUR', { locale: 'en' })   // "€"
anyamount.symbol('GBP', { locale: 'en' })   // "£"
anyamount.symbol('JPY', { locale: 'ja' })   // "¥"
anyamount.symbol('RUB', { locale: 'ru' })   // "₽"

anyamount.symbol('USD', { locale: 'en', display: 'code' })   // "USD"
anyamount.symbol('USD', { locale: 'en', display: 'name' })   // "US dollars"

display defaults to 'narrowSymbol' — the bare symbol, never the disambiguated US$some locales prefer. Codes with no symbol in the locale's data come back as the code itself.

Note: a malformed code throws a RangeError straight from Intl — 'US' is not a currency. Formatting a full amount? Stay in currency mode with currencyDisplay; this is the escape hatch for when there is no amount.

Modes

The mode option picks the rendering strategy. Each mode reads only the options that apply to it — the rest are ignored.

smart (default)

Compact notation for big numbers, plain formatting for small ones. The cutoff is |value| >= 10000.

1234567→ "1.2M"
10000→ "10K"
9999→ "9,999"
42→ "42"
0.1234→ "0.12"

reads: locale, style, digits

currency

Money via the Intl.NumberFormat currency style. currency is required — any ISO 4217 code. Missing it throws a TypeError.

anyamount(1999, { mode: 'currency', currency: 'EUR', locale: 'en' })
// "€1,999.00"

anyamount(1999, { mode: 'currency', currency: 'RSD', locale: 'sr' })
// "1.999,00 RSD"

anyamount(1999, { mode: 'currency', currency: 'JPY', locale: 'ja' })
// "¥1,999"  — JPY has no minor unit, Intl knows

anyamount(1999.99, { mode: 'currency', currency: 'EUR', locale: 'en', digits: 0 })
// "€2,000"

currencyDisplay picks how the currency itself is spelled — symbol by default, opt into anything else.

anyamount(1999, { mode: 'currency', currency: 'USD', locale: 'en' })
// "$1,999.00"  — 'symbol' (default)

anyamount(1999, { mode: 'currency', currency: 'USD', locale: 'en-CA', currencyDisplay: 'narrowSymbol' })
// "$1,999.00"  — bare symbol, where the locale would print "US$"

anyamount(1999, { mode: 'currency', currency: 'USD', locale: 'en', currencyDisplay: 'code' })
// "USD 1,999.00"

anyamount(1999, { mode: 'currency', currency: 'USD', locale: 'en', currencyDisplay: 'name' })
// "1,999.00 US dollars"

reads: locale, currency, currencyDisplay, digits

unit

Measurements via the Intl.NumberFormat unit style. unit is required — any sanctioned identifier, including compound -per- pairs. Missing it throws a TypeError.

anyamount(3.2, { mode: 'unit', unit: 'gigabyte', locale: 'en' })
// "3.2 GB"

anyamount(120, { mode: 'unit', unit: 'kilometer-per-hour', locale: 'en' })
// "120 km/h"

anyamount(3.2, { mode: 'unit', unit: 'gigabyte', locale: 'en', style: 'long' })
// "3.2 gigabytes"

anyamount(5, { mode: 'unit', unit: 'kilometer', locale: 'en', style: 'narrow' })
// "5km"

reads: locale, unit, style, digits

Units

Intl supports a fixed, sanctioned list of unit identifiers (from ECMA-402), plus any <unit>-per-<unit> compound of them. anyamount ships the full list as a TypeScript union, so invalid units fail at compile time.

acre bit byte celsius centimeter day degree fahrenheit
fluid-ounce foot gallon gigabit gigabyte gram hectare hour
inch kilobit kilobyte kilogram kilometer liter megabit
megabyte meter microsecond mile mile-scandinavian milliliter
millimeter millisecond minute month nanosecond ounce percent
petabyte pound second stone terabit terabyte week yard year
// compounds work too
anyamount(120, { mode: 'unit', unit: 'kilometer-per-hour' })   // "120 km/h"
anyamount(8.5, { mode: 'unit', unit: 'liter-per-kilometer' })  // "8.5 L/km"
anyamount(2, { mode: 'unit', unit: 'meter-per-second' })       // "2 m/s"

Options

mode'smart' | 'currency' | 'unit'default: 'smart'

Rendering strategy. Each mode reads only the options that apply to it.

localestring | string[]default: runtime locale

Any valid BCP 47 locale tag, or a fallback array — 'en', 'en-US', 'zh-TW', ['sr-Latn-RS', 'en'].

currencystring

Currency mode only, required. Any ISO 4217 code — 'EUR', 'USD', 'JPY', 'RSD'.

currencyDisplay'symbol' | 'narrowSymbol' | 'code' | 'name'default: 'symbol'

Currency mode only. How the currency is spelled: '$1,999.00', 'USD 1,999.00', or '1,999.00 US dollars'. 'narrowSymbol' keeps the bare '$' where a locale would print 'US$'.

unitUnit

Unit mode only, required. A sanctioned unit identifier or a compound '<unit>-per-<unit>' pair. Typed as a union — your editor autocompletes it.

style'long' | 'short' | 'narrow'default: 'short'

Smart and unit modes. Wording length: '1.2M' vs '1.2 million', '3.2 GB' vs '3.2 gigabytes'.

digitsnumberdefault: per mode

maximumFractionDigits — a ceiling, not a fixed width: trailing zeros are not padded on, so digits: 2 renders 2.5, not 2.50. Defaults: smart — 2 plain / 1 compact, unit — 2, currency — the currency's own (which it keeps as a minimum).

What breaks without this

Every one of these is an assumption baked into a hand-written formatter, and each is wrong somewhere.

The symbol is not always in front

'$' + value.toFixed(2) is a German price written backwards: de-DE puts the symbol last and swaps both separators — 1.999,00 €. Two locales sharing a currency need not write it the same way.

toFixed is not money rounding

It rounds a binary double, so the half-way cases go where the bits fall rather than where accounting expects. It also assumes two decimals, which JPY does not have and KWD exceeds — the right number of digits is a fact about the currency, and Intl already knows it.

Compact notation is not K and M

1.2M is English. Russian writes 1,2 млн; Japanese groups by ten-thousands and writes 123万, a different place value, not a translated suffix. A K/M/B table cannot be localized into being correct.

Units are a closed list, and they inflect

Writing "3.2 GB" by hand skips the part where the unit name agrees with the number and the locale. Intl takes a sanctioned list of units and handles both — outside that list there is no localized name to be had, from any library.

Recipes

Copy, paste, move on.

// Dashboard stat
anyamount(views, { locale: 'en' })
// "1.2M"

// …spelled out
anyamount(views, { locale: 'en', style: 'long' })
// "1.2 million"

// Price
anyamount(product.cents / 100, { mode: 'currency', currency: 'EUR', locale: 'de' })
// "1.999,00 €"

// Price with no cents
anyamount(total, { mode: 'currency', currency: 'EUR', digits: 0 })
// "€2,000"

// Storage meter
anyamount(file.gb, { mode: 'unit', unit: 'gigabyte' })
// "3.2 GB"

// Speed, compound unit
anyamount(120, { mode: 'unit', unit: 'kilometer-per-hour', locale: 'ru' })
// "120 км/ч"

// Currency affix inside an input, amount rendered separately
anyamount.symbol(account.currency)
// "$" 

React / Next.js

anyamount is pure and synchronous, so it works in a component as-is. Whatanyfamily-react adds is a shared locale: set it once onAnyfamilyProvider and every hook below picks it up, so you do not thread locale through every call.

import { AnyfamilyProvider, useAnyamount } from 'anyfamily-react'

function Price({ cents }: { cents: number }) {
  return <b>{useAnyamount(cents / 100, { mode: 'currency', currency: 'EUR' })}</b>
}

<AnyfamilyProvider locale="de">
  <Price cents={199900} />
</AnyfamilyProvider>

`useAnyamountSymbol` is there too, for the bare currency symbol.

Locales

Same calls in a few languages — no extra setup, no locale files.

// smart mode
anyamount(1234567, { locale: 'ru' })   // "1,2 млн"
anyamount(1234567, { locale: 'de' })   // "1,2 Mio."
anyamount(1234567, { locale: 'ja' })   // "123.5万"

// currency mode
anyamount(1999, { mode: 'currency', currency: 'USD', locale: 'de' })
// "1.999,00 $"
anyamount(1999, { mode: 'currency', currency: 'INR', locale: 'hi' })
// "₹1,999.00"

// unit mode
anyamount(120, { mode: 'unit', unit: 'kilometer-per-hour', locale: 'ru' })
// "120 км/ч"

Pass any valid BCP 47 language tag — including regional variants like en-GB, zh-TW, or pt-BR. Locale is optional; when omitted, native Intl uses the runtime locale. Fallback arrays like ['sr-Latn-RS', 'en'] also work.

Output is pure — no clock reads, no environment sniffing — so server and client render identically. SSR-safe by construction.

vs the alternatives

What you would otherwise reach for, and what changes if you do.

anyamountnumeral.jsaccounting.js
locale data bundlednone (Intl)one file per localenone, you configure it
locales200+registered by handwhatever you pass
currency rulesfrom the currencymanual symbolmanual symbol
decimal digitsper currencymanualmanual
unitssanctioned listnono
compact notationevery localeEnglish formsno
dependencies000

anyamount is 0.9kb gzipped and formats numbers. It is not a money type: it does not add prices, hold exchange rates, or protect you from floating-point arithmetic. Do the arithmetic in minor units or in a decimal library, then hand the result here to be written down.

Compatibility

anyamount uses Intl.NumberFormat with compact notation and unit support — widely available since 2020.

Node.js18+CI runs the suite on Node 20, 22, 24
Chrome77+
Firefox78+
Safari14.1+
Edge79+
Vercel Edge Runtime
Cloudflare Workers
Deno

Limitations

A few things worth knowing before you ship:

No byte auto-scaling yet

anyamount(3200000000, { mode: 'unit', unit: 'byte' }) will not pick GB for you — pass the unit you want. Automatic scaling is planned for a future minor.

Output depends on the runtime's Intl data

anyamount delegates all formatting to native Intl. Exact output — separators, spacing, compact suffixes — may vary between Node versions, browsers, and OSes. Don't hardcode expected strings in tests; use pattern matching instead.

Sanctioned units only

Intl supports a fixed list of unit identifiers and -per- compounds of them. There is no way to format arbitrary custom units — that's an Intl constraint, not an anyamount one.

Deliberately small

One function, three modes, on purpose. No percent mode, no ranges, no parsing. anyamount follows semver — the 1.x API is stable, new options arrive in minors, breaking changes only in majors.