Skip to content

Description

The step indicator (progress indicator) is a visual representation of a user's progress through a set of steps or series of actions. Their purpose is to both guide the user through the process and to help them create a mental model of the amount of time and effort that is required to fulfill the process.

If the user should be able to navigate back and forth, use the mode="loose" property. More about the modes further down.

The current active step is set with the current_step property or within the data with the is_current object property.

NB: Ensure, when ever possible, to bind the current_step to the browsers path location. See the example above or the example on CodeSandbox.

Sidebar

The StepIndicator has two layouts. One for larger screens (above medium) and one for more narrow screens (below medium).

Depending on the screen width, the component will hide the Sidebar responsively, and in return show a button to access all the steps inside a Drawer. You may have a look at an demo.

Why a sidebar?

In order to provide a user experience, where the number of steps are almost unlimited, and at the same time, make these steps as accessible as possible, the solution was to provide the steps side-by-side the content, as long as there is enough available room for it to show properly.

How to use the sidebar?

The step indicator has an optional Sidebar "sister" component you can place in your layout if the steps should be shown side-by-side to the content. Use the property sidebar_id to bind these two together:

<main>
<StepIndicator sidebar_id="unique-id" mode="strict" data={[...]} />
</main>
<aside>
<StepIndicator.Sidebar sidebar_id="unique-id" />
</aside>

Sidebar and SSR

NB: Keep in mind, if you SSR render the Sidebar before the main component, it has no access to the data and will show a basic skeleton to ensure the sidebar gets its width. This helps to lower the impact of unwanted layout shifts.

If you are able to provide the data to the Sidebar as well, it will use it during SSR render. You also can provide the data with a wrapper Eufemia Provider (You can nest Eufemia Providers):

<Provider StepIndicator={{ data: [...] }}>
<main>
<StepIndicator sidebar_id="unique-id" mode="strict" />
</main>
<aside>
<StepIndicator.Sidebar sidebar_id="unique-id" />
</aside>
</Provider>

Modes

The mode property is mandatory. It tells the component how it should behave.

Strict mode

Use strict for a chronological step order.

The user can navigate between the visited steps and the current step. The component keeps track of these reached steps.

Loose mode

Use loose if the user should be able to navigate freely between all steps. Also, those which are not visited before.

Static mode

Use static for non-interactive steps.

Modify a step

You can easily modify a step – e.g. should one step not be interactive, you can use the inactive property on that step:

const steps = [
{ title: 'Active' },
{ title: 'Not active', inactive: true },
]

More details about modifying steps in the properties panel.

How wide should the sidebar be?

The sidebar comes with these styles:

.dnb-step-indicator__sidebar {
max-width: 20rem;
margin-right: var(--spacing-x-large);
}
.dnb-step-indicator__sidebar .dnb-step-indicator__item {
min-width: 320px;
}

API changes after version 9.8

The API has been simplified and some properties have to be changed by the next major version release. The changes are made backward compatible. But in order to use the new UX look, you have to make a few changes:

  • Add a property called mode="strict" (choose your mode)
  • Add a property called sidebar_id with an unique ID.
  • Optional, place <StepIndicator.Sidebar sidebar_id="unique-step-indicator" /> in your layout.

Demos

StepIndicator in loose mode

Every step can be clicked.

You want to place <StepIndicator.Sidebar sidebar_id="unique-id-loose" /> somewhere in your layout.

const InteractiveDemo = () => {
const [step, setStep] = React.useState(1)
return (
<div
style={{
display: 'flex',
}}
>
<StepIndicator.Sidebar sidebar_id="unique-id-loose" />
<Space stretch>
<StepIndicator
sidebar_id="unique-id-loose"
mode="loose"
current_step={step}
on_change={({ current_step }) => {
setStep(current_step)
}}
data={[
'Cum odio si bolig bla et ta',
'Auctor tortor vestibulum placerat bibendum sociis aliquam nunc sed venenatis massa eget duis',
'Bibendum sociis',
]}
bottom
/>
<Button
variant="secondary"
on_click={() => {
setStep((step) => {
if (step >= 2) {
step = -1
}
return step + 1
})
}}
>
Next step
</Button>
</Space>
</div>
)
}
render(<InteractiveDemo />)

StepIndicator in strict mode

Every visited step can be clicked, including the current step.

