Skip to content

Import

import { NumberFormat } from '@dnb/eufemia'

Description

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

For a complete locale comparison, see Best Practices for number formatting.

Relevant links

Good reasons for why we have this:

  • To standardize the formatting of numbers for all DNB applications.
  • To make numbers accessible to screen readers.

Supported formats

  • Numbers in general e.g.
  • Currency e.g.
  • Percentage e.g.
  • Phone numbers e.g.
  • Bank account number e.g.
  • National identification number e.g.
  • Organization number e.g.
  • Compact (short) numbers e.g.
  • Compact (long) currency e.g.

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

Norwegian kroner

When the currency format is set to currencyDisplay="name", the currency will be displayed as "kroner" instead of "Norwegian kroner".

  • Norwegian currency:
  • Swedish currency:

Not available

When a number should be displayed but is not available to the frontend application, the NumberFormat component will display a single em dash (–), and a screen reader will receive the text "Ikke tilgjengelig" / "Not available".

Example:

Decimals

If the value has more decimal places than specified by the decimals={2} property, it will be rounded accordingly.

Here are the available options for the rounding property:

  • omit: Truncate decimals without rounding.
  • half-even: Round to the nearest even number.
  • half-up (default): Round up if the fractional part is 0.5 or greater; otherwise, round down.

Value Components

The formatting helpers power several Value.* components:

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={{ currencyDisplay: 'code' }}>
<MyApp>
text <NumberFormat.Number>123</NumberFormat.Number> table etc.
</MyApp>
</Provider>
)

Formatter utilities

Each variant has a matching formatter function you can use outside of React (e.g. in utilities, tests or server code). They are all available from the main import:

import {
formatNumber,
formatCurrency,
formatPercent,
formatPhoneNumber,
formatBankAccountNumber,
formatNationalIdentityNumber,
formatOrganizationNumber,
} from '@dnb/eufemia/components/NumberFormat'
FormatterComponent variant
formatNumberNumberFormat.Number
formatCurrencyNumberFormat.Currency
formatPercentNumberFormat.Percent
formatPhoneNumberNumberFormat.PhoneNumber
formatBankAccountNumberNumberFormat.BankAccountNumber
formatNationalIdentityNumberNumberFormat.NationalIdentityNumber
formatOrganizationNumberNumberFormat.OrganizationNumber

Each formatter accepts (value, options?) and returns a formatted string. Pass { returnAria: true } to get the full object with number, aria, cleanedValue and locale.

NumberFormat Hook

