A web part that ignores the site theme sticks out like a sore thumb — bright blue buttons on a dark-themed site, hard-coded colours that vanish on a coloured section background. Modern SharePoint pages let users apply themes and section backgrounds, and a well-built web part adapts to all of them. The tools are IReadonlyTheme for the colours and Fluent UI React for the components.
Why hard-coded colours fail
Modern pages support tenant and site themes, plus per-section backgrounds (including dark "emphasis" sections). If you bake #0078d4 into your CSS, your web part looks wrong the moment someone changes the theme or drops it into a dark section. The fix is to read colours from the theme at runtime and re-render when it changes.
Receiving the theme
SPFx delivers the current theme to your web part through the ThemeProvider. Acquire it in onInit(), store the theme, and subscribe to changes:
import { ThemeProvider, ThemeChangedEventArgs, IReadonlyTheme }
from '@microsoft/sp-component-base';
private _themeProvider: ThemeProvider;
private _theme: IReadonlyTheme | undefined;
protected onInit(): Promise<void> {
this._themeProvider =
this.context.serviceScope.consume(ThemeProvider.serviceKey);
this._theme = this._themeProvider.tryGetTheme();
this._themeProvider.themeChangedEvent.add(this, this._onThemeChanged);
return super.onInit();
}
private _onThemeChanged(args: ThemeChangedEventArgs): void {
this._theme = args.theme;
this.render(); // re-render with new colours
}
Using semantic colours
Don't read raw palette entries — read semantic slots, which are defined for every theme including dark ones. bodyText, bodyBackground, link, and buttonBackground always resolve to a sensible value for the current theme:
public render(): void {
const semantic = this._theme && this._theme.semanticColors;
const text = semantic?.bodyText ?? '#323130';
const bg = semantic?.bodyBackground ?? '#ffffff';
const link = semantic?.link ?? '#0078d4';
this.domElement.innerHTML = `
<div style="color:${text};background:${bg};padding:16px">
<h2>Contracts</h2>
<a style="color:${link}" href="#">View all</a>
</div>`;
}
palette.themePrimary tells you the accent; semanticColors.bodyText tells you "the right text colour for this surface." Semantic slots are what keep your web part legible on a dark emphasis section automatically.
Fluent UI: components that already theme themselves
The bigger win is to stop styling primitives by hand and use Fluent UI React (the office-ui-fabric-react / @fluentui/react package that ships with SPFx). Its components read the ambient theme, are keyboard-accessible, and match the rest of SharePoint out of the box:
import * as React from 'react';
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { DetailsList, IColumn } from 'office-ui-fabric-react/lib/DetailsList';
export const ContractForm: React.FC = () => (
<div>
<TextField label="Contract title" required />
<PrimaryButton text="Save" />
<DefaultButton text="Cancel" />
</div>
);
A PrimaryButton picks up the site's accent colour; a DetailsList renders a sortable, accessible, virtualized grid that looks identical to native SharePoint lists. You get consistency and accessibility for free, instead of reinventing — and likely under-delivering — both.
Accessibility isn't optional
- Contrast. Semantic colours guarantee readable contrast across themes; hand-picked hex values don't.
- Keyboard. Fluent components are fully keyboard-navigable and screen-reader labelled — re-creating that correctly with raw divs is genuinely hard.
- Focus & ARIA. Fluent manages focus rings, roles, and ARIA attributes that manual markup routinely gets wrong.
<div onClick> "button" fails keyboard and screen-reader testing; a Fluent PrimaryButton passes it without you writing a line of ARIA.
Putting it together
The recipe for a web part that feels native:
- Read the theme via
ThemeProviderand subscribe tothemeChangedEvent. - Pass the theme (or just the semantic colours) into your React component as a prop.
- Build the UI from Fluent UI components and let them consume the theme.
- Use semantic colours for any custom surfaces you style yourself.
The result is a web part that looks intentional on a default site, a branded site, and a dark emphasis section alike — and that passes accessibility review. Respecting the theme is a small amount of code that separates a professional solution from an obvious bolt-on.