You want to place <StepIndicator.Sidebar sidebar_id="unique-id-strict" /> somewhere in your layout.

<StepIndicator.Sidebar sidebar_id="unique-id-strict" />
<StepIndicator
sidebar_id="unique-id-strict"
mode="strict"
current_step={1}
on_change={({ current_step }) => {
console.log('on_change', current_step)
}}
data={[
{
title: 'Velg mottaker',
},
{
title: 'Bestill eller erstatt',
on_click: ({ current_step }) =>
console.log('current_step:', current_step),
status:
'Du må velge bestill nytt kort eller erstatt kort for å kunne fullføre bestillingen din.',
},
{
title: 'Oppsummering',
},
]}
/>

StepIndicator in static mode

None of the steps are clickable.

You want to place <StepIndicator.Sidebar sidebar_id="unique-id-static" /> somewhere in your layout.

<StepIndicator
sidebar_id="unique-id-static"
mode="static"
current_step={1}
on_change={({ current_step }) => {
console.log('on_change', current_step)
}}
data={[
{
title: 'Om din nye bolig',
},
{
title: 'Ditt lån og egenkapital',
on_click: ({ current_step }) => console.log(current_step),
},
{
title: 'Oppsummering',
},
]}
/>

StepIndicator with sidebar

<StepIndicator
style={{ display: 'none' }}
sidebar_id="unique-id-sidebar"
mode="loose"
data={[
{
title: 'Om din nye bolig',
},
{
title: 'Ditt lån og egenkapital',
},
{
title: 'Oppsummering',
is_current: true,
},
]}
/>
<StepIndicator.Sidebar sidebar_id="unique-id-sidebar" top="large" />

StepIndicator with a router

const StepIndicatorWithRouter = () => {
const [currentStep, setCurrentStep] = React.useState(1)
const [history, setInstance] = React.useState(null)
React.useEffect(() => {
const history = createBrowserHistory()
const step =
parseFloat(history.location.search?.replace(/[?]/, '')) || 1
setCurrentStep(step)
setInstance(history)
}, [])
return (
<>
<StepIndicator
sidebar_id="step-indicator-router"
mode="loose"
current_step={currentStep - 1}
on_change={({ current_step }) => {
const step = current_step + 1
setCurrentStep(step)
history.push('?' + step)
}}
data={[
{
title: 'Om din nye bolig',
},
{
title: 'Ditt lån og egenkapital',
},
{
title: 'Oppsummering',
},
]}
space
/>
<StepIndicator.Sidebar sidebar_id="step-indicator-router" space />
</>
)
}
render(<StepIndicatorWithRouter />)

StepIndicator customized

Completely customized step indicator.

Step One
function CustomStepIndicator({ children, ...props }) {
const [step, setStep] = React.useState(0)
return (
<>
<StepIndicator
sidebar_id="unique-id-customized"
mode="loose"
current_step={step}
on_change={({ current_step }) => setStep(current_step)}
{...props}
/>
<Section style_type="lavender" spacing>
{children(step)}
</Section>
</>
)
}
render(
<CustomStepIndicator
data={[
{
title: 'First',
is_current: true,
},
{
title: 'Second',
},
{
title: 'Last',
},
]}
>
{(step) => {
switch (step) {
case 0:
return <>Step One</>
case 1:
return <>Step Two</>
default:
return <>Fallback</>
}
}}
</CustomStepIndicator>
)

StepIndicator with text only

<StepIndicator
sidebar_id="unique-id-text"
mode="static"
current_step="1"
data={['Om din nye bolig', 'Ditt lån og egenkapital', 'Oppsummering']}
/>

StepIndicator with a custom renderer.

<StepIndicator
sidebar_id="unique-id-renderer"
mode="strict"
current_step={1}
on_change={({ current_step }) => {
console.log('on_change', current_step)
}}
on_item_render={({ StepItem }) => {
return <StepItem onClick={(e) => console.log(e)} />
}}
data={[
{
title: 'Om din nye bolig',
},
{
title: 'Ditt lån og egenkapital',
on_click: ({ current_step }) => console.log(current_step),
on_render: ({ StepItem, props, params }) => (
<StepItem onClick={(e) => console.log(e)} />
),
},
{
title: 'Oppsummering',
/**
* We can also overwrite the states
* inactive: true
* is_current: true
*/
},
]}
/>