Import
import { ProgressIndicator } from '@dnb/eufemia'
Description
The ProgressIndicator component shows a visual indicator during loading or processing states. Use it whenever the user has to wait for more than 150ms.
It supports three types: circular (default), linear, and countdown. Each type can display either a determinate state (with a known progress value) or an indeterminate state (when the duration is unknown).
This component is also known as: Indicator (Activity-Indicator), Loader (Pre-loader), Spinner.
Determinate vs indeterminate
- Determinate: Use when the progress percentage is known (e.g. file uploads, multi-step processes). Set the
progressprop to a value between0and100. - Indeterminate: Use when the duration is unknown (e.g. fetching data, waiting for a response). Omit the
progressprop to show a continuous animation.
Types
circular(default): A spinning ring. Works well inline or centered in a container. Supports label placement inside, horizontally, or vertically.linear: A horizontal bar. Suited for wider layouts or when vertical space is limited. Common at the top of a page or section.countdown: A circular variant that animates counterclockwise, useful for timers or session expiry indicators.
Visibility
Use the show prop to control when the indicator appears and disappears. When show transitions to false, the indicator animates out and fires the onComplete callback once the exit animation finishes.
Accessibility
- The component uses
role="progressbar"witharia-valuenowfor determinate states, giving screen readers the current progress. - For indeterminate states,
role="alert"is used to announce that loading is in progress. - Use the
titleprop to provide a descriptive accessible label (e.g."Loading account details"). - The
showDefaultLabelprop adds a visible "In progress..." label, which also helps screen reader users understand the context.
When to use
- Processing or loading states lasting more than 150ms.
- File uploads, data fetching, or form submissions where the user needs feedback.
- Timers or countdowns using the
countdowntype.
When not to use
- For full-page or section-level loading states, consider the Skeleton component instead.
- For processes lasting less than 150ms — no loading indicator is needed.
Relevant links
Demos
Indeterminate (unknown progress)
When the duration is unknown, omit the progress prop to show a continuous animation. The default type is circular.
<ProgressIndicator />
The linear type works well for wider layouts, such as the top of a page or section.
<ProgressIndicator type="linear" />
Determinate (known progress)
Set the progress prop (0–100) when you know how far along the process is. This gives the user a clear expectation of remaining time.
<ProgressIndicator type="circular" progress="50" size="large" noAnimation />
<ProgressIndicator type="linear" progress="50" size="large" noAnimation />
Labels
Labels help users understand what is loading. Use showDefaultLabel for the built-in "In progress..." text, or provide a custom label.
Horizontal label
<ProgressIndicator // label="Custom label ..." type="circular" showDefaultLabel={true} labelDirection="horizontal" />
<ProgressIndicator type="linear" // label="Custom label ..." showDefaultLabel={true} labelDirection="horizontal" />
Vertical label
The default label direction is vertical.
<ProgressIndicator // label="Custom label ..." type="circular" showDefaultLabel={true} />
<ProgressIndicator type="linear" showDefaultLabel={true} />
Inside label (circular only)
Inside labels are placed in the center of the circle. Use sparingly — they work best for short content like an icon or a number.
<ProgressIndicator right label={<IconPrimary icon="save" />} type="circular" labelDirection="inside" /> <ProgressIndicator progress={72} size="large" type="circular" labelDirection="inside" label={ <span className="dnb-p dnb-t__weight--bold dnb-t__size--small"> {72}% </span> } />
Animated progress transitions
When the progress value changes, the indicator animates smoothly between values.
const Example = () => { const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min const [progress, setProgressIndicator] = useState(random(1, 100)) useEffect(() => { const timer = setInterval( () => setProgressIndicator(random(1, 100)), 1e3 ) return () => clearInterval(timer) }) return ( <ProgressIndicator type="circular" size="large" progress={progress} /> ) } render(<Example />)
const Example = () => { const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min const [progress, setProgressIndicator] = useState(random(1, 100)) useEffect(() => { const timer = setInterval( () => setProgressIndicator(random(1, 100)), 1e3 ) return () => clearInterval(timer) }) return <ProgressIndicator type="linear" progress={progress} /> } render(<Example />)
Controlling visibility with show
Use the show prop to toggle the indicator. The onComplete callback fires after the exit animation finishes, which is useful for sequencing UI updates.
const Example = () => { const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min const [show, setShow] = useState(true) useEffect(() => { const timer = setInterval(() => setShow(!show), random(2400, 4200)) return () => clearTimeout(timer) }) return ( <ProgressIndicator type="circular" size="large" show={show} onComplete={() => { console.log('onCompleteCircular') }} /> ) } render(<Example />)
Inside a Dialog
A ProgressIndicator can be placed inside a Dialog to block interaction while a process completes.
<Dialog spacing={false} maxWidth="12rem" fullscreen={false} alignContent="centered" hideCloseButton triggerAttributes={{ text: 'Show', }} preventClose={false} > <ProgressIndicator type="circular" showDefaultLabel top="large" bottom="large" size="large" /> </Dialog>
Countdown
The countdown type animates counterclockwise and is suited for timers, session expiry, or time-limited actions. Combine it with labelDirection="inside" to display a value in the center.
const ChangeValue = () => { const max = 60 const [current, setCurrent] = useState(10) 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" label={<MyCustomLabel aria-hidden>{current}</MyCustomLabel>} /> ) } 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 have a few more options than linear.