Skip to content

NumberFormat

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/number-format.json
See it liveThe same number in four languages, money and percentages, and a column with and without tabular figures.→

The separators are the language’s. A thousand is 1,000 here, 1 000 there and 1.000 somewhere else — and the last one is the same string another reader would read as one. Intl knows this and a product does not have to.

The figures line up. tabular-nums gives every digit the same width, so a column of numbers has its digits above each other and the eye can compare lengths without reading. Without it a proportional font gives 1 less room than 8, the column ripples, and the only way to tell 9,999 from 10,000 is to count.

This is the part that gets left out, because it looks fine on the one number a developer tries it on and only fails in a column — which is exactly where numbers live. Having it in a component is how it stops being remembered.

<NumberFormat value={1234567} />
<NumberFormat value={1299.5} style="currency" currency="USD" />
<NumberFormat value={0.427} style="percent" maximumFractionDigits={1} />
<NumberFormat value={1200000} notation="compact" /> {/* 1.2M */}

The language is the application’s, not the browser’s. Left unset, the locale is useLocale()’s answer — the nearest LocaleProvider, else the page’s <html lang>. It used to be the browser’s language, which is how kasl-server showed an English interface with Russian week headings to a reader whose browser spoke Russian. Passing 'en-US' to be safe is the opposite mistake: a German reader shown American separators for the life of the product. See no-implicit-locale.

Intl’s options, not a second vocabulary. Currency, percentages and units take the same options object, so those are not three more components. What Intl does not decide is compactness: 1.2M is a choice about how much precision the reader is owed, and it is the caller’s, in notation.

style means Intl’s style here, not a CSS object — the two collide on the name, and the formatting one wins. Inline styles are given up in exchange, which costs nothing: appearance is the theme’s business and className is still there.

value is a number, not number | null. A row with no value shows whatever the product says absence looks like — a dash, a word, an empty cell — and that is a decision about the data, not about formatting. See table-sort for the same distinction in the ordering.

formatNumber(value, locale, options?) gives the string alone, for a title, an aria-label or a CSV.

Props

Read from the types in ui/number-format.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

PropTypeDefault
valuerequirednumberThe number to format.
localestringThe application's language by default - see useLocale.

And every prop of React's HTMLAttributes<HTMLSpanElement> except children, style and Intl.NumberFormatOptions, passed on.

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 4 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/number-format.tsx - the file a product copies - newest first.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.31.0breaking
    • feat dates and numbers in the application's language 9be5f95
  3. v0.21.0
    • feat a table, its pages, and the two ways a cell is written 32dc405