NumberFormat

Description

A ready-to-use DNB number formatter. Use it where over you have to display a number, a currency value, phone number, etc.

Good reasons for why we have this is to:

  • uniform the formation of numbers for all DNB applications.
  • and make numbers accessible to screen readers.

Supported formats

  • Numbers in general e.g.
    12 345 678,9
  • Currency e.g.
    12 345 678,90 norske kroner
  • Percentage e.g.
    12,34 %
  • Phone numbers e.g.
    0047 99 99 99 99
  • Bank account number e.g.
    20 00 12 34 56 7
  • National identification number e.g.
    18 08 92 1 2 3 4 5
  • Organization number e.g.
    1 2 3 4 5 6 7 8 9
  • Compact (shorten) numbers e.g.
    123,5 tusen

Defaults

It uses the browser APIs number.toLocaleString or Intl.NumberFormat.format under the hood. As well as some custom formatter. The locale defaults to:

  • Locale: nb-NO
  • Currency: NOK

Decimals

When the amount of wanted decimals is set as a property, but the given value contains decimals that exceeds the wanted decimals, the output value will get rounded up or down. Use omit_rounding if you need to hard-cut decimals from the displayed value.

Provider

You can send down the locale as an application-wide property (Context). More info about the provider and locale usage.

import Provider from '@dnb/eufemia/shared/Provider'
render(
<Provider locale="en-GB" NumberFormat={{ currency_display: 'code' }}>
<MyApp>
text <NumberFormat>123</NumberFormat> table etc.
</MyApp>
</Provider>
)

NumberFormat Hook

Heads up: If you do so, keep in mind, you will have to ensure all the accessibility enhancements the component offers. For that you can use the aria field:

import Provider from '@dnb/eufemia/shared/Provider'
import useNumberFormat from '@dnb/eufemia/components/number-format/useNumberFormat'
function Component() {
// By using returnAria you get an object
const { number, aria } = useNumberFormat(12345678.9, {
// Props are inherited from the Eufemia Provider and the NumberFormat object
returnAria: true,
})
return (
<span>
<span aria-hidden>{number}</span>
<span className="dnb-sr-only">{aria}</span>
</span>
)
}
render(
<Provider locale="en-GB" NumberFormat={{ currency: 'EUR' }}>
<Component />
</Provider>
)

Formatting only (interceptor)

You can use the format method without using a React Component or React Hook.

Heads up: If you do so, keep in mind, you will have to ensure all the accessibility enhancements the component offers. For that you can use the aria field:

import { format } from '@dnb/eufemia/components/number-format/NumberUtils'
// By using returnAria you get an object
const { number, aria } = format(12345678.9, {
locale: 'nb-NO', // not inherited
currency: true,
returnAria: true,
})
// Basic formatting
const number = format(1234)

The format method will accept the same properties as the component.

Interceptor helpers

Also, you may check out the related tests NumberFormat > cleanNumber in the source code to find more examples.

import { cleanNumber } from '@dnb/eufemia/components/number-format/NumberUtils'
const string = cleanNumber('prefix -12 345,678 suffix') // returns -12345.678
const string = cleanNumber('prefix -12.345,678 suffix') // returns -12345.678

Element and style

The number component is style-independent, so it has no visual styles. By default, a <span> is used (with speak-as: numbers, even the support is very low). But you can easily change the element type by providing something else to element="div" property.

Accessibility

To enhance the Copy & Paste experience of copying numbers into other applications, the NumberFormat component automatically changes the number formatting to be without thousand separators and to have a dot, instead of a comma for the decimal separator.

NVDA has also issues on reconciling the lang attribute, this makes it hard to have a solid and good solution for reading numbers. VoiceOver on desktop makes a perfect job there.

VoiceOver on mobile devices (iOS) only supports numbers read out properly to a maximum of 99,999.00. On amounts above this value, VO reads numbers digit by digit.

More details

Screen readers require numbers to be formatted properly in order to be read as numbers. The NumberFormat component will help to achieve this requirement.

So, numbers are formatted differently for screen readers than the visual number. And numbers also get assigned a lang attribute, so the screen reader knows what language (locale) should be used on the particular number, even if the text around does not correspond to the same language.

Sources

Eufemia is basing their number formats on both the Norwegian authority and Språkradet. Wikipedia has more info on world wide decimal separator usage.

For international number formatting, we use the EU Style Guide.

Node.js and SSR usage

If you run the component or format function in Node.js you have to include ICU data in order to display other locales than en-GB. You can do this by:

  • installing npm i full-icu
  • and call node (or jest) with an environment variable pointing to the package: NODE_ICU_DATA=./node_modules/full-icu node ...
  • after a Node.js version upgrade you may have to run npm rebuild

Known issues

Edge Browser on Windows 10 is converting numbers automatically to followable links. This makes the experience on NVDA bad, as it reads also the new, unformatted link number.

You can disable this behavior:

