Fluent UI in SharePoint Framework (SPFx)

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:

TopicFluent UI v8Fluent UI v9
Package name@fluentui/react@fluentui/react-components
Included in SPFx scaffoldYesNo (manual install)
Requires FluentProvider wrapperNoYes
Min SPFx versionAny1.19+
Styling approachMerge styles / CSS-in-JSGriffel (CSS-in-JS, atomic)
Component API styleClass-based propsModern, slotting model
TypeScript compatibilityTypeScript 4.x+TypeScript 5.3+ (SPFx 1.21+)
Best forExisting projects, quick buildsNew 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.

fluent ui persona card in spfx web part

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 in SharePoint Framework

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 FluentProvider wrapper, or you placed it inside a child component instead of at the root of your render tree. Move FluentProvider to the outermost level.
  • npm install @fluentui/react-components fails 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 your tsconfig.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-components version. 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 FluentProvider scope. 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 serve command works differently now. If you upgraded and your serve command is broken, run npm run serve instead. 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 data
  • Persona â€” showing user info from Graph API
  • DetailsList â€” displaying SharePoint list data in a table
  • Panel â€” slide-out forms (great for edit forms without navigating away)
  • Dialog â€” confirmation modals, delete warnings
  • Stack â€” your main layout tool; handles spacing and direction
  • TextField â€” text input with built-in label and validation
  • Spinner â€” show while fetching data from SharePoint or Graph
  • MessageBar â€” success/error feedback after form submissions
  • PrimaryButton / DefaultButton â€” action triggers

v9 Components (from @fluentui/react-components):

  • Button â€” replaces PrimaryButton/DefaultButton with a cleaner API
  • Input â€” text input with a more composable design
  • Select â€” lightweight alternative to Dropdown
  • DataGrid â€” replaces DetailsList for sortable, modern tables
  • Dialog â€” improved accessibility over v8 Dialog
  • Toast / Toaster â€” non-blocking notifications (much better than MessageBar for real-time feedback)
  • Accordion â€” collapsible content sections, great for FAQ or settings panels
  • Badge â€” 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 label prop or an aria-label on form controls. Fluent UI doesn’t automatically associate labels with inputs unless you use the built-in label prop or the htmlFor / id pattern.
  • Fluent UI Dialog and Panel both 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’s loadTheme() to v9 components.
  • Don’t skip the onDispose method. Always call ReactDom.unmountComponentAtNode(this.domElement) in onDispose(). 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 ComboBox for large data sets. If your Dropdown is populated by a SharePoint list with hundreds of items, switch to ComboBox with allowFreeform and consider filtering on the server side before binding to the control.

Also, you may like:

Leave a Comment

Power Apps functions free pdf

30 Power Apps Functions

This free guide walks you through the 30 most-used Power Apps functions with real business examples, exact syntax, and results you can see.

Live Webinar

Create a SharePoint List & Columns from Excel Using Power Automate

Learn how to automatically create a SharePoint Online list and all its columns from an Excel file using Power Automate—without using any Premium connectors..

📅 4th Aug 2026 – 10:00 AM EST | 7:30 PM IST

Download User registration canvas app

DOWNLOAD USER REGISTRATION POWER APPS CANVAS APP

Download a fully functional Power Apps Canvas App (with Power Automate): User Registration App