One of the biggest strengths of the SharePoint Framework is theme inheritance.
Your web part automatically respects:
Fluent UI design tokens
tenant branding
site theme colors
dark mode
Microsoft Teams theme
Customizing ProgressStepsIndicator Colors in SPFx Without Affecting the Entire Site
One of the biggest strengths of the SharePoint Framework is theme inheritance.
Your web part automatically respects:
- tenant branding
- site theme colors
- dark mode
- Microsoft Teams theme
- Fluent UI design tokens
This creates consistency.
But sometimes consistency is not enough.
Sometimes business rules demand visual emphasis.
For example:
- current approval step must be green
- failed step must be red
- completed process must be blue
- blocked stage must be orange
This raises an important question:
Can I customize the ProgressStepsIndicator color without changing the entire site theme?
The answer is:
Yes.
And understanding how is very important.
This article explains:
- how
themeVariantworks - how local theme overrides work
- why cloning themes is safe
- what is global vs local theming
- how to make
ProgressStepsIndicatorgreen without affecting SharePoint
This is a very important concept for scalable SPFx architecture.
The default behavior
The ProgressStepsIndicator from PnP SPFx React Controls uses:
themeVariant.palette.themePrimary
This means:
if your tenant theme is blue:
the active step will be blue
If your tenant theme is purple:
the active step will be purple
This happens automatically.
Example:
<ProgressStepsIndicator steps={progressSteps} currentStep={2} themeVariant={themeVariant}/>
This inherits the current site theme.
Official docs:
PnP ProgressStepsIndicator Documentation
The business problem
Imagine this workflow:
DraftReviewApprovedPublished
Your business wants:
Current step = green
Not because of theme.
Because green means:
healthyapprovedactivegood
This is semantic color.
Different from branding color.
That distinction matters.
The wrong approach (global theme override)
Many developers think about doing this:
import { loadTheme } from '@fluentui/react';loadTheme({ palette: { themePrimary: '#107c10' }});
This works.
But it is dangerous.
Why?
Because it modifies:
global Fluent UI theme
That means:
- buttons may change
- links may change
- dialogs may change
- all Fluent UI components may change
Potentially:
whole web partor more
This is not ideal.
Fluent UI documentation:
The correct approach: local theme clone
Instead of changing the global theme:
Clone the current theme.
Modify only the clone.
Pass it only to the control.
Architecture:
Original Site Theme ↓Clone Theme ↓Override themePrimary ↓Pass to ProgressStepsIndicator only
This is safe.
Step 1 — Clone the theme
Inside your React component:
const customTheme = themeVariant ? { ...themeVariant, palette: { ...themeVariant.palette, themePrimary: '#107c10' } } : undefined;
What this does:
copies everythingchanges only themePrimarykeeps all other theme tokens
This is critical.
Step 2 — Pass the custom theme
<ProgressStepsIndicator steps={progressSteps} currentStep={2} themeVariant={customTheme}/>
Done.
Current step becomes green.
Full example
import * as React from 'react';import { ProgressStepsIndicator } from '@pnp/spfx-controls-react/lib/ProgressStepsIndicator';import { IProgressStepsIndicatorWpProps } from './IProgressStepsIndicatorWpProps';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}) => { const customTheme = themeVariant ? { ...themeVariant, palette: { ...themeVariant.palette, themePrimary: '#107c10' } } : undefined; return ( <ProgressStepsIndicator steps={progressSteps} currentStep={2} themeVariant={customTheme} /> );};export default ProgressStepsIndicatorWp;
What changes?
Only this control.
This is the important part.
Effects:
✔ active step becomes green
✔ completed step can inherit green behavior
✔ site theme stays unchanged
✔ other web parts stay unchanged
✔ page stays unchanged
✔ tenant theme stays unchanged
This is local override.
What stays inherited?
Everything else:
✔ dark mode support
✔ semantic colors
✔ typography
✔ spacing
✔ accessibility
✔ responsive behavior
Because you cloned the theme.
You did not replace it.
This is the beauty of the approach.
Why this is architecturally better
Because enterprise SPFx solutions need:
global consistencywith local flexibility
This pattern gives exactly that.
Example:
Site theme:
Blue
Control override:
Green current step
Everything coexists.
No conflict.
Alternative: CSS override
You could also do:
:global(.pnp-progressStepsIndicator-stepActive) { color: #107c10 !important;}
This works.
But:
Problems:
- fragile
- class names may change
- less aligned with Fluent UI tokens
- less reusable
Prefer theme override.
Best practices
Always:
✔ clone themeVariant
✔ override only what you need
✔ keep original theme structure
✔ prefer local overrides over global
Avoid:
✘ loadTheme() for small changes
✘ replacing the entire theme object
✘ hardcoded CSS if theme APIs exist
Final thoughts
This pattern is one of the most important theming concepts in SPFx.
It teaches an enterprise mindset:
do not change the systemchange the component
That mindset scales.
Especially when building:
- dashboards
- workflows
- approval systems
- operational portals
- process visualizations
Exactly where ProgressStepsIndicator shines.
