numbro

A JavaScript library for formatting and manipulating numbers as currency, percentages, bytes, ordinals, and time, with built-in locale support.

Library
npm
v2.5.0
1,141stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
49/100Fair
Development Activity0
Maintenance32
Community76
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
71/100Good
Architecture78
Code Quality82
Innovation75
Learning Curve50

Numbro is a mature JavaScript library, forked from the original Numeral.js, for turning raw numbers into human-friendly display strings and back again. A single numbro(value) wrapper exposes chainable formatting methods — .format(), .formatCurrency(), .formatTime(), and byte-unit helpers — alongside arithmetic helpers (add, subtract, multiply, divide, difference) that operate on the wrapped value.

Under the hood, numbro delegates to bignumber.js for its arithmetic, which avoids the floating-point rounding artifacts that plain JavaScript number math introduces when formatting currency or percentages. Formatting is driven by a compact format-string syntax (mirroring Numeral.js’s 0,0.00 style tokens) that is parsed once and reused across format, unformat, and validate calls.

A standout feature is its language-pack system: 61 bundled locale files under languages/ register currency symbols, delimiters, abbreviations, and ordinal rules per BCP 47 tag, and numbro.registerLanguage() / numbro.setLanguage() let consuming apps swap locales at runtime or load only the ones they need via loadLanguagesInNode(). This makes numbro a common choice for dashboards and financial UIs that need locale-correct number, currency, and byte-size display without pulling in a full i18n framework.

What You Get

  • A single numbro(value) factory returning a chainable instance with .format(), .formatCurrency(), .formatTime(), .binaryByteUnits(), .decimalByteUnits(), and .byteUnits() output methods
  • Arithmetic helpers (add, subtract, multiply, divide, difference, set) that operate on the wrapped value via BigNumber.js for rounding-safe math
  • A compact, reusable format-string syntax (e.g. "$0,0.00", "0 b", "0o") shared across format, unformat, and validate
  • 61 bundled language packs covering currency symbols, thousands/decimal delimiters, abbreviations, and ordinal suffixes, switchable at runtime with setLanguage()
  • A numbro.validate() helper to check whether a string parses cleanly against a given format before committing it back to a number
  • TypeScript typings (numbro.d.ts) shipped alongside the CommonJS, ES module, and minified UMD builds produced by Rollup

Common Use Cases

  • Formatting currency amounts and percentages consistently across a multi-currency dashboard or invoicing UI
  • Converting raw byte counts (file sizes, transfer totals, storage usage) into human-readable binary or decimal units
  • Displaying locale-correct number formatting (delimiters, currency symbols, abbreviations) for an internationalized product
  • Parsing user-typed numeric input (e.g. "1,234.50" or "$1.2m") back into a plain number for calculations or storage

Under The Hood

Architecture The library centers on a single Numbro class defined in src/numbro.js, which wraps a raw value and delegates almost all behavior to sibling modules constructed as factories that close over the numbro singleton (formatter = require("./formatting")(numbro), manipulate = require("./manipulating")(numbro)) so those modules can recursively invoke top-level numbro static methods without a circular import. Formatting concerns are split cleanly by responsibility: formatting.js builds output strings by output type (currency, percent, byte, time, ordinal, number), parsing.js turns format-string tokens into option objects, unformatting.js reverses a formatted string back to a number, validating.js checks strings against formats, and globalState.js centralizes the mutable current-language/current-defaults/current-currency state that the other modules read from. This gives the codebase a clear one-module-per-concern layering with no cross-cutting god object beyond the thin Numbro façade itself, though the module-level mutable global state (current language, defaults) is a coupling point anything touching formatting has to be aware of.

Tech Stack numbro is plain ES6 JavaScript (CommonJS require/module.exports, no TypeScript source) with a single runtime dependency, bignumber.js (^8 || ^9), used throughout manipulating.js and formatting.js for precision-safe decimal math. The build pipeline is Rollup (@rollup/plugin-commonjs, @rollup/plugin-node-resolve, @rollup/plugin-terser) producing a CommonJS bundle, an ES-module build (dist/es/numbro.js, used as the browser field), and a minified UMD file, with hand-maintained TypeScript typings (numbro.d.ts) shipped alongside rather than generated from source. There’s no server, database, or framework surface at all — it’s a pure formatting/computation library meant to be imported into any JS runtime.

Code Quality Tests run under Jasmine (npx jasmine), with a large tests/languages/ suite (62 files) that exercises every bundled locale plus a tests/src/ suite (9 files) covering the core formatting/parsing/manipulating modules, and nyc enforces 100% line, branch, statement, and function coverage via coverage:check — a notably strict bar. Separate GitHub Actions workflows run lint, test, and coverage independently, and ESLint runs with custom rules from a project-local eslint_rules/ directory in addition to eslint-plugin-jasmine. Naming is consistent camelCase throughout, error handling is explicit (invalid formats return an "ERROR: invalid format" string rather than throwing or silently coercing), and there is no TypeScript on the source itself — types are maintained by hand in a separate .d.ts file, which is a drift risk the tests don’t catch.

API Design The public surface is small and consistent: every consumer touches numbro(value) once and then chains .format()/.formatCurrency()/.formatTime()/byte-unit methods or arithmetic methods off the returned instance, and the same format-string syntax works for both format() and the reverse unformat()/validate() operations, which keeps the mental model minimal. The locale system is a genuine strength for developer experience — registering or switching a language is a single setLanguage() call rather than a separate i18n dependency — though the reliance on shared mutable global state for the current language/defaults means format calls are not purely referentially transparent (two calls to .format() with the same instance can differ if global defaults changed in between), a tradeoff worth knowing about before using it in concurrent or multi-tenant formatting contexts.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search