If you’ve ever scaffolded a new SPFx web part and stared at the file structure wondering, “Should I use Fluent UI v8 or v9?” — this guide is for you.
I’ve been building SPFx solutions for a while now, and Fluent UI is one of those topics that sounds straightforward until you actually try to wire it up in a real SharePoint environment. The Microsoft docs are helpful, but they gloss over the version confusion, the FluentProvider gotchas, and the real-world quirks you only discover after a few hours of debugging.
In this tutorial, I’ll walk you through everything — what Fluent UI is, how it fits into SPFx, the v8 vs. v9 decision you’ll need to make, working code examples, common pitfalls, and how to apply SharePoint’s native theme tokens so your web part looks at home on any site.
What Is Fluent UI?
Fluent UI is Microsoft’s open-source design system and React component library. It replaced Office UI Fabric and is now the standard UI framework across SharePoint, Teams, Outlook, and other Microsoft 365 products.
When you build SPFx web parts, using Fluent UI means your components automatically feel native to SharePoint — the fonts, colors, spacing, and interaction patterns all match what users already see on the page. You don’t have to style a button from scratch. You just import PrimaryButton and it looks right out of the box.
There are currently two active versions:
- Fluent UI React v8 (
@fluentui/react) — the version SPFx ships with by default - Fluent UI React v9 (
@fluentui/react-components) — the newer, redesigned library with a more modern API and Windows 11-inspired styling
Both are used in real-world SPFx projects today. Understanding the difference is the first thing you need to get right.
Fluent UI v8 vs. v9 in SPFx: Which One Should You Use?
This is the question I get asked more than any other. Here’s the honest answer: it depends on your SPFx version and how much refactoring you’re willing to do.
SPFx ships Fluent UI React v8 by default. When you scaffold a new web part with SPFx 1.19 or later, your project already has @fluentui/react in node_modules. No installation required for v8.
Fluent UI v9 has been supported alongside SPFx since version 1.19, but it requires a manual installation and additional setup. It does not replace v8 in the scaffolding — you bring it in yourself.
Here’s a quick comparison to help you decide:
| Topic | Fluent UI v8 | Fluent UI v9 |
|---|---|---|
| Package name | @fluentui/react | @fluentui/react-components |
| Included in SPFx scaffold | Yes | No (manual install) |
| Requires FluentProvider wrapper | No | Yes |
| Min SPFx version | Any | 1.19+ |
| Styling approach | Merge styles / CSS-in-JS | Griffel (CSS-in-JS, atomic) |
| Component API style | Class-based props | Modern, slotting model |
| TypeScript compatibility | TypeScript 4.x+ | TypeScript 5.3+ (SPFx 1.21+) |
| Best for | Existing projects, quick builds | New projects, Teams/Viva alignment |
My recommendation: If you’re starting a brand new project on SPFx 1.22, go with v9. If you’re maintaining an existing solution or need to ship something quickly, stick with v8. Both are fully supported, and both look good in SharePoint.
Setting Up Your SPFx Project
Before we write any Fluent UI code, make sure your environment is ready. As of early 2026, the latest stable SPFx release is v1.22.2, which introduced a major toolchain change — migrating from Gulp to Heft as the build orchestrator and upgrading TypeScript to v5.8. If you haven’t scaffolded a new project since SPFx 1.21, things look a bit different now.
Prerequisites
- Node.js v22 (required for SPFx 1.22+)
- npm v10+
- Yeoman and the SPFx generator
npm install -g yo @microsoft/generator-sharepoint
Scaffold a New Web Part
yo @microsoft/sharepoint
When prompted:
- What is your solution name? fluent-ui-demo
- Which type of client-side component to create? WebPart
- What is your Web part name? fluent-ui-demo
- Which template would you like to use? React
Using Fluent UI v8 in SPFx (Default Setup)
Since v8 is already in your project, you can import and use components immediately with zero extra installation.
A Practical Example: User Profile Card
Let’s build something more useful than a basic “fluent-ui-demo” — a simple user profile card component that shows a name, role, and an action button.

