Building Reusable Context Menus in SharePoint Framework (SPFx) with Fluent UI
Introduction
In enterprise SharePoint solutions, users often need quick contextual actions directly attached to data rows.
Examples include:
- Open item
- Edit metadata
- Trigger workflow
- View history
- Delete record
- Archive item
Instead of navigating away or opening a separate panel, a Context Menu offers immediate interaction.
This pattern is everywhere.
If you have ever used:
- SharePoint document libraries
- Microsoft Lists
- Teams file tabs
- Planner boards
you have already interacted with contextual menus.
In this article, we will build a reusable contextual menu component inside a SharePoint Framework (SPFx) web part using Fluent UI.
This example intentionally uses mocked data.
That is important.
We are isolating UI behavior first.
In the next article, we will connect this same pattern to a real SharePoint list using PnPjs and the official PnP ListView control.
This article is step one.
Why Start with Mock Data?
Many developers jump directly into SharePoint data integration.
That creates unnecessary complexity.
Before introducing:
- REST calls
- PnPjs
- async state
- loading indicators
- error handling
it is often better to validate the UI pattern first.
Mock data helps us:
1. Focus on component behavior
We only care about:
- rendering
- clicks
- menu opening
- action dispatching
2. Reduce noise
No API calls.
No permissions.
No network dependencies.
No authentication complexity.
3. Improve reusability
When the UI is isolated first, it becomes easier to connect to any data source later.
Examples:
- SharePoint list
- Graph API
- Dataverse
- SQL API
- Azure Function
That is good architecture.
The Architecture
This solution follows a small but scalable component model:
Parent Component | |-- Mocked Data Array | |-- Render Rows | |-- ECB Component | |-- Fluent UI IconButton |-- Fluent UI Contextual Menu
This separation is intentional.
The row owns the data.
The ECB component owns the actions.
This is called Separation of Concerns.
A core principle in React.
Technologies Used
| Technology | Purpose |
|---|---|
| SPFx | SharePoint hosting framework |
| React | Component architecture |
| TypeScript | Type safety |
| Fluent UI | UI components |
| SCSS Modules | Scoped styling |
Official References
SharePoint Framework
Official Microsoft documentation for SPFx.
Fluent UI
Fluent UI Official Documentation
Used for all UI elements.
Fluent UI IconButton
Fluent UI IconButton Documentation
Core component used in this article.
Fluent UI ContextualMenu
Fluent UI ContextualMenu Documentation
Official menu system used by the IconButton.
SPFx + Fluent UI Guidance
Microsoft’s official integration guidance.
Project Structure
src/├── webparts/│ ├── spPnPbasicListViewContextMenu/│ │ ├── components/│ │ │ ├── ECB.tsx│ │ │ ├── IECBProps.ts│ │ │ ├── SpPnPbasicListViewContextMenu.tsx│ │ │ ├── ISpPnPbasicListViewContextMenuProps.ts│ │ │ └── SpPnPbasicListViewContextMenu.module.scss
Simple.
Clean.
Scalable.
The Data Model
We start with a strongly typed model.
export interface IListitem { Firstname: string; Lastname: string;}
This represents our row data.
For now:
mocked only
Later:
real SharePoint list items.
Mock Dataset
Our dataset:
const mockItems = [ { Firstname: "John", Lastname: "Doe" }, { Firstname: "Mary", Lastname: "Smith" }, { Firstname: "Robert", Lastname: "Johnson" }];
This simulates SharePoint list rows.
Important:
This is not fake in a bad way.
It is a design-first approach.
The Reusable Context Menu Component
File:
ECB.tsx
This is the most important part.
It encapsulates the menu logic.
Complete Code
import * as React from 'react';import { IconButton } from '@fluentui/react';import { ContextualMenuItemType } from '@fluentui/react/lib/ContextualMenu';import { IECBProps } from './IECBProps';import styles from './ECB.module.scss';const ECB: React.FC<IECBProps> = ({ item }) => { const handleClick = React.useCallback((source: string) => { alert(`${source} clicked`); }, []); return ( <div className={styles.ecb}> <IconButton id="ContextualMenuButton1" className={styles.ecbbutton} iconProps={{ iconName: 'MoreVertical' }} menuIconProps={{ iconName: '' }} menuProps={{ shouldFocusOnMount: true, items: [ { key: 'action1', text: 'Action 1', onClick: () => handleClick(`${item.Firstname} Action 1`) }, { key: 'divider_1', itemType: ContextualMenuItemType.Divider }, { key: 'action2', text: 'Action 2', onClick: () => handleClick(`${item.Firstname} Action 2`) }, { key: 'action3', text: 'Action 3', onClick: () => handleClick(`${item.Lastname} Action 3`) }, { key: 'disabled', text: 'Disabled action', disabled: true } ] }} /> </div> );};export default ECB;
Breaking Down the Component
IconButton
<IconButton />
This renders the ellipsis button.
We define the icon:
iconProps={{ iconName: 'MoreVertical' }}
This is the standard Microsoft ellipsis.
Used everywhere in SharePoint.
menuProps
This connects the button to the menu:
menuProps={{ ... }}
Without this:
No menu.
Menu Items
Each action:
{ key: 'action1', text: 'Action 1'}
Requires:
- key
- text
- action
Dividers
Used for grouping:
{ key: 'divider_1', itemType: ContextualMenuItemType.Divider}
Great for separating:
CRUD actions
from
administrative actions.
Disabled Actions
Example:
{ key: 'disabled', text: 'Disabled action', disabled: true}
Real scenarios:
- no permission
- item locked
- workflow completed
This is common in enterprise apps.
Event Handling
We centralize logic:
const handleClick = React.useCallback(...)
Why?
Because:
- reusable
- easier maintenance
- cleaner actions
Instead of duplicating alert logic.
Props Interface
export interface IECBProps { item: IListitem;}
Strong typing.
Predictable data.
Safer refactoring.
This matters a lot in enterprise codebases.
Parent Component
This renders the rows.
Complete Code
import * as React from 'react';import ECB from './ECB';const mockItems = [ { Firstname: "John", Lastname: "Doe" }, { Firstname: "Mary", Lastname: "Smith" }, { Firstname: "Robert", Lastname: "Johnson" }];const SpPnPbasicListViewContextMenu: React.FC = () => { return ( <div> {mockItems.map((item, index) => ( <div key={index}> <span>{item.Firstname} {item.Lastname}</span> <ECB item={item} /> </div> ))} </div> );};export default SpPnPbasicListViewContextMenu;
Why This Pattern Matters
This exact architecture scales to:
- SharePoint ListView
- GridLayout
- Dashboard cards
- Document libraries
- Approval tables
Anywhere you have rows.
You will likely attach actions.
This component becomes reusable.
Future Evolution
This article stops here intentionally.
Next article:
We will replace:
const mockItems = [...]
with:
sp.web.lists.getByTitle(...)
using:
and later integrate:
PnP SPFx React Controls ListView
That will introduce:
- real data
- async calls
- loading states
- error handling
- contextual menu per row
That is the natural progression.
Mock first.
Real data second.
Scale third.
Final Thoughts
Many developers underestimate simple UI patterns.
But contextual menus are everywhere.
Mastering them early improves:
- UX
- productivity
- code reuse
- maintainability
And by starting with mocked data:
you isolate complexity.
That makes learning faster.
And architecture better.
This is the foundation for more advanced SPFx data-driven solutions.
The next step is turning this into a real SharePoint list experience.
And that is exactly where we go next.
