UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

674 lines (606 loc) 14.9 kB
--- title: 'ProgressIndicator' description: 'The ProgressIndicator component is a waiting loader / spinner to show while other content is in progression.' version: 10.104.0 generatedAt: 2026-04-17T18:46:09.948Z checksum: 93b6f37f7b6c15ec345d58c93a83c4f52596c3df794509054b90a3e9c6a778ca --- # ProgressIndicator ## Import ```tsx import { ProgressIndicator } from '@dnb/eufemia' ``` ## Description Use a ProgressIndicator whenever the user has to wait for more than _150ms_. This component is also known as: - Indicator (Activity-Indicator) - Loader (Pre-loader) - Spinner ## Relevant links - [Figma](https://www.figma.com/design/cdtwQD8IJ7pTeE45U148r1/%F0%9F%92%BB-Eufemia---Web?node-id=21616-18893) - [Source code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-eufemia/src/components/progress-indicator) - [Docs code](https://github.com/dnbexperience/eufemia/tree/main/packages/dnb-design-system-portal/src/docs/uilib/components/progress-indicator) ## Demos ### Default ProgressIndicator is Circular ```tsx render(<ProgressIndicator />) ``` ### Default Circular ProgressIndicator ```tsx render(<ProgressIndicator type="circular" />) ``` ### Circular ProgressIndicator with a label in a horizontal direction ```tsx render( <ProgressIndicator // label="Custom label ..." type="circular" showDefaultLabel={true} labelDirection="horizontal" /> ) ``` ### Circular ProgressIndicator with a label in a vertical direction ```tsx render( <ProgressIndicator // label="Custom label ..." type="circular" showDefaultLabel={true} labelDirection="vertical" /> ) ``` ### Circular ProgressIndicator with a label inside Inside labels must be carefully sized, and are generally meant for just an icon or a number. ```tsx <ProgressIndicator right label={<IconPrimary icon="save" />} type="circular" labelDirection="inside" /> <ProgressIndicator progress={72} size="large" type="circular" labelDirection="inside" data-visual-test="progress-indicator-label-inside" > <span className="dnb-p dnb-t__weight--bold dnb-t__size--small"> {72}% </span> </ProgressIndicator> ``` ### Shows a large Circular ProgressIndicator with a static 50% in progress ```tsx render( <ProgressIndicator type="circular" progress="50" size="large" noAnimation /> ) ``` ### Circular ProgressIndicator with random value ```tsx const ChangeValue = () => { const [value, setValue] = React.useState(50) return ( <Flex.Horizontal align="center"> <ProgressIndicator type="circular" progress={value} showDefaultLabel noAnimation /> <Button left size="small" variant="secondary" onClick={() => setValue(Math.random() * 100)} > Change </Button> </Flex.Horizontal> ) } render(<ChangeValue />) ``` ### Circular ProgressIndicator with random progress value to show the transition ```tsx const Example = () => { const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min const [progress, setProgressIndicator] = React.useState(random(1, 100)) React.useEffect(() => { const timer = setInterval( () => setProgressIndicator(random(1, 100)), 1e3 ) return () => clearInterval(timer) }) return ( <ProgressIndicator type="circular" size="large" progress={progress} /> ) } render(<Example />) ``` ### Circular ProgressIndicator with random `on_complete` callback ```tsx const Example = () => { const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min const [visible, setVisible] = React.useState(true) React.useEffect(() => { const timer = setInterval( () => setVisible(!visible), random(2400, 4200) ) return () => clearTimeout(timer) }) return ( <ProgressIndicator type="circular" size="large" visible={visible} onComplete={() => { console.log('on_complete_circular') }} /> ) } render(<Example />) ``` ### Circular ProgressIndicator inside a Dialog ```tsx render( <Dialog spacing={false} maxWidth="12rem" fullscreen={false} alignContent="centered" hideCloseButton triggerAttributes={{ text: 'Show', }} preventClose={false} > <ProgressIndicator type="circular" showDefaultLabel labelDirection="vertical" top="large" bottom="large" size="large" /> </Dialog> ) ``` ### Default Linear ProgressIndicator ```tsx render(<ProgressIndicator type="linear" />) ``` ### Small Linear ProgressIndicator ```tsx render(<ProgressIndicator type="linear" size="small" />) ``` ### Linear ProgressIndicator with a label in a horizontal direction ```tsx render( <ProgressIndicator type="linear" // label="Custom label ..." showDefaultLabel={true} labelDirection="horizontal" /> ) ``` ### Linear ProgressIndicator with a label in a vertical direction ```tsx render( <ProgressIndicator type="linear" // label="Custom label ..." showDefaultLabel={true} labelDirection="vertical" /> ) ``` ### Shows a large Linear ProgressIndicator with a static 50% in progress ```tsx render( <ProgressIndicator type="linear" progress="50" size="large" noAnimation /> ) ``` ### Linear ProgressIndicator with random value ```tsx const ChangeValue = () => { const [value, setValue] = React.useState(50) return ( <FormRow centered> <ProgressIndicator type="linear" progress={value} noAnimation /> <Button left size="small" variant="secondary" onClick={() => setValue(Math.random() * 100)} > Change </Button> </FormRow> ) } render(<ChangeValue />) ``` ### Linear ProgressIndicator with random progress value to show the transition ```tsx const Example = () => { const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min const [progress, setProgressIndicator] = React.useState(random(1, 100)) React.useEffect(() => { const timer = setInterval( () => setProgressIndicator(random(1, 100)), 1e3 ) return () => clearInterval(timer) }) return <ProgressIndicator type="linear" progress={progress} /> } render(<Example />) ``` ### Linear ProgressIndicator with random `on_complete` callback ```tsx const Example = () => { const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min const [visible, setVisible] = React.useState(true) React.useEffect(() => { const timer = setInterval( () => setVisible(!visible), random(2400, 4200) ) return () => clearTimeout(timer) }) return ( <ProgressIndicator type="linear" size="large" visible={visible} onComplete={() => { console.log('on_complete_linear') }} /> ) } render(<Example />) ``` ### Linear ProgressIndicator inside a Dialog ```tsx render( <Dialog spacing={false} maxWidth="12rem" fullscreen={false} alignContent="centered" hideCloseButton triggerAttributes={{ text: 'Show', }} preventClose={false} > <ProgressIndicator type="linear" showDefaultLabel labelDirection="vertical" top="large" bottom="large" /> </Dialog> ) ``` ### Countdown indicator ```tsx const ChangeValue = () => { const max = 60 const [current, setCurrent] = React.useState(10) React.useEffect(() => { const timer = setInterval(() => { setCurrent(current === 0 ? max - 1 : current - 1) }, 1000) return () => clearTimeout(timer) }) return ( <ProgressIndicator type="countdown" progress={(current / max) * 100} title={`${current} av ${max}`} size="large" labelDirection="inside" > <MyCustomLabel aria-hidden>{current}</MyCustomLabel> </ProgressIndicator> ) } render(<ChangeValue />) ``` ### Style customization The sizes and colors can be customized with the properties `size`, `customColors`, and `customCircleWidth` if needed. The types `circular` and `countdown` has a few more options than `linear`. ```tsx const MyProgressIndicator = () => { const StyledText = styled.span` color: var(--color-white); font-size: var(--font-size-small); ` const StyledTitle = styled.span` display: block; font-weight: var(--font-weight-medium); font-size: var(--font-size-medium); ` const daysLeft = 20 const daysInMonth = 31 return ( <DarkBackground> <ProgressIndicator type="countdown" progress={(daysLeft / daysInMonth) * 100} size="6rem" labelDirection="inside" customColors={{ line: 'var(--color-summer-green)', shaft: 'transparent', background: 'var(--color-sea-green)', }} title={daysLeft + 'days left'} customCircleWidth="0.5rem" > <StyledText> <StyledTitle>{daysLeft} d</StyledTitle> left </StyledText> </ProgressIndicator> </DarkBackground> ) } render(<MyProgressIndicator />) ``` ```tsx const MyProgressIndicator = () => { const StyledText = styled.span` color: white; font-size: var(--font-size-basis); ` return ( <DarkBackground> <ProgressIndicator type="linear" progress={75} size="1rem" labelDirection="vertical" customColors={{ line: 'var(--color-summer-green)', shaft: 'var(--color-sea-green)', }} > <StyledText> <NumberFormat percent value={75} /> done </StyledText> </ProgressIndicator> </DarkBackground> ) } render(<MyProgressIndicator />) ``` ```tsx <ProgressIndicator type="linear" progress={32} customColors={{ line: 'red', shaft: 'green', }} size="4rem" /> <ProgressIndicator type="circular" progress={32} customColors={{ line: 'red', shaft: 'green', background: 'blue', }} size="4rem" /> ``` ## Properties ```json { "props": { "progress": { "doc": "A number between 0-100, if not supplied a continuous loading-type animation will be used.", "type": ["string", "number"], "defaultValue": "undefined", "status": "optional" }, "visible": { "doc": "Defines the visibility of the progress. Toggling the `visible` property to `false` will force a fade-out animation.", "type": "boolean", "defaultValue": "true", "status": "optional" }, "type": { "doc": "Defines the type.", "type": ["'circular'", "'linear'", "'countdown'"], "defaultValue": "'circular'", "status": "optional" }, "noAnimation": { "doc": "Disables the fade-in and fade-out animation.", "type": "boolean", "defaultValue": "false", "status": "optional" }, "size": { "doc": "Defines the size.", "type": [ "'default'", "'small'", "'medium'", "'large'", "'huge'", "string" ], "defaultValue": "'default'", "status": "optional" }, "label": { "doc": "Content of a custom label. (Overrides `indicator_label` and `showDefaultLabel`)", "type": "React.ReactNode", "defaultValue": "undefined", "status": "optional" }, "children": { "doc": "Same as `label` prop (`label` prop has priority)", "type": "React.ReactNode", "defaultValue": "undefined", "status": "optional" }, "labelDirection": { "doc": "Sets the position of the label. `'inside'` only works with `type='circular'.", "type": ["'horizontal'", "'vertical'", "'inside'"], "defaultValue": "'horizontal'", "status": "optional" }, "showDefaultLabel": { "doc": "If set to `true` a default label (from text locales) will be shown.", "type": "boolean", "defaultValue": "false", "status": "optional" }, "indicator_label": { "doc": "Use this to override the default label from text locales.", "type": "string", "defaultValue": "undefined", "status": "optional" }, "title": { "doc": "Used to set title and aria-label. Defaults to the value of progress property, formatted as a percent.", "type": "string", "defaultValue": "undefined", "status": "optional" }, "[customColors](/uilib/components/progress-indicator/properties/#data-object-customcolors)": { "doc": "Send in custom css colors that overrides any css. See below for data structure.", "type": "object", "defaultValue": "undefined", "status": "optional" }, "customCircleWidth": { "doc": "Send in custom css width for circle progress line. (`undefined` defaults to one eighth of the size).", "type": "string", "defaultValue": "undefined", "status": "optional" }, "[Space](/uilib/layout/space/properties)": { "doc": "Spacing properties like `top` or `bottom` are supported.", "type": ["string", "object"], "status": "optional" } }, "showDefaultValue": true } ``` ## Translations ```json { "locales": ["da-DK", "en-GB", "nb-NO", "sv-SE"], "entries": { "ProgressIndicator.indicator_label": { "nb-NO": "Vennligst vent ...", "en-GB": "Please wait ...", "sv-SE": "Vänligen vänta ...", "da-DK": "Vent venligst ..." } } } ``` ### Data object `customColors` ```json { "props": { "line": { "doc": "Override the moving line color.", "type": "string", "defaultValue": "undefined", "status": "optional" }, "shaft": { "doc": "Override the background line color.", "type": "string", "defaultValue": "undefined", "status": "optional" }, "background": { "doc": "Set a background color for the center of the circle.", "type": "string", "defaultValue": "undefined", "status": "optional" } }, "showDefaultValue": true } ``` ## Deprecated properties ```json { "props": { "no_animation": { "doc": "Use `noAnimation`.", "type": " boolean", "status": "deprecated" }, "label_direction": { "doc": "Use `labelDirection`.", "type": "string", "status": "deprecated" }, "show_label": { "doc": "Use `showDefaultLabel`.", "type": "boolean", "status": "deprecated" } } } ``` ## Events ```json { "props": { "onComplete": { "doc": "Will be called once it's no longer `visible`.", "type": "function", "defaultValue": "undefined", "status": "optional" } }, "showDefaultValue": true } ``` ## Deprecated events ```json { "props": { "on_complete": { "doc": "Use `onComplete`.", "type": "function", "status": "deprecated" } } } ```