Inside your component file (e.g., FluentUiDemo.tsx):
import * as React from 'react';
import styles from './FluentUiDemo.module.scss';
import type { IFluentUiDemoProps } from './IFluentUiDemoProps';
import { Persona, PersonaSize, PrimaryButton, Stack, IStackTokens } from '@fluentui/react';
const stackTokens: IStackTokens = { childrenGap: 12 };
export interface IUserCardProps {
displayName: string;
jobTitle: string;
email: string;
imageUrl?: string;
}
const UserCard: React.FC<IUserCardProps> = ({ displayName, jobTitle, email, imageUrl }) => {
return (
<Stack tokens={stackTokens} styles={{ root: { padding: 16, maxWidth: 320 } }}>
<Persona
text={displayName}
secondaryText={jobTitle}
size={PersonaSize.size72}
imageUrl={imageUrl}
imageInitials={displayName.charAt(0)}
/>
<PrimaryButton
text="Send Email"
onClick={() => window.open(`mailto:${email}`)}
/>
</Stack>
);
};
export default class FluentUiDemo extends React.Component<IFluentUiDemoProps> {
public render(): React.ReactElement<IFluentUiDemoProps> {
const { hasTeamsContext, userDisplayName, userPhotoUrl } = this.props;
return (
<section className={`${styles.fluentUiDemo} ${hasTeamsContext ? styles.teams : ''}`}>
<UserCard
displayName={userDisplayName}
jobTitle="SharePoint Developer"
email="user@contoso.com"
imageUrl={userPhotoUrl}
/>
</section>
);
}
}
This is a real-world pattern. I use Persona constantly in SharePoint intranets for displaying user info pulled from Microsoft Graph. The Stack component is your go-to for layout — it handles spacing between children cleanly without writing custom CSS.
Using Fluent UI v9 in SPFx (Manual Setup)
Now let’s look at how to bring in v9 alongside your existing SPFx project. This is where most tutorials go vague, so I’ll be specific.
Step 1: Install the Package
npm install @fluentui/react-components --legacy-peer-deps
The --legacy-peer-deps flag is required. Without it, npm will throw peer dependency conflicts because SPFx’s internal packages expect older React peer dependency ranges. This tripped me up the first time.
Step 2: Update tsconfig.json
Open tsconfig.json in the root of your project and make sure jsx is set to react-jsx:
{
"compilerOptions": {
"jsx": "react-jsx"
}
}
This is required for v9 components to compile correctly with TypeScript 5.3+.
Step 3: Wrap Your Component in FluentProvider
This is the most important step. Fluent UI v9 components will not render with correct styles unless they’re wrapped inside a FluentProvider. Without it, you’ll get unstyled or broken-looking components — even in local Workbench.
Here’s how to do it inside your SPFx web part’s main component file:
import * as React from 'react';
import { FluentProvider, webLightTheme, Button, Input, Label } from '@fluentui/react-components';
export interface IFluentV9DemoProps {
siteUrl: string;
}
const FluentV9Demo: React.FC<IFluentV9DemoProps> = ({ siteUrl }) => {
const [searchValue, setSearchValue] = React.useState('');
return (
<FluentProvider theme={webLightTheme}>
<div style={{ padding: '16px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
<Label htmlFor="search-input" weight="semibold">Search SharePoint Site</Label>
<Input
id="search-input"
placeholder="Enter a keyword..."
value={searchValue}
onChange={(_, data) => setSearchValue(data.value)}
/>
<Button appearance="primary" onClick={() => console.log(`Searching: ${searchValue} on ${siteUrl}`)}>
Search
</Button>
</div>
</FluentProvider>
);
};
export default FluentV9Demo;
Notice I’m passing siteUrl as a prop — this comes from this.context.pageContext.web.absoluteUrl in your main web part class. Always pass SharePoint context down through props rather than importing it directly in your components. It makes testing much easier.

Fluent UI Dropdown in SPFx (Real SPFx Context, Not Generic React)
Most Fluent UI dropdown tutorials show you a standalone React component. That’s fine, but when you’re in SPFx, you need to wire it up to actual SharePoint data. Here’s a complete example that loads a Dropdown with site collection names (or any dynamic list you’d pull via PnP JS or Graph).
The Web Part Class (WebPart.ts)
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import SiteDropdownComponent, { ISiteDropdownProps } from './components/SiteDropdownComponent';
export default class SiteDropdownWebPart extends BaseClientSideWebPart<{}> {
public render(): void {
const element: React.ReactElement<ISiteDropdownProps> = React.createElement(
SiteDropdownComponent,
{
siteAbsoluteUrl: this.context.pageContext.web.absoluteUrl,
userDisplayName: this.context.pageContext.user.displayName,
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
}
The Component (SiteDropdownComponent.tsx)
import * as React from 'react';
import { Dropdown, IDropdownOption, Stack, Text } from '@fluentui/react';
export interface ISiteDropdownProps {
siteAbsoluteUrl: string;
userDisplayName: string;
}
const SiteDropdownComponent: React.FC<ISiteDropdownProps> = ({ siteAbsoluteUrl, userDisplayName }) => {
const [selectedKey, setSelectedKey] = React.useState<string | number | undefined>(undefined);
const options: IDropdownOption[] = [
{ key: 'team', text: 'Team Site' },
{ key: 'communication', text: 'Communication Site' },
{ key: 'hub', text: 'Hub Site' },
{ key: 'project', text: 'Project Site' },
];
const handleChange = (_: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
if (option) {
setSelectedKey(option.key);
console.log(`Selected: ${option.text} — Context: ${siteAbsoluteUrl}`);
}
};
return (
<Stack tokens={{ childrenGap: 12 }} styles={{ root: { padding: 16, maxWidth: 360 } }}>
<Text variant="large">Hello, {userDisplayName}</Text>
<Dropdown
label="Select Site Type"
options={options}
selectedKey={selectedKey}
onChange={handleChange}
placeholder="Choose a site type"
/>
{selectedKey && (
<Text variant="small" styles={{ root: { color: '#605e5c' } }}>
Selected: {options.find(o => o.key === selectedKey)?.text}
</Text>
)}
</Stack>
);
};
export default SiteDropdownComponent;
This is the pattern I use every time. You get the SharePoint context in the web part class, pass it as props, and keep your component pure and testable.
Fluent UI v9 DataGrid in SPFx
The DataGrid component in Fluent UI v9 is one of the most searched topics among SPFx developers — and for good reason. It’s far more capable than the v8 DetailsList for modern scenarios.
Here’s a working example using DataGrid wrapped in FluentProvider:
import * as React from 'react';
import {
FluentProvider,
webLightTheme,
DataGrid,
DataGridBody,
DataGridRow,
DataGridHeader,
DataGridHeaderCell,
DataGridCell,
createTableColumn,
TableCellLayout,
} from '@fluentui/react-components';
type ListItem = {
id: number;
title: string;
status: string;
assignedTo: string;
};
const items: ListItem[] = [
{ id: 1, title: 'Onboarding Portal', status: 'In Progress', assignedTo: 'Priya S.' },
{ id: 2, title: 'IT Request Form', status: 'Complete', assignedTo: 'James T.' },
{ id: 3, title: 'HR Policy Page', status: 'Review', assignedTo: 'Maria L.' },
];
const columns = [
createTableColumn<ListItem>({
columnId: 'title',
renderHeaderCell: () => 'Title',
renderCell: (item) => <TableCellLayout>{item.title}</TableCellLayout>,
}),
createTableColumn<ListItem>({
columnId: 'status',
renderHeaderCell: () => 'Status',
renderCell: (item) => <TableCellLayout>{item.status}</TableCellLayout>,
}),
createTableColumn<ListItem>({
columnId: 'assignedTo',
renderHeaderCell: () => 'Assigned To',
renderCell: (item) => <TableCellLayout>{item.assignedTo}</TableCellLayout>,
}),
];
const ItemDataGrid: React.FC = () => {
return (
<FluentProvider theme={webLightTheme}>
<DataGrid items={items} columns={columns} sortable>
<DataGridHeader>
<DataGridRow>
{({ renderHeaderCell }) => (
<DataGridHeaderCell>{renderHeaderCell()}</DataGridHeaderCell>
)}
</DataGridRow>
</DataGridHeader>
<DataGridBody<ListItem>>
{({ item, rowId }) => (
<DataGridRow<ListItem> key={rowId}>
{({ renderCell }) => (
<DataGridCell>{renderCell(item)}</DataGridCell>
)}
</DataGridRow>
)}
</DataGridBody>
</DataGrid>
</FluentProvider>
);
};
export default ItemDataGrid;
One thing I’ll warn you about upfront: if you have more than 200 rows, you’ll want to look into virtualization. The DataGrid renders all rows in the DOM by default. For enterprise SharePoint scenarios where you’re pulling a large list, add pagination or use a virtualized wrapper — otherwise performance will tank on lower-spec machines.
Applying SharePoint Theme Tokens in Fluent UI
This is something the Microsoft docs mention briefly, but almost nobody writes a practical example for — and it’s genuinely important if you’re building for enterprise tenants where every site has a different brand theme.
SharePoint exposes CSS theme tokens like [theme:themePrimary] and [theme:neutralPrimary] in CSS module files. The SPFx build process replaces these with the actual color values from the current site’s theme at runtime.
Here’s how you use them in a Fluent UI component’s styles:
MyWebPart.module.scss:
.themedButton {
background-color: "[theme:themePrimary, default:#0078d4]";
color: "[theme:white, default:#ffffff]";
&:hover {
background-color: "[theme:themeDarkAlt, default:#106ebe]";
}
}
MyComponent.tsx:
import styles from './MyWebPart.module.scss';
import { DefaultButton } from '@fluentui/react';
<DefaultButton className={styles.themedButton} text="Theme-Aware Button" />
The default: value is your fallback. Always include it — if SharePoint can’t resolve the token (e.g., in Workbench), it uses the fallback instead of leaving the element unstyled.
This approach means your web part automatically respects tenant branding, dark mode, and section background changes — without you writing a single conditional style rule.
Common Pitfalls I’ve Hit (So You Don’t Have To)
After building a fair number of SPFx solutions with Fluent UI, here are the real problems you’ll run into:
- Fluent UI v9 components look completely unstyled in Workbench — This almost always means you forgot the
FluentProviderwrapper, or you placed it inside a child component instead of at the root of your render tree. MoveFluentProviderto the outermost level. npm install @fluentui/react-componentsfails with peer dependency errors — Always add--legacy-peer-deps. SPFx’s internally bundled React packages clash with v9’s declared peer dependencies. This is a known issue and the flag is the correct workaround.- TypeScript errors like “Type X is not assignable to type Y” — Check that
"jsx": "react-jsx"is in yourtsconfig.json. Without it, v9’s component generics don’t compile cleanly under TypeScript 5.3+. - Icon rendering breaks after installing v9 — If you also install
@fluentui/react-icons, pin it to a version that’s compatible with your@fluentui/react-componentsversion. Mismatched minor versions between icon and component packages cause silent rendering failures. - Dropdown showing wrong selection state — This happens when you forget to lift state up. In SPFx, if you render the same component multiple times on the page (e.g., inside a repeating section), each instance needs its own isolated state. Don’t rely on module-level variables for selection state.
- Styles leaking between Fluent UI v8 and v9 components — If you’re using both libraries in the same web part (which is valid), make sure your v9 components are fully inside the
FluentProviderscope. If v8 and v9 elements are siblings at the same DOM level without proper containment, you’ll see CSS specificity clashes. - gulp serve Replaced in SPFx 1.22 — With the migration from Gulp to Heft in SPFx 1.22, the old
gulp servecommand works differently now. If you upgraded and your serve command is broken, runnpm run serveinstead. The new Heft-based toolchain uses npm scripts.
Fluent UI Controls You’ll Use Most in SPFx
Here’s a quick reference of the most useful Fluent UI components for SharePoint solutions, with the most common use cases:
v8 Components (from @fluentui/react):
Dropdown— any selection input driven by list dataPersona— showing user info from Graph APIDetailsList— displaying SharePoint list data in a tablePanel— slide-out forms (great for edit forms without navigating away)Dialog— confirmation modals, delete warningsStack— your main layout tool; handles spacing and directionTextField— text input with built-in label and validationSpinner— show while fetching data from SharePoint or GraphMessageBar— success/error feedback after form submissionsPrimaryButton/DefaultButton— action triggers
v9 Components (from @fluentui/react-components):
Button— replaces PrimaryButton/DefaultButton with a cleaner APIInput— text input with a more composable designSelect— lightweight alternative to DropdownDataGrid— replaces DetailsList for sortable, modern tablesDialog— improved accessibility over v8 DialogToast/Toaster— non-blocking notifications (much better than MessageBar for real-time feedback)Accordion— collapsible content sections, great for FAQ or settings panelsBadge— status indicators on list items or cards
Accessibility: Why It Matters More in SharePoint Than You Think
Fluent UI is built with accessibility as a first-class concern, which is one of the strongest reasons to use it over custom UI. But there’s a SharePoint-specific reason accessibility matters even more: many enterprise organizations have intranet accessibility compliance policies tied to their IT governance.
A few things to keep in mind:
- Always provide a
labelprop or anaria-labelon form controls. Fluent UI doesn’t automatically associate labels with inputs unless you use the built-inlabelprop or thehtmlFor/idpattern. - Fluent UI
DialogandPanelboth handle focus trapping automatically — this is something you’d have to implement manually with custom HTML. - For
DataGrid, test keyboard navigation explicitly. Tab order in complex grids needs validation in your target browser (Edge, primarily, for SharePoint). - Test in SharePoint Online directly, not just Workbench. Some accessibility features behave differently because of the SharePoint page’s own focus management.
Frequently Asked Questions
Does SPFx include Fluent UI by default?
Yes. SPFx ships with Fluent UI React v8 (@fluentui/react) as a default dependency. You can use v8 components without installing anything extra.
Can I use Fluent UI v9 in SPFx?
Yes, starting from SPFx 1.19. You need to install @fluentui/react-components manually using npm install @fluentui/react-components --legacy-peer-deps and wrap your v9 components in a FluentProvider.
Can I use v8 and v9 in the same web part?
Yes, they can coexist. Just keep your v9 components inside a FluentProvider and avoid mixing v8 and v9 versions of the same component type (e.g., don’t use both the v8 Button and v9 Button in the same render tree — pick one for consistency).
What is FluentProvider and why do I need it?
FluentProvider is a context wrapper that provides theme and styling tokens to all Fluent UI v9 components inside it. Without it, v9 components render without styles.
What happened to Office UI Fabric?
Office UI Fabric was the original name for Microsoft’s UI library. It was rebranded as Fluent UI React. If you see @uifabric/react in older projects, that’s the old package name — it still works but is no longer maintained.
Which SPFx version should I use in 2026?
Use SPFx 1.22.2, the latest stable release as of early 2026. It includes TypeScript 5.8, a modernized Heft-based build toolchain, and clean npm audit reports.
Common Mistakes to Avoid
Before wrapping up, here’s a short list of decisions that come back to bite people later:
- Don’t mix v8 and v9 theme systems. If you start with v9, use v9’s
webLightTheme/teamsLightTheme. Don’t try to apply v8’sloadTheme()to v9 components. - Don’t skip the
onDisposemethod. Always callReactDom.unmountComponentAtNode(this.domElement)inonDispose(). Memory leaks from unmounted components are common in SPFx when users navigate between pages. - Don’t test only in Workbench. Always do a final test in SharePoint Online itself. Workbench doesn’t fully simulate the page context, section backgrounds, or real theme tokens.
- Don’t hardcode colors. Use Fluent UI tokens or SharePoint theme tokens. Hardcoded hex values break in dark mode and when tenants apply custom branding.
- Don’t ignore the
ComboBoxfor large data sets. If your Dropdown is populated by a SharePoint list with hundreds of items, switch toComboBoxwithallowFreeformand consider filtering on the server side before binding to the control.
Also, you may like:
- SharePoint Framework (SPFx) Cascading Dropdown
- Create a Modal Popup in SPFx
- Build a Custom Slides Manager in SPFx Web Part Property Pane (Drag & Drop, Reorder, Hide Slides)
- Fluent UI React Dropdown in SharePoint Framework (SPFx)

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.