Heads up: If you do so, keep in mind that 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,
formatCurrency,
} from '@dnb/eufemia/components/NumberFormat'
function Component() {
// By using returnAria you get an object
const { number, aria } = useNumberFormat(12345678.9, formatCurrency, {
// 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>
)

NumberFormat Hook with parts

You can also use useNumberFormatWithParts when you need split output for custom layouts:

import Provider from '@dnb/eufemia/shared/Provider'
import {
useNumberFormatWithParts,
formatCurrency,
} from '@dnb/eufemia/components/NumberFormat'
function Component() {
// useNumberFormatWithParts defaults to returnAria=true
const { number, aria, parts } = useNumberFormatWithParts(
12345678.9,
formatCurrency
)
return (
<span>
<span aria-hidden>
{parts.sign}
{parts.number}
{parts.currency ? ` ${parts.currency}` : null}
</span>
<span className="dnb-sr-only">{aria}</span>
</span>
)
}
render(
<Provider locale="en-GB" NumberFormat={{ currency: 'EUR' }}>
<Component />
</Provider>
)

Related component

For prominent values with dedicated typography controls, use Stat.

Formatting only (interceptor)

You can use the variant formatter functions without using a React Component or React Hook.

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

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

Each variant formatter accepts the same options as the corresponding component variant.

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

Format bank account numbers

Use formatBankAccountNumberByType to format bank account numbers with a specific type. It supports Norwegian BBAN, Swedish BBAN, Swedish Bankgiro, Swedish Plusgiro, and IBAN.

import { formatBankAccountNumberByType } from '@dnb/eufemia/components/NumberFormat'
formatBankAccountNumberByType('20001234567') // { number: '2000 12 34567', aria: '20 00 12 34 56 7' }
formatBankAccountNumberByType('50001234567', 'swedishBban') // { number: '5000-1234567', aria: '50 00 12 34 56 7' }
formatBankAccountNumberByType('59140129', 'swedishBankgiro') // { number: '5914-0129', aria: '59 14 01 29' }
formatBankAccountNumberByType('1263664', 'swedishPlusgiro') // { number: '126366-4', aria: '12 63 66 4' }
formatBankAccountNumberByType('NO9386011117947', 'iban') // { number: 'NO93 8601 1117 947', aria: 'NO93 8601 1117 947' }

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 though the support is very low). However, you can easily change the element type by providing a different value to the element="div" property.

Accessibility

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

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.

To enhance the Copy & Paste experience of copying numbers into other applications (Excel), you may use the cleanCopyValue property. It will then provide a second number, without thousand separators and to have a comma/dot (depending on the locale) as the decimal separator. This number is not visible, but will be used when selecting & copying the whole number on the first click to the system clipboard.

You can enable this feature on all your NumberFormat components by using the Provider:

import { Provider } from '@dnb/eufemia/shared'
render(
<Provider value={{ NumberFormat: { cleanCopyValue: true } }}>
<YourApp />
</Provider>
)

More details

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

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

Sources

Eufemia bases its number formats on both the Norwegian authority and Språkradet, and currency is based on guidelines from Språkrådet. Wikipedia has more info on worldwide decimal separator usage.

For international number formatting we use these sources:

Difference between formats:

  • 1 234.00 – 🇬🇧 Current DNB practice for English
  • 1,234.00 – 🇬🇧 Recommended by official UK sources (ONS, GOV.UK, NHS)

Accessibility: WCAG 1.3.1 Info and Relationships and 3.1.1 Language of Page require that content can be correctly interpreted by assistive technologies.

When using spaces as thousand separators, screen readers misinterpret numbers in English. For example: 45 804 is read as "45" and "804" instead of "forty-five thousand eight hundred and four."

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>

Demos

Default numbers

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

Currency

<P>
  <NumberFormat.Currency>12345</NumberFormat.Currency>
  <NumberFormat.Currency currencyPosition="before" value={-12345678.9} />
  <NumberFormat.Currency value={-12345678.95} decimals={0} />
  <NumberFormat.Currency value={-12345678.9} currencyDisplay="code" />
  <NumberFormat.Currency value={-12345678.9} currencyDisplay={false} />
  <NumberFormat.Currency decimals={2}>invalid</NumberFormat.Currency>
</P>

Hero-style values

For prominent values, use Stat with Stat.Currency and Stat.Percent.

<Stat.Currency
  value={12345}
  currency="NOK"
  suffix="/mnd"
  signDisplay="always"
  mainSize="x-large"
  auxiliarySize="x-small"
/>

Compact (shorten) numbers

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

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

Percentage

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

Phone

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

<P>
  <NumberFormat.PhoneNumber value="99999999" />
  <NumberFormat.PhoneNumber value="+4799999999" />
  <NumberFormat.PhoneNumber value="004799999999" />
  <NumberFormat.PhoneNumber value="+4780022222" link="sms" />
  <NumberFormat.PhoneNumber value="+47116000" selectAll={false} />
  <NumberFormat.PhoneNumber value="+4702000" />
</P>

Bank Account number (Kontonummer)

<P>
  <NumberFormat.BankAccountNumber value="20001234567" />
</P>

National Identification number (Fødselsnummer)

<P>
  <NumberFormat.NationalIdentityNumber value="18089212345" />
</P>

Organization number (Organisasjonsnummer)

<P>
  <NumberFormat.OrganizationNumber value="123456789" suffix="MVA" />
</P>

Numbers and currencies in different locales

Numbers

Currencies

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

NumberFormat and spacing

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

texttexttext
<span>text</span>
<NumberFormat.Currency value="1234" left right />
<span>text</span>
<NumberFormat.Currency value="5678" left right />
<span>text</span>

Sign display

Control when to display the sign for numbers using the signDisplay property. Options include always, exceptZero, negative, and never.

signDisplay="auto"

signDisplay="always"

signDisplay="never"

signDisplay="negative"

signDisplay="exceptZero"

<H3>signDisplay="auto"</H3>
<P>
  <NumberFormat.Number signDisplay="auto" value={1234} />
  <NumberFormat.Number signDisplay="auto" value={-1234} />
  <NumberFormat.Number signDisplay="auto" value={0} />
</P>
<H3>signDisplay="always"</H3>
<P>
  <NumberFormat.Number signDisplay="always" value={1234} />
  <NumberFormat.Number signDisplay="always" value={-1234} />
  <NumberFormat.Number signDisplay="always" value={0} />
</P>
<H3>signDisplay="never"</H3>
<P>
  <NumberFormat.Number signDisplay="never" value={1234} />
  <NumberFormat.Number signDisplay="never" value={-1234} />
  <NumberFormat.Number signDisplay="never" value={0} />
</P>
<H3>signDisplay="negative"</H3>
<P>
  <NumberFormat.Number signDisplay="negative" value={1234} />
  <NumberFormat.Number signDisplay="negative" value={-1234} />
  <NumberFormat.Number signDisplay="negative" value={0} />
</P>
<H3>signDisplay="exceptZero"</H3>
<P>
  <NumberFormat.Number signDisplay="exceptZero" value={1234} />
  <NumberFormat.Number signDisplay="exceptZero" value={-1234} />
  <NumberFormat.Number signDisplay="exceptZero" value={0} />
</P>

Using the Provider with NumberFormat

In this example every NumberFormat will receive the Provider defined properties, including cleanCopyValue.

<Provider
  value={{
    NumberFormat: {
      currency: true,
      rounding: 'omit',
      cleanCopyValue: true,
    },
  }}
>
  <P>
    <NumberFormat.Currency>12345</NumberFormat.Currency>
    <NumberFormat.Currency value={-12345.123} decimals={0} />
    <NumberFormat.Currency
      value={-12345678.955}
      currencyPosition="before"
    />
  </P>
</Provider>

Monospace

By using the monospace property you can set the font to DNB Mono Regular

<NumberFormat.Currency
  value="123456"
  locale="en-GB"
  currency="NOK"
  monospace
/>