Stepper
Keep the user informed on their progress by dividing content into logical steps.
- 1First Step
- 2Second Step
- 3Third Step
- 4Last Step
import * as React from 'react';
import { Button, Stepper } from '@itwin/itwinui-react';
const steps = [
{ name: 'First Step' },
{ name: 'Second Step' },
{ name: 'Third Step' },
{ name: 'Last Step' },
];
export default () => {
const [currentStep, setCurrentStep] = React.useState(2);
return (
<div className='demo-container'>
<div className='demo-stepper'>
<Stepper
currentStep={currentStep}
steps={steps}
onStepClick={(index) => {
setCurrentStep(index);
}}
/>
</div>
<div className='demo-button-container'>
<Button
disabled={currentStep === 0}
onClick={() => {
if (currentStep !== 0) setCurrentStep(currentStep - 1);
}}
>
Previous
</Button>
<Button
styleType='cta'
disabled={currentStep === steps.length - 1}
onClick={() => {
if (currentStep < steps.length - 1) setCurrentStep(currentStep + 1);
}}
>
Next
</Button>
</div>
</div>
);
};
In defined, multi-step user interactions, it is useful to inform the user of the number of steps involved in the process, and the current step within that sequence. A UI ‘Stepper’ guides the user through the steps and provides some interaction to return to previous steps. A standard example of a Stepper would be an e-Commerce checkout process, but there are many others where the user must perform sequential steps.
Variants
There are two different styles of stepper, depending on the amount of steps required to complete the process.
Short Stepper
In the default “short” stepper, each step is named under a circle.
- 1Previous Step
- 2Current Step
- 3Next Step
import * as React from 'react';
import { Button, Stepper } from '@itwin/itwinui-react';
const steps = [
{ name: 'Previous Step' },
{ name: 'Current Step' },
{ name: 'Next Step' },
];
export default () => {
const [currentStep, setCurrentStep] = React.useState(1);
return (
<div className='demo-container'>
<div className='demo-stepper'>
<Stepper
currentStep={currentStep}
steps={steps}
onStepClick={(index) => {
setCurrentStep(index);
}}
type='default'
/>
</div>
<div className='demo-button-container'>
<Button
disabled={currentStep === 0}
onClick={() => {
if (currentStep !== 0) setCurrentStep(currentStep - 1);
}}
>
Previous
</Button>
<Button
styleType='cta'
disabled={currentStep === steps.length - 1}
onClick={() => {
if (currentStep < steps.length - 1) setCurrentStep(currentStep + 1);
}}
>
Next
</Button>
</div>
</div>
);
};
Designers should confirm the display is acceptable due to the number of steps and labels at various resolutions. If text wrapping occurs or the stepper’s visual quality decreases significantly when the window is re-sized, use the long stepper instead.
Long Stepper
In the long stepper, there are no labels underneath the step indicators to save space. Instead, the label for the step currently in progress appears under the diagram along with ‘Step X of X’. This allocates more space for longer step names, as well as allowing more step indicators to be contained within the diagram.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
import * as React from 'react';
import { Button, Stepper } from '@itwin/itwinui-react';
const steps = [
{ name: 'First Step' },
{ name: 'Second Step' },
{ name: 'Third Step' },
{ name: 'Fourth Step' },
{ name: 'Fifth Step' },
{ name: 'Sixth Step' },
{ name: 'Last Step' },
];
export default () => {
const [currentStep, setCurrentStep] = React.useState(2);
return (
<div className='demo-container'>
<div className='demo-stepper'>
<Stepper
currentStep={currentStep}
steps={steps}
onStepClick={(index) => {
setCurrentStep(index);
}}
type='long'
/>
</div>
<div className='demo-button-container'>
<Button
disabled={currentStep === 0}
onClick={() => {
if (currentStep !== 0) setCurrentStep(currentStep - 1);
}}
>
Previous
</Button>
<Button
styleType='cta'
disabled={currentStep === steps.length - 1}
onClick={() => {
if (currentStep < steps.length - 1) setCurrentStep(currentStep + 1);
}}
>
Next
</Button>
</div>
</div>
);
};
Usage
Short vs Long Stepper
-
Use a short stepper if a process has less than five steps and if the label for each step can be summarized with 1-2 words.
-
Use a long stepper if a process has five steps or more or if the label for each step are long (20 characters or over) and/or cannot be summarized
-
A long stepper may be used for a process corresponding to the short stepper’s criteria if desired. However, using a short stepper for a process that would require a long stepper is not recommended.
Tooltip
Additional (optional) information can be displayed within a tooltip when hovering over a step indicator.
- 1Completed step
- 2Current step
- 3Next step
- 4Last step
import * as React from 'react';
import { Button, Stepper } from '@itwin/itwinui-react';
const steps = [
{ name: 'Completed step', description: 'Completed tooltip' },
{ name: 'Current step', description: 'Current tooltip' },
{ name: 'Next step', description: 'Next tooltip' },
{ name: 'Last step', description: 'Last tooltip' },
];
export default () => {
const [currentStep, setCurrentStep] = React.useState(1);
return (
<div className='demo-container'>
<div className='demo-stepper'>
<Stepper
currentStep={currentStep}
steps={steps}
onStepClick={(index) => {
setCurrentStep(index);
}}
/>
</div>
<div className='demo-button-container'>
<Button
disabled={currentStep === 0}
onClick={() => {
if (currentStep !== 0) setCurrentStep(currentStep - 1);
}}
>
Previous
</Button>
<Button
styleType='cta'
disabled={currentStep === steps.length - 1}
onClick={() => {
if (currentStep < steps.length - 1) setCurrentStep(currentStep + 1);
}}
>
Next
</Button>
</div>
</div>
);
};
Localization
The ‘Step X of X’ label that appears in the default long stepper can be replaced with a localized string.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
import * as React from 'react';
import { Button, Stepper } from '@itwin/itwinui-react';
const steps = [
{ name: 'First Step' },
{ name: 'Second Step' },
{ name: 'Third Step' },
{ name: 'Fourth Step' },
{ name: 'Fifth Step' },
{ name: 'Sixth Step' },
{ name: 'Last Step' },
];
const localization = {
stepsCountLabel: (currentStep, totalSteps) =>
`Localized step ${currentStep} of ${totalSteps}:`,
};
export default () => {
const [currentStep, setCurrentStep] = React.useState(2);
return (
<div className='demo-container'>
<div className='demo-stepper'>
<Stepper
currentStep={currentStep}
steps={steps}
onStepClick={(index) => {
setCurrentStep(index);
}}
type='long'
localization={localization}
/>
</div>
<div className='demo-button-container'>
<Button
disabled={currentStep === 0}
onClick={() => {
if (currentStep !== 0) setCurrentStep(currentStep - 1);
}}
>
Previous
</Button>
<Button
styleType='cta'
disabled={currentStep === steps.length - 1}
onClick={() => {
if (currentStep < steps.length - 1) setCurrentStep(currentStep + 1);
}}
>
Next
</Button>
</div>
</div>
);
};
Layout
We recommend the following layout:
-
Title: Displayed above the stepper so the user acknowledges what the process is about.
-
Stepper
-
Content
-
Buttons: A call-to-action button for progress & one or two default buttons for canceling and/or going back one step. If the user has not completed a required task, the call-to-action button may be disabled. If the stepper is in a full screen page, the buttons can be placed within a sticky footer.
Color survey
- 1User Info
- 2Color Selection
- 3Explanation
import * as React from 'react';
import {
Button,
Input,
Label,
Stepper,
InputGroup,
InputGrid,
Radio,
} from '@itwin/itwinui-react';
const stepLabels = [
{ name: 'User Info' },
{ name: 'Color Selection' },
{ name: 'Explanation' },
];
export default () => {
const [currentStep, setCurrentStep] = React.useState(0);
const [disableProgress, setDisableProgress] = React.useState(true);
React.useEffect(() => {
setDisableProgress(true);
}, [currentStep]);
const stepOne = (
<InputGrid>
<Label required>Name</Label>
<Input
key='name'
placeholder='Enter name'
onChange={({ target: { value } }) => {
setDisableProgress(!value);
}}
/>
<Label>Occupation</Label>
<Input key='occupation' placeholder='Enter occupation' />
</InputGrid>
);
const stepTwo = (
<InputGroup
key='color'
label='Choose your favorite color'
required
onChange={({ target: { value } }) => {
setDisableProgress(!value);
}}
>
<Radio name='color' value='Red' label='Red' />
<Radio name='color' value='Orange' label='Orange' />
<Radio name='color' value='Yellow' label='Yellow' />
<Radio name='color' value='Green' label='Green' />
<Radio name='color' value='Blue' label='Blue' />
<Radio name='color' value='Purple' label='Purple' />
</InputGroup>
);
const stepThree = (
<InputGrid>
<Label required>Why is this your favorite color</Label>
<Input
key='explanation'
placeholder='Enter text here...'
onChange={({ target: { value } }) => {
setDisableProgress(!value);
}}
/>
</InputGrid>
);
const steps = [stepOne, stepTwo, stepThree];
return (
<>
<div className='demo-container'>
<h2 className='demo-header'>Color survey</h2>
<div className='demo-stepper'>
<Stepper
currentStep={currentStep}
steps={stepLabels}
onStepClick={(index) => {
setDisableProgress(true);
setCurrentStep(index);
}}
/>
</div>
<div className='demo-current-step'>{steps[currentStep]}</div>
<div className='demo-button-container'>
<Button
disabled={currentStep === 0}
onClick={() => {
if (currentStep !== 0) {
setDisableProgress(true);
setCurrentStep(currentStep - 1);
}
}}
>
Back
</Button>
<Button
styleType='cta'
disabled={disableProgress}
onClick={() => {
if (currentStep < steps.length - 1) {
setDisableProgress(true);
setCurrentStep(currentStep + 1);
}
}}
>
{currentStep === 2 ? 'Register' : 'Next'}
</Button>
</div>
</div>
</>
);
};
Props
Prop | Description | Default |
---|---|---|
currentStep | Current step index, 0 - based. number | |
steps | An array of step objects. StepProperties[] | |
type | The type of Stepper to display. "default" | "long" | 'default' |
localization | Option to provide localized strings. StepperLocalization | |
onStepClick | Click handler on completed step. (clickedIndex: number) => void | |
stepProps | Callback that can provide additional props for <li> representing a step.(index: number) => DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement> | |
trackContentProps | Allows props to be passed for track content. (index: number) => DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> | |
circleProps | Allows props to be passed for circle. (index: number) => DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | |
nameProps | Allows props to be passed for name. (index: number) => DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | |
labelProps | Allows props to be passed for label. DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> | |
labelCountProps | Allows props to be passed for label count. DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | |
as | "symbol" | "object" | "div" | "a" | "abbr" | "address" | "area" | "article" | "aside" | "audio" | "b" | "base" | "bdi" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | ... 158 more ... | FunctionComponent<...> |