How to Fetch Secondary Theme Colors in SPFx with supportsThemeVariants: true
Fetching Secondary Theme Colors in SharePoint SPFx with supportsThemeVariants: true
When building modern SharePoint Framework (SPFx) web parts, supporting theme variants is essential for compatibility with dark mode and section backgrounds.
However, there is an important limitation developers often face:
When
supportsThemeVariantsis set totrue,tryGetTheme()does not return thesecondaryColorspalette.
But if you disable it:
"supportsThemeVariants": falsethen tryGetTheme() can fetch the secondary colors correctly.
Unfortunately, disabling theme variants causes another major issue:
SharePoint injects root background styling
The web part becomes incompatible with dark themes
Section background adaptation breaks
Theme-aware rendering no longer works correctly
So the challenge becomes:
How do we keep supportsThemeVariants: true AND still fetch secondary theme colors?
The solution is to directly read the SharePoint theme XML file (theme.spcolor) from the themed CSS folder.
Why tryGetTheme() Fails with Theme Variants Enabled
Normally in SPFx, developers use:
this.context.serviceScope.consume(ThemeProvider.serviceKey);or:
tryGetTheme();to access current theme information.
This works fine for primary palette values such as:
themePrimary
themeSecondary
neutralLight
neutralLighter
But once theme variants are enabled:
"supportsThemeVariants": truethe returned theme object no longer includes the secondaryColors collection.
This is because SharePoint applies variant-aware theme processing internally and trims certain palette metadata from the runtime theme object.
The Better Approach
Instead of relying on tryGetTheme(), we can fetch the actual SharePoint theme definition file:
theme.spcolorThis XML file contains:
Primary theme colors
Neutral colors
Accent colors
Secondary color palettes
Light/Dark theme variations
And it works perfectly even when theme variants are enabled.
SPFx Solution
Step 1 — Fetch theme.spcolor
We first locate the themed CSS folder using:
this.context.pageContext.legacyPageContext.themedCssFolderUrlThen request the XML file.
const legacyCtx = this.context.pageContext.legacyPageContext;
const themedCssFolderUrl = legacyCtx.themedCssFolderUrl;
const response = await this.context.spHttpClient.get(
`${themedCssFolderUrl}/theme.spcolor`,
SPHttpClient.configurations.v1,
{ headers: { Accept: "text/xml, application/xml" } },
);Step 2 — Parse the XML
We use DOMParser to convert the XML response into a readable document.
const xmlText = await response.text();
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, "application/xml");Step 3 — Extract Colors
The helper function below reads colors from the XML palette.
const getColor: (name: string) => string | undefined = (name) => {
const nodes = xmlDoc.querySelectorAll("colorPalette > color");
for (const node of Array.from(nodes)) {
if (node.getAttribute("name") === name) {
const raw = node.getAttribute("value") ?? "";
return raw.length >= 8 ? `#${raw.slice(2)}` : `#${raw}`;
}
}
return undefined;
};This automatically converts SharePoint ARGB values into standard HEX colors.
Example:
FF0078D4 → #0078D4Reading Secondary Colors
The important part is extracting:
<secondaryColors>from the XML.
const secondaryPaletteNodes = xmlDoc.querySelectorAll(
"secondaryColors > light > colorPalette",
);Then we transform the palette into usable JavaScript objects.
const palettesFromXml = Array.from(secondaryPaletteNodes).map((palette) => {
const entry: Record<string, string> = {};
palette.querySelectorAll("color").forEach((c) => {
const n = c.getAttribute("name") ?? "";
const v = c.getAttribute("value") ?? "";
entry[n] = v.length >= 8 ? `#${v.slice(2)}` : `#${v}`;
});
return entry;
});Creating a Fallback Palette
Not every SharePoint theme contains secondary palettes.
To avoid runtime issues, we generate fallback combinations using primary theme colors.
const fallback = [
[white, getColor("themePrimary")],
[white, getColor("accent")],
[getColor("themePrimary"), white],
[white, getColor("themeSecondary")],
]This guarantees that the UI still has usable color pairs even if secondary colors are missing.
Final Result
const secondaryColors = (
palettesFromXml.length > 0 ? palettesFromXml : fallback
).map((e) => ({
...e,
backgroundColor: e.backgroundColor ?? white,
}));Now your SPFx web part can:
Fully support dark mode
Respect SharePoint section themes
Use theme variants properly
Access secondary palettes
Avoid disabling
supportsThemeVariants
Complete Working Method
public async getSiteBrandingColors(): Promise<void> {
const legacyCtx = this.context.pageContext.legacyPageContext;
const themedCssFolderUrl = legacyCtx.themedCssFolderUrl;
const response = await this.context.spHttpClient.get(
`${themedCssFolderUrl}/theme.spcolor`,
SPHttpClient.configurations.v1,
{ headers: { Accept: "text/xml, application/xml" } },
);
if (!response.ok) {
console.error("Failed to fetch theme.spcolor:", response.status);
return;
}
const xmlText = await response.text();
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, "application/xml");
const getColor: (name: string) => string | undefined = (name) => {
const nodes = xmlDoc.querySelectorAll("colorPalette > color");
for (const node of Array.from(nodes)) {
if (node.getAttribute("name") === name) {
const raw = node.getAttribute("value") ?? "";
return raw.length >= 8 ? `#${raw.slice(2)}` : `#${raw}`;
}
}
return undefined;
};
const secondaryPaletteNodes = xmlDoc.querySelectorAll(
"secondaryColors > light > colorPalette",
);
const palettesFromXml = Array.from(secondaryPaletteNodes).map((palette) => {
const entry: Record<string, string> = {};
palette.querySelectorAll("color").forEach((c) => {
const n = c.getAttribute("name") ?? "";
const v = c.getAttribute("value") ?? "";
entry[n] = v.length >= 8 ? `#${v.slice(2)}` : `#${v}`;
});
return entry;
});
const white = "#ffffff";
const fallback = [
[white, getColor("themePrimary")],
[white, getColor("accent")],
[getColor("themePrimary"), white],
[white, getColor("themeSecondary")],
[getColor("themePrimary"), getColor("neutralLighter") ?? "#f3f2f1"],
[white, getColor("themeTertiary")],
[white, getColor("themeLight")],
[white, getColor("HeaderBackground")],
]
.filter((pair): pair is [string, string] => !!(pair[0] && pair[1]))
.map(([themePrimary, backgroundColor]) => ({
themePrimary,
backgroundColor,
}));
const secondaryColors = (
palettesFromXml.length > 0 ? palettesFromXml : fallback
).map((e) => ({
...e,
backgroundColor: e.backgroundColor ?? white,
}));
console.log("secondaryColors:", secondaryColors);
}
Conclusion
If your SPFx solution needs:
Theme-aware rendering
Dark theme compatibility
SharePoint section support
Secondary theme palettes
then avoid disabling:
"supportsThemeVariants": trueInstead, fetch and parse theme.spcolor directly.
This approach gives you the best of both worlds:
✅ Full theme variant support
✅ Access to SharePoint secondary color palettes
This technique is especially useful for:
Branding web parts
Theme pickers
Dynamic UI generators
Design systems
Multi-theme SPFx solutions
Tenant branding utilities