In enterprise environments, users rarely think in isolated actions.
They think in processes.
Every business operation usually follows a sequence:
a training process follows certification milestones
a document moves through approval stages
a ticket evolves through support statuses
an onboarding request passes multiple departments
a deployment pipeline moves through environments
Building Step-Based User Interfaces in SPFx with ProgressStepsIndicator
In enterprise environments, users rarely think in isolated actions.
They think in processes.
Every business operation usually follows a sequence:
- a document moves through approval stages
- a ticket evolves through support statuses
- an onboarding request passes multiple departments
- a deployment pipeline moves through environments
- a training process follows certification milestones
This means one thing:
state visualization matters
One of the biggest UX mistakes in corporate applications is showing process state as plain text.
Example:
Status: In Review
Technically correct.
But visually weak.
Users immediately understand much more when status is represented as a journey.
Example:
Draft → Review → Approved → Published
This creates context.
Context reduces confusion.
Confusion reduction improves productivity.
That is exactly where ProgressStepsIndicator becomes useful.
Instead of building custom progress bars, step components, or status pipelines manually, the PnP SPFx React Controls library gives us a reusable and production-ready control.
This control is especially powerful inside the SharePoint Framework ecosystem because it integrates naturally with:
- SharePoint Lists
- Power Automate
- Fluent UI
- Microsoft Teams
- tenant themes
- dark mode
This makes it ideal for building modern process-driven SharePoint solutions.
In this article we will explore:
- what the control does
- how it works
- how to integrate it into SPFx
- how to make it theme-aware
- how it connects to Fluent UI concepts
- enterprise use cases
- best practices
We will build a complete example.
What is ProgressStepsIndicator?
The ProgressStepsIndicator is a PnP reusable SPFx control that renders a multi-step visual process.
Example:
Planning → Development → Testing → Deployment
Instead of this:
Current Status: Testing
you get:
✓ Planning✓ Development➜ Testing○ Deployment
This makes workflow state much easier to understand.
Official documentation:
PnP ProgressStepsIndicator Documentation
Why use it?
Because enterprise users think in stages.
Examples:
Document approval
DraftReviewApprovedPublishedArchived
Employee onboarding
RegistrationHR ValidationIT ProvisioningTrainingCompleted
Incident management
OpenedAssignedIn ProgressResolvedClosed
SharePoint migration
DiscoveryPlanningMigrationValidationGo Live
Perfect for SharePoint.
Architecture of our solution
We will use:
SPFx Web Part ↓ThemeProvider ↓React Component ↓ProgressStepsIndicator
This follows the correct SPFx architecture.
Why ThemeProvider matters here
The control accepts:
themeVariant
This makes it inherit:
- SharePoint site theme
- tenant branding
- dark mode
- Teams theme
Without this:
visual inconsistency
With this:
native Microsoft 365 experience
Project Structure
src/├── webparts/│ ├── progressStepsIndicatorWp/│ │ ├── ProgressStepsIndicatorWpWebPart.ts│ │ ├── components/│ │ │ ├── IProgressStepsIndicatorWpProps.ts│ │ │ ├── ProgressStepsIndicatorWp.tsx
Simple.
Clean.
Scalable.
Step 1 — Create the props interface
File:
IProgressStepsIndicatorWpProps.ts
import { IReadonlyTheme } from '@microsoft/sp-component-base';export interface IProgressStepsIndicatorWpProps { themeVariant?: IReadonlyTheme;}
Why optional?
Because during initialization:
theme may not yet be loaded
This avoids runtime issues.
Step 2 — Web Part setup
File:
ProgressStepsIndicatorWpWebPart.ts
import * as React from 'react';import * as ReactDom from 'react-dom';import { Version } from '@microsoft/sp-core-library';import { IPropertyPaneConfiguration, PropertyPaneTextField} from '@microsoft/sp-property-pane';import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';import { ThemeProvider, ThemeChangedEventArgs, IReadonlyTheme} from '@microsoft/sp-component-base';import * as strings from 'ProgressStepsIndicatorWpWebPartStrings';import ProgressStepsIndicatorWp from './components/ProgressStepsIndicatorWp';import { IProgressStepsIndicatorWpProps } from './components/IProgressStepsIndicatorWpProps';export interface IProgressStepsIndicatorWpWebPartProps { description: string;}export default class ProgressStepsIndicatorWpWebPart extends BaseClientSideWebPart<IProgressStepsIndicatorWpWebPartProps> { private _themeProvider: ThemeProvider | undefined; private _themeVariant: IReadonlyTheme | undefined; public render(): void { const element: React.ReactElement<IProgressStepsIndicatorWpProps> = React.createElement( ProgressStepsIndicatorWp, { themeVariant: this._themeVariant } ); ReactDom.render(element, this.domElement); } protected onInit(): Promise<void> { this._themeProvider = this.context.serviceScope.consume( ThemeProvider.serviceKey ); this._themeVariant = this._themeProvider.tryGetTheme(); this._themeProvider.themeChangedEvent.add( this, this._handleThemeChangedEvent ); return Promise.resolve(); } private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void { this._themeVariant = args.theme; this.render(); } protected onDispose(): void { if (this._themeProvider) { this._themeProvider.themeChangedEvent.remove( this, this._handleThemeChangedEvent ); } ReactDom.unmountComponentAtNode(this.domElement); } protected get dataVersion(): Version { return Version.parse('1.0'); }}
Important concept: Why not this.context.themeProvider?
Wrong:
this.context.themeProvider.tryGetTheme()
This causes:
Property 'themeProvider' does not exist
Correct:
this.context.serviceScope.consume(ThemeProvider.serviceKey)
This is the SPFx dependency injection model.
Very important.
Step 3 — React component
File:
ProgressStepsIndicatorWp.tsx
import * as React from 'react';import { IProgressStepsIndicatorWpProps } from './IProgressStepsIndicatorWpProps';import { ProgressStepsIndicator } from '@pnp/spfx-controls-react/lib/ProgressStepsIndicator';const progressSteps = [ { title: 'Planning', description: 'Requirements and analysis' }, { title: 'Development', description: 'Building the solution' }, { title: 'Testing', description: 'Validation and QA' }, { title: 'Deployment', description: 'Production release' }];const ProgressStepsIndicatorWp: React.FC<IProgressStepsIndicatorWpProps> = ({ themeVariant}) => { return ( <ProgressStepsIndicator steps={progressSteps} currentStep={2} themeVariant={themeVariant} /> );};export default ProgressStepsIndicatorWp;
Understanding currentStep
This property controls current active stage.
Example:
currentStep={0}
Output:
➜ Planning○ Development○ Testing○ Deployment
currentStep={2}
Output:
✓ Planning✓ Development➜ Testing○ Deployment
Very useful for workflow states.
Relation to Fluent UI
PnP controls are built on top of Fluent UI.
Meaning:
this control inherits:
- Microsoft styling system
- accessibility standards
- responsive behavior
- keyboard navigation
- theme tokens
Fluent UI documentation:
Microsoft Fluent UI Documentation
This is why the control feels native.
SPFx relation
This control runs inside:
SharePoint Framework
SPFx provides:
- context
- lifecycle
- dependency injection
- theme services
- property pane
- Microsoft 365 host integration
Official SPFx docs:
Microsoft Learn – SharePoint Framework Overview
Final thoughts
ProgressStepsIndicator is not just a visual component.
It is a process communication component.
That distinction matters.
In enterprise systems, process clarity is often more important than raw data.
When users understand where they are in a process:
- they make fewer mistakes
- they require less training
- they complete tasks faster
- they trust the system more
That is the true value of good UX.
And this control solves that elegantly inside the Microsoft 365 ecosystem.
