@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
706 lines (611 loc) • 20.7 kB
Markdown
---
title: 'Stat'
description: 'Composable metric components for highlighted values, trends, and labels.'
version: 10.104.0
generatedAt: 2026-04-17T18:46:11.945Z
checksum: 08ee69ee2cf524d4d12e50da5b28dab1961268780073012f1217e2ffecd712fb
---
# Stat
## Import
```tsx
import { Stat } from '@dnb/eufemia'
```
## Description
`Stat` contains components for prominent values with a label, where typography and visual emphasis are part of the component.
## Available components
- `Stat.Root` renders a definition list (`dl`).
- `Stat.Label` renders descriptive text with dedicated typography and color for metric context (`dt`).
- `Stat.Content` renders the main value as a definition description (`dd`).
- `Stat.Number` is the base value formatter built on the [NumberFormat](/uilib/components/number-format/) formatting logic.
- `Stat.Currency` and `Stat.Percent` are convenience wrappers around `Stat.Number`.
- It adds typography-specific properties such as `fontSize`, `fontWeight` and `colorizeBySign`, along with `mainSize` and `auxiliarySize` as well as `mainWeight` and `auxiliaryWeight` that can be used to customize the visual emphasis of the different parts of the value (currency symbol or percent sign).
- `Stat.Trend` renders explicit `+` / `-` indicators with red/green background states and screen-reader text.
- `Stat.Rating` renders a star rating (defaults to 5 stars) and colorizes stars based on `value`. The `max` prop is clamped to `20` to prevent excessive DOM output; a console warning is emitted when the limit is exceeded.
- `Stat.Info` renders supporting text with a smaller, muted style.
- `Stat.Inline` is a horizontal layout container for grouping content elements like `Stat.Trend` and `Stat.Info` side by side with consistent spacing and alignment.
- `Stat.Text` renders custom content and supports properties such as `fontSize`, `fontWeight`, and `colorizeBySign`.
### Deprecated
- `Stat.Amount` is deprecated and will be removed in a future version. Use `Stat.Number` instead.
## Accessibility
- `Stat.Root` provides semantic definition-list markup (`dl`), where `Stat.Label` is rendered as `dt` and `Stat.Content` as `dd`.
- If the label also acts as a section heading, use a heading element inside `Stat.Label` (for example `H3`) to preserve a meaningful heading outline.
- Use `srLabel` to prepend context in the screen-reader text only, for example turning `1,234 kr` into `Revenue 1,234 kr` for screen readers.
- When e.g. `signDisplay="always"` is used, the sign is rendered as a separate visual element with CSS spacing, while the accessible text stays based on the formatted number string.
- All Stat variants keep dedicated accessibility handling. `Currency`, `Percent`, and `Trend` use a dedicated screen-reader value (`.dnb-sr-only`) based on the formatted content. `Rating` uses an accessible label (`role="img"` + `aria-label`) that includes value and max.
## Relevant links
- [Source code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-eufemia/src/components/stat)
- [Docs code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-design-system-portal/src/docs/uilib/components/stat)
## Demos
### Basic usage
```tsx
render(
<Stat.Root>
<Stat.Label>Revenue growth</Stat.Label>
<Stat.Content direction="vertical">
<Stat.Currency value={1234} signDisplay="always" />
<Stat.Trend srLabel="Change">+12.4%</Stat.Trend>
<Stat.Info>Some additional information.</Stat.Info>
</Stat.Content>
</Stat.Root>
)
```
### Root and Label
If the label acts as a section heading, place a heading element inside `Stat.Label` (for example `H3`).
```tsx
render(
<Stat.Root>
<Stat.Label>
<H3>Revenue growth</H3>
</Stat.Label>
<Stat.Content direction="vertical">
<Stat.Currency
value={1234}
mainSize="x-large"
auxiliarySize="x-small"
/>
<Stat.Trend srLabel="Growth trend">+12.4%</Stat.Trend>
</Stat.Content>
<Stat.Label top>Monthly change</Stat.Label>
<Stat.Content direction="vertical">
<Stat.Currency
value={-1234}
signDisplay="always"
mainSize="x-large"
auxiliarySize="x-small"
/>
<Stat.Inline>
<Stat.Trend srLabel="Change trend">-2.1%</Stat.Trend>
<Stat.Info>(some additional information)</Stat.Info>
</Stat.Inline>
</Stat.Content>
</Stat.Root>
)
```
#### Hidden Label
Use a visually hidden label (`srOnly`) when the visible UI context already describes the statistic.
```tsx
render(
<Stat.Root>
<Stat.Label srOnly>I'm a hidden label</Stat.Label>
<Stat.Content>
<Stat.Currency value={1234} />
</Stat.Content>
</Stat.Root>
)
```
### Currency
You can use `mainSize` and `auxiliarySize` to adjust the relative size of the currency symbol and the amount.
```tsx
render(
<Stat.Root>
<Stat.Label>Always show sign</Stat.Label>
<Stat.Content>
<Stat.Currency
value={1234}
mainSize="x-large"
signDisplay="always"
auxiliarySize="x-small"
/>
</Stat.Content>
<Stat.Label top>With suffix</Stat.Label>
<Stat.Content>
<Stat.Currency
value={1234}
currency="USD"
suffix="/mnd"
mainSize="x-large"
auxiliarySize="x-small"
/>
</Stat.Content>
<Stat.Label top>
Colorized using <Code>en-GB</Code> locale
</Stat.Label>
<Stat.Content>
<Stat.Currency
value={-1234.5}
decimals={2}
currency="USD"
signDisplay="always"
fontSize="medium"
colorizeBySign
locale="en-GB"
/>
</Stat.Content>
</Stat.Root>
)
```
### Currency within a Trend
```tsx
render(
<Stat.Root>
<Stat.Label>
<DateFormat value="P1Y" />
</Stat.Label>
<Stat.Content direction="vertical">
<Stat.Currency value={350234} srLabel="Annual revenue" />
<Stat.Inline>
<Stat.Trend>
<Stat.Currency
value={46692}
signDisplay="always"
srLabel="Revenue delta"
/>
</Stat.Trend>
<Stat.Info>
(
<Stat.Percent
value={16.79}
decimals={2}
srLabel="Relative change"
/>)
</Stat.Info>
</Stat.Inline>
</Stat.Content>
</Stat.Root>
)
```
### Number
```tsx
render(
<Stat.Root>
<Stat.Label>Number</Stat.Label>
<Stat.Content>
<Stat.Number
value={1234}
signDisplay="always"
mainSize="x-large"
auxiliarySize="x-small"
/>
</Stat.Content>
<Stat.Label top>Number in Trend and Info</Stat.Label>
<Stat.Content>
<Stat.Trend tone="negative" srLabel="Negative trend">
<Stat.Number value={-1234} signDisplay="always" />
</Stat.Trend>
<Stat.Info>
(
<Stat.Number value={1234} srLabel="Signed amount with currency" />)
</Stat.Info>
</Stat.Content>
</Stat.Root>
)
```
### Percent
```tsx
render(
<Stat.Root>
<Stat.Label>Percentage</Stat.Label>
<Stat.Content>
<Stat.Percent
value={12.3}
mainSize="x-large"
auxiliarySize="x-small"
/>
</Stat.Content>
<Stat.Label top>Percentage colorized</Stat.Label>
<Stat.Content>
<Stat.Percent
value={0.1234}
decimals={2}
signDisplay="always"
fontSize="medium"
colorizeBySign
/>
</Stat.Content>
</Stat.Root>
)
```
### Percent colorized by sign
```tsx
render(
<Stat.Root>
<Stat.Label>Positive without signDisplay</Stat.Label>
<Stat.Content>
<Stat.Percent value={12.3} fontSize="medium" colorizeBySign />
</Stat.Content>
<Stat.Label top>Negative without signDisplay</Stat.Label>
<Stat.Content>
<Stat.Percent value={-12.3} fontSize="medium" colorizeBySign />
</Stat.Content>
<Stat.Label top>Zero without signDisplay</Stat.Label>
<Stat.Content>
<Stat.Percent value={0} fontSize="medium" colorizeBySign />
</Stat.Content>
</Stat.Root>
)
```
### Rating
```tsx
render(
<Stat.Root>
<Stat.Label>Stars rating</Stat.Label>
<Stat.Content>
<Stat.Rating value={4} />
</Stat.Content>
<Stat.Label top>Progressive rating</Stat.Label>
<Stat.Content direction="vertical">
<Stat.Rating variant="progressive" value={5} />
</Stat.Content>
</Stat.Root>
)
```
### Text
```tsx
render(
<Stat.Root>
<Stat.Label>Label</Stat.Label>
<Stat.Content>
<Stat.Text colorizeBySign={-123}>Custom content</Stat.Text>
</Stat.Content>
<Stat.Label top>With medium font weight and size</Stat.Label>
<Stat.Content>
<Stat.Text
srLabel="Screen reader label"
colorizeBySign={123}
fontWeight="medium"
fontSize="medium"
>
Larger and bolder
</Stat.Text>
</Stat.Content>
</Stat.Root>
)
```
### With Subtle Label
Also, the order of the content and label can be switched for visual users (not screen readers), and the label is styled with the `subtle` variant to further de-emphasize it.
```tsx
function Example() {
const { rating } = useTranslation().Stat
return (
<Grid.Container
rowGap
columnGap
style={{
gridAutoRows: '1fr',
}}
>
<Grid.Item
span={{
small: [1, 12],
medium: [1, 12],
large: [1, 3],
}}
>
<Card
style={{
height: '100%',
}}
>
<Stat.Root visualOrder="content-label">
<Stat.Label variant="subtle">
<DateFormat value="P1Y" />
</Stat.Label>
<Stat.Content direction="vertical">
<IconPrimary icon="arrow_up" top="x-small" />
<Stat.Percent
top="small"
value={5.21}
decimals={2}
fontSize="basis"
srLabel="Revenue growth percentage"
/>
</Stat.Content>
</Stat.Root>
</Card>
</Grid.Item>
<Grid.Item
span={{
small: [1, 12],
medium: [1, 12],
large: [4, 6],
}}
>
<Card
style={{
height: '100%',
}}
>
<Stat.Root visualOrder="content-label">
<Stat.Label variant="subtle">Yearly cost</Stat.Label>
<Stat.Content direction="vertical">
<Icon icon={globe_medium} />
<Stat.Percent
top="small"
value={0.6}
decimals={1}
fontSize="basis"
/>
</Stat.Content>
</Stat.Root>
</Card>
</Grid.Item>
<Grid.Item
span={{
small: [1, 12],
medium: [1, 12],
large: [7, 9],
}}
>
<Card
style={{
height: '100%',
}}
>
<Stat.Root visualOrder="content-label">
<Stat.Label variant="subtle">Risiko</Stat.Label>
<Stat.Content direction="vertical">
<Stat.Rating variant="progressive" value={2} />
<Stat.Info top variant="prominent">
Lav
</Stat.Info>
</Stat.Content>
</Stat.Root>
</Card>
</Grid.Item>
<Grid.Item
span={{
small: [1, 12],
medium: [1, 12],
large: [10, 12],
}}
>
<Card
style={{
height: '100%',
}}
>
<Stat.Root visualOrder="content-label">
<Stat.Label variant="subtle">Stars rating</Stat.Label>
<Stat.Content direction="vertical">
<Stat.Rating value={2} />
<Stat.Info top variant="prominent">
{rating.replace('%value', '2').replace('%max', '5')}
</Stat.Info>
</Stat.Content>
</Stat.Root>
</Card>
</Grid.Item>
</Grid.Container>
)
}
render(<Example />)
```
### With AriaLive
Use the [AriaLive](/uilib/components/aria-live/) component to announce dynamic value updates to screen readers. Wrap `Stat.Root` with `AriaLive` so that changes are announced when the content updates.
```tsx
function Example() {
const [value, setValue] = React.useState(1234)
return (
<Flex.Stack>
<AriaLive variant="content">
<Stat.Root>
<Stat.Label>Revenue</Stat.Label>
<Stat.Content>
<Stat.Currency value={value} />
</Stat.Content>
</Stat.Root>
</AriaLive>
<Button
text="Update value"
variant="secondary"
on_click={() => setValue((prev) => prev + 100)}
/>
</Flex.Stack>
)
}
render(<Example />)
```
## Stat.Currency
<PropertiesTable props={CurrencyProperties} />
## Stat.Percent
<PropertiesTable props={PercentProperties} />
## Stat.Number
```json
{
"props": {
"value": {
"doc": "A number.",
"type": "number",
"status": "required"
},
"currency": {
"doc": "Currency code (ISO 4217) or `true` to use the default `NOK`. Uses two decimals by default.",
"type": ["string", "boolean"],
"status": "optional"
},
"currencyDisplay": {
"doc": "Use either empty/false to hide the sign/name or use `code` (NOK), `name` (kroner), `symbol` (kr) or `narrowSymbol` (for a shorter symbol variant). Defaults to `narrowSymbol` when the locale is `no` else we default to `code`.",
"type": "string",
"status": "optional"
},
"currencyPosition": {
"doc": "Use either `before` or `after` to change/define the position of the currency. Defaults to `auto` (Browser API defaults, but with an exception, if the locale is `nb-NO` or `no`, use after as the default position).",
"type": "string",
"status": "optional"
},
"decimals": {
"doc": "Set a number to define the number of decimals. Like `decimals=\"0\"` will ensure that decimals are simply not shown. The default decimals for currency usage are `2` (Browser API default).",
"type": "number",
"status": "optional"
},
"rounding": {
"doc": "If `omit` is given, the decimal will NOT be rounded. If set to `half-even`, the value will be rounded to the nearest even number. If set to `half-up`, the fractional part is 0.5 or greater, the number is rounded up. If the fractional part is less than 0.5, the number is rounded down. Defaults to `half-up`.",
"type": ["omit", "half-even", "half-up"],
"status": "optional"
},
"signDisplay": {
"doc": "When to display the sign for the number. Use `auto` (default) for negative numbers only, `always` to always display sign, `exceptZero` for positive and negative numbers but not zero, `negative` for negative numbers only including negative zero, or `never` to never display sign.",
"type": ["auto", "always", "exceptZero", "negative", "never"],
"status": "optional"
},
"compact": {
"doc": "Shortens any number or currency including an abbreviation. You can combine `compact` with `currency`. It gives you zero decimal by default `decimals={0}`. Use either `short` or `long`. Defaults to `short` if `true` is given.",
"type": ["boolean", "string"],
"status": "optional"
},
"prefix": {
"doc": "Add a string or React component before the number, including white space.",
"type": "React.Node",
"status": "optional"
},
"suffix": {
"doc": "Appends a string or React component after the number, including white space. When the suffix is a string starting with `/`, no space is added (e.g. `suffix=\"/mnd\"` renders \"123/mnd\").",
"type": "React.ReactNode",
"status": "optional"
},
"locale": {
"doc": "Use a [2 Letter Language Code](https://www.sitepoint.com/iso-2-letter-language-codes/) or an extended code such as `nb-NO`. Use `auto` to detect the locale from the browser (`navigator.language`). Defaults to the Norwegian locale: `nb-NO`.",
"type": "string",
"status": "optional"
},
"srLabel": {
"doc": "Will add a visually hidden label, to give screen reader users the missing context to easier understand what the number represents.",
"type": "string",
"status": "optional"
},
"skeleton": {
"doc": "If set to `true`, an overlaying skeleton with animation will be shown.",
"type": "boolean",
"status": "optional"
},
"options": {
"doc": "Accepts all [number.toLocaleString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString) or [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) options as an object - can also be a JSON given as the parameter e.g. `options={{ 'minimumFractionDigits': 2 }}`.",
"type": "object",
"status": "optional"
},
"fontSize": {
"doc": "Typography size fallback used for both main and auxiliary content. `mainSize` and `auxiliarySize` override this value. If omitted, default is `large` (`basis` when nested inside `Stat.Trend` or `Stat.Info`, unless any size prop is set).",
"type": [
"\"x-small\"",
"\"small\"",
"\"basis\"",
"\"medium\"",
"\"large\"",
"\"x-large\"",
"\"xx-large\""
],
"status": "optional"
},
"mainSize": {
"doc": "Typography size for the main content. When omitted, it falls back to `fontSize` if provided.",
"type": [
"\"x-small\"",
"\"small\"",
"\"basis\"",
"\"medium\"",
"\"large\"",
"\"x-large\"",
"\"xx-large\""
],
"defaultValue": "large (`basis` when nested inside `Stat.Trend` or `Stat.Info`, unless `fontSize`, `mainSize`, or `auxiliarySize` is set)",
"status": "optional"
},
"mainWeight": {
"doc": "Typography weight for the main content.",
"type": ["\"regular\"", "\"medium\""],
"defaultValue": "medium",
"status": "optional"
},
"auxiliaryWeight": {
"doc": "Typography weight for secondary content like currency sign and affixes. If omitted, and `mainSize` equals `auxiliarySize` while `mainWeight` is omitted, `medium` is used.",
"type": ["\"regular\"", "\"medium\""],
"status": "optional"
},
"auxiliarySize": {
"doc": "Typography size for secondary content like currency sign and affixes (`prefix` and `suffix`). When omitted, it falls back to `fontSize` if provided.",
"type": [
"\"x-small\"",
"\"small\"",
"\"basis\"",
"\"medium\"",
"\"large\"",
"\"x-large\"",
"\"xx-large\""
],
"defaultValue": "large (`basis` when nested inside `Stat.Trend` or `Stat.Info`, unless `fontSize`, `mainSize`, or `auxiliarySize` is set)",
"status": "optional"
},
"colorizeBySign": {
"doc": "If `true`, text color follows sign tone (`+` green, `-` red).",
"type": ["boolean"],
"defaultValue": "false",
"status": "optional"
},
"[Space](/uilib/layout/space/properties)": {
"doc": "Spacing properties like `top` or `bottom` are supported.",
"type": ["string", "object"],
"status": "optional"
}
}
}
```
## Stat.Trend
<PropertiesTable props={TrendProperties} />
## Stat.Rating
<PropertiesTable props={RatingProperties} />
## Stat.Info
<PropertiesTable props={InfoProperties} />
## Stat.Root
<PropertiesTable props={RootProperties} />
## Stat.Label
<PropertiesTable props={LabelProperties} />
## Stat.Content
<PropertiesTable props={ContentProperties} />
## Stat.Inline
```json
{
"props": {
"children": {
"doc": "Inline layout container for content elements, typically `Stat.Trend` and `Stat.Info`.",
"type": ["React.ReactNode"],
"status": "optional"
},
"skeleton": {
"doc": "Applies skeleton state to the inline container.",
"type": "boolean",
"status": "optional"
},
"[Flex.Horizontal](/uilib/layout/flex/horizontal/properties)": {
"doc": "Supports all additional `Flex.Horizontal` properties.",
"type": "Various",
"status": "optional"
}
}
}
```
## Stat.Text
<PropertiesTable props={TextProperties} />
## Translations
```json
{
"locales": ["da-DK", "en-GB", "nb-NO", "sv-SE"],
"entries": {
"Stat.rating": {
"nb-NO": "%value av %max",
"en-GB": "%value of %max",
"sv-SE": "%value av %max",
"da-DK": "%value af %max"
}
}
}
```