UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

706 lines (611 loc) 20.7 kB
--- 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" } } } ```