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 themeVariant works
  • how local theme overrides work
  • why cloning themes is safe
  • what is global vs local theming
  • how to make ProgressStepsIndicator green 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:

Draft
Review
Approved
Published

Your business wants:

Current step = green

Not because of theme.

Because green means:

healthy
approved
active
good

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 part
or more

This is not ideal.

Fluent UI documentation:

Fluent UI Theme System


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 everything
changes only themePrimary
keeps 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 consistency
with 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 system
change the component

That mindset scales.

Especially when building:

  • dashboards
  • workflows
  • approval systems
  • operational portals
  • process visualizations

Exactly where ProgressStepsIndicator shines.

Edvaldo Guimrães Filho Avatar

Published by