Skip to content

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 progress prop to a value between 0 and 100.
  • Indeterminate: Use when the duration is unknown (e.g. fetching data, waiting for a response). Omit the progress prop 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" with aria-valuenow for determinate states, giving screen readers the current progress.
  • For indeterminate states, role="alert" is used to announce that loading is in progress.
  • Use the title prop to provide a descriptive accessible label (e.g. "Loading account details").
  • The showDefaultLabel prop 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 countdown type.

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

Vennligst vent ...
<ProgressIndicator
  // label="Custom label ..."
  type="circular"
  showDefaultLabel={true}
  labelDirection="horizontal"
/>
Vennligst vent ...
<ProgressIndicator
  type="linear"
  // label="Custom label ..."
  showDefaultLabel={true}
  labelDirection="horizontal"
/>

Vertical label

The default label direction is vertical.

Vennligst vent ...
<ProgressIndicator
  // label="Custom label ..."
  type="circular"
  showDefaultLabel={true}
/>
Vennligst vent ...
<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.

72%
<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.

20 dleft
done