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

TechnologyPurpose
SPFxSharePoint hosting framework
ReactComponent architecture
TypeScriptType safety
Fluent UIUI components
SCSS ModulesScoped styling

Official References

SharePoint Framework

SharePoint Framework Overview

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

Using Fluent UI in SPFx

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:

PnPjs Official Documentation

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.

Edvaldo Guimrães Filho Avatar

Published by