<html x-ms-format-detection="none">
...
</html>

Lighthouse and axe-core

In order to enhance the UX while using a screen reader, the NumberFormat component is using a role called role="text". This allows the screen reader to read particular numbers within the context, without interrupting the flow while reading paragraphs.

Lighthouse is using axe-core under the hood. Older versions of axe-core is allowing us to use role="text" only within no focusable descendants. But because the NumberFormat component also includes a Copy & Paste feature, it uses tabindex="-1" which allows JavaScript to focus the descendant DOM Element.

Demos

Default numbers

Total: 12 345-12 345 678,9prefix -12 345 678,9 suffix-1 234,5-1 234,00

<P>
<NumberFormat value="12345" srLabel="Total:" />
<NumberFormat>-12345678.9</NumberFormat>
<NumberFormat prefix={<b>prefix</b>} suffix="suffix">-12345678.9</NumberFormat>
<NumberFormat decimals={1}>-1234.54321</NumberFormat>
<NumberFormat decimals={2}>-1234</NumberFormat>
</P>

Currency

12 345,00 norske kroner-12 345 678,90 norske kroner-12 345 679 norske kroner-12 345 678,90 norske kroner

<P>
<NumberFormat currency>12345</NumberFormat>
<NumberFormat currency currency_position="before" value={-12345678.9} />
<NumberFormat currency value={-12345678.95} decimals={0} />
<NumberFormat
currency
value={-12345678.9}
currency_display="code"
/>
</P>

Compact (shorten) numbers

Shorten numbers should only be used for numbers above 100 000. A small k for thausand is not a Norwegian standard, and should not be used in formal contexts.

1,2 tusen123,5 tusen-1,72 millioner-1,235 millioner12,3 tusen norske kroner123,456 milliarder

<P>
<NumberFormat compact decimals={1}>1234</NumberFormat>
<NumberFormat
compact
decimals={1}
value={123456}
/>
<NumberFormat
compact="short"
decimals={2}
value={-1723967.38}
/>
<NumberFormat
compact="long"
decimals={3}
value={-1234567.9876}
/>
<NumberFormat
compact="long"
currency
value={12345}
decimals={1}
currency_display="name"
/>
<NumberFormat
compact
value={123455678912}
decimals={3}
/>
</P>

Percentage

12,34 %−12,34 %−12,3 %

<P>
<NumberFormat percent value="12.34" />
<NumberFormat percent>-12.34</NumberFormat>
<NumberFormat percent decimals={1}>-12.34</NumberFormat>
</P>

Phone

By using selectall={false} you disable the auto-select all feature.

99 99 99 990047 99 99 99 990047 99 99 99 990047 800 22 2220047 11 60 000047 02 00 0

<P>
<NumberFormat value="99999999" phone />
<NumberFormat value="4799999999" phone />
<NumberFormat value="++4799999999" phone />
<NumberFormat value="+4780022222" phone link="sms" />
<NumberFormat value="+47116000" phone selectall="false" />
<NumberFormat value="+4702000" phone />
</P>

Bank Account number (Kontonummer)

20 00 12 34 56 7

<P>
<NumberFormat value="20001234567" ban />
</P>

National Identification number (Fødselsnummer)

18 08 92 1 2 3 4 5

<P>
<NumberFormat value="18089212345" nin />
</P>

Organization number (Organisasjonsnummer)

1 2 3 4 5 6 7 8 9 MVA

<P>
<NumberFormat value="123456789" org suffix="MVA" />
</P>

Numbers and currencies in different locales

Numbers

-12 345 678,9-12 345 678.9-12.345.678,9-12’345’678.9-12 345 678,9

Currencies

-12 345,60 norske kroner-12 345.60 Norwegian kroner-12.345,60 Norwegische Kronen-12’345.60 Norwegische Kronen-12 345.60 couronnes norvégiennes

<H3>Numbers</H3>
<P>
<NumberFormat locale="nb-NO" value="-12345678.9" />
<NumberFormat locale="en-GB" value="-12345678.9" />
<NumberFormat locale="de-DE" value="-12345678.9" />
<NumberFormat locale="de-CH" value="-12345678.9" />
<NumberFormat locale="fr-CH" value="-12345678.9" />
</P>
<H3>Currencies</H3>
<P>
<NumberFormat locale="nb-NO" value="-12345.6" currency />
<NumberFormat locale="en-GB" value="-12345.6" currency />
<NumberFormat locale="de-DE" value="-12345.6" currency />
<NumberFormat locale="de-CH" value="-12345.6" currency />
<NumberFormat locale="fr-CH" value="-12345.6" currency />
</P>

NumberFormat and spacing

The NumberFormat uses display: inline-block; in order to make the spacing system to work.

text 1 234,00 norske kronertext 5 678,00 norske kroner text
text <NumberFormat value="1234" currency left right />
text <NumberFormat value="5678" currency left right /> text