How to Use PropertyFieldOrder in an SPFx Web Part and Fix Missing Icons

The PropertyFieldOrder control is part of the PnP SPFx Property Controls library.

It allows users to reorder a collection of items directly from the Web Part Property Pane by using move-up and move-down buttons.

In this example, the control manages a list of items such as:

Cat
Pig
Human
Robot
Dog

When the user changes the order in the Property Pane, the new order is stored in the Web Part properties and immediately displayed by the React component.

Official documentation:

PropertyFieldOrder — PnP SPFx Property Controls


1. Install the PnP Property Controls package

From the root folder of the existing SPFx solution, run:

npm install @pnp/spfx-property-controls --save

Restore all project dependencies:

npm install

Start the SPFx development server:

heft start

2. Project structure

The example uses the following files:

src
└── webparts
└── propertyFieldOrderWp
├── PropertyFieldOrderWpWebPart.ts
└── components
├── IPropertyFieldOrderWpProps.ts
├── PropertyFieldOrderWp.tsx
└── orderedItem.tsx

Pay close attention to file-name capitalization.

In this example, the file is named:

orderedItem.tsx

Therefore, the import must use exactly the same capitalization:

import { orderedItem } from './components/orderedItem';

Using:

import { orderedItem } from './components/OrderedItem';

while the actual file is named orderedItem.tsx may cause TypeScript error TS1261.


3. Ordered item model

Each reorderable item contains two properties:

export interface IOrderedItem {
text: string;
iconName: string;
}

The text property contains the text displayed in the control.

The iconName property contains the Fluent UI or MDL2 icon name associated with the item.

Example:

{
text: 'Robot',
iconName: 'Robot'
}

The complete collection is stored in the Web Part properties:

orderedItems: IOrderedItem[];

4. Web Part properties interface

Inside PropertyFieldOrderWpWebPart.ts, define the item model and Web Part properties:

export interface IOrderedItem {
text: string;
iconName: string;
}
export interface IPropertyFieldOrderWpWebPartProps {
description: string;
orderedItems: IOrderedItem[];
}

The orderedItems property is responsible for storing the current order selected by the user.


5. Complete PropertyFieldOrderWpWebPart.ts

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
type IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import {
BaseClientSideWebPart
} from '@microsoft/sp-webpart-base';
import {
IReadonlyTheme
} from '@microsoft/sp-component-base';
import {
initializeIcons
} from '@fluentui/react/lib/Icons';
import * as strings from 'PropertyFieldOrderWpWebPartStrings';
import PropertyFieldOrderWp
from './components/PropertyFieldOrderWp';
import {
IPropertyFieldOrderWpProps
} from './components/IPropertyFieldOrderWpProps';
import {
PropertyFieldOrder
} from '@pnp/spfx-property-controls/lib/PropertyFieldOrder';
import {
orderedItem
} from './components/orderedItem';
export interface IOrderedItem {
text: string;
iconName: string;
}
export interface IPropertyFieldOrderWpWebPartProps {
description: string;
orderedItems: IOrderedItem[];
}
export default class PropertyFieldOrderWpWebPart
extends BaseClientSideWebPart<IPropertyFieldOrderWpWebPartProps> {
private _isDarkTheme: boolean = false;
private _environmentMessage: string = '';
public render(): void {
const element: React.ReactElement<IPropertyFieldOrderWpProps> =
React.createElement(
PropertyFieldOrderWp,
{
description: this.properties.description,
isDarkTheme: this._isDarkTheme,
environmentMessage: this._environmentMessage,
userDisplayName:
this.context.pageContext.user.displayName,
orderedItems:
this.properties.orderedItems || []
}
);
ReactDom.render(
element,
this.domElement
);
}
protected async onInit(): Promise<void> {
await super.onInit();
initializeIcons();
if (
!this.properties.orderedItems ||
this.properties.orderedItems.length === 0
) {
this.properties.orderedItems = [
{
text: 'Home',
iconName: 'Home'
},
{
text: 'Mail',
iconName: 'Mail'
},
{
text: 'Calendar',
iconName: 'Calendar'
},
{
text: 'People',
iconName: 'People'
},
{
text: 'Settings',
iconName: 'Settings'
}
];
}
this._environmentMessage =
await this._getEnvironmentMessage();
this.context.propertyPane.refresh();
}
private _getEnvironmentMessage(): Promise<string> {
if (this.context.sdks.microsoftTeams) {
return this.context.sdks.microsoftTeams.teamsJs.app
.getContext()
.then(context => {
switch (context.app.host.name) {
case 'Office':
return this.context.isServedFromLocalhost
? strings.AppLocalEnvironmentOffice
: strings.AppOfficeEnvironment;
case 'Outlook':
return this.context.isServedFromLocalhost
? strings.AppLocalEnvironmentOutlook
: strings.AppOutlookEnvironment;
case 'Teams':
case 'TeamsModern':
return this.context.isServedFromLocalhost
? strings.AppLocalEnvironmentTeams
: strings.AppTeamsTabEnvironment;
default:
return strings.UnknownEnvironment;
}
});
}
return Promise.resolve(
this.context.isServedFromLocalhost
? strings.AppLocalEnvironmentSharePoint
: strings.AppSharePointEnvironment
);
}
protected onThemeChanged(
currentTheme: IReadonlyTheme | undefined
): void {
if (!currentTheme) {
return;
}
this._isDarkTheme =
!!currentTheme.isInverted;
const {
semanticColors
} = currentTheme;
if (semanticColors) {
this.domElement.style.setProperty(
'--bodyText',
semanticColors.bodyText || null
);
this.domElement.style.setProperty(
'--link',
semanticColors.link || null
);
this.domElement.style.setProperty(
'--linkHovered',
semanticColors.linkHovered || null
);
}
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(
this.domElement
);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration():
IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description:
strings.PropertyPaneDescription
},
groups: [
{
groupName:
strings.BasicGroupName,
groupFields: [
PropertyPaneTextField(
'description',
{
label:
strings.DescriptionFieldLabel
}
),
PropertyFieldOrder(
'orderedItems',
{
key: 'orderedItems',
label: 'Ordered Items',
items:
this.properties.orderedItems || [],
textProperty: 'text',
onRenderItem: orderedItem,
properties: this.properties,
onPropertyChange:
this.onPropertyPaneFieldChanged
}
)
]
}
]
}
]
};
}
}

6. Initializing the default items

The items are initialized inside onInit():

if (
!this.properties.orderedItems ||
this.properties.orderedItems.length === 0
) {
this.properties.orderedItems = [
{
text: 'Home',
iconName: 'Home'
},
{
text: 'Mail',
iconName: 'Mail'
},
{
text: 'Calendar',
iconName: 'Calendar'
},
{
text: 'People',
iconName: 'People'
},
{
text: 'Settings',
iconName: 'Settings'
}
];
}

The condition is important.

Without it, the default array would replace the order saved by the user every time the Web Part loads.

Avoid this:

this.properties.orderedItems = [
// Default items
];

Use this instead:

if (
!this.properties.orderedItems ||
this.properties.orderedItems.length === 0
) {
this.properties.orderedItems = [
// Default items
];
}

This ensures that the collection is created only when no saved configuration exists.

After creating the default values, refresh the Property Pane:

this.context.propertyPane.refresh();

7. Configuring PropertyFieldOrder

The control is added to the Property Pane configuration:

PropertyFieldOrder(
'orderedItems',
{
key: 'orderedItems',
label: 'Ordered Items',
items:
this.properties.orderedItems || [],
textProperty: 'text',
onRenderItem: orderedItem,
properties: this.properties,
onPropertyChange:
this.onPropertyPaneFieldChanged
}
)

Property name

'orderedItems'

This must match the property declared in the Web Part interface:

orderedItems: IOrderedItem[];

key

key: 'orderedItems'

The key uniquely identifies the Property Pane control.

label

label: 'Ordered Items'

This is the label displayed above the collection.

items

items: this.properties.orderedItems || []

This provides the collection displayed by the control.

Using || [] prevents errors when the property is temporarily undefined.

textProperty

textProperty: 'text'

This tells the control which property contains the item label.

Each object contains:

{
text: 'Home',
iconName: 'Home'
}

Therefore, the display property is text.

onRenderItem

onRenderItem: orderedItem

This defines a custom React renderer for each item.

properties

properties: this.properties

This gives the control access to the current Web Part properties.

onPropertyChange

onPropertyChange:
this.onPropertyPaneFieldChanged

This allows the control to update:

this.properties.orderedItems

when the user changes the item order.


8. orderedItem.tsx

The orderedItem.tsx file controls how each item appears inside the Property Pane.

import * as React from 'react';
import {
Icon
} from '@fluentui/react/lib/Icon';
export interface IOrderedItem {
text: string;
iconName: string;
}
export const orderedItem = (
item: IOrderedItem,
index: number
): JSX.Element => {
return (
<span>
<Icon
iconName={item.iconName}
styles={{
root: {
paddingRight: '6px'
}
}}
aria-hidden="true"
/>
{item.text}
</span>
);
};

The item text is rendered with:

{item.text}

The icon is rendered with:

<Icon iconName={item.iconName} />

9. The icon issue

The most important issue in this example is the icon rendering.

The original implementation used:

<i
className={`ms-Icon ms-Icon--${item.iconName}`}
style={{ paddingRight: '4px' }}
aria-hidden="true"
/>

This does not create an SVG icon or import a React component.

It only generates HTML such as:

<i class="ms-Icon ms-Icon--Robot"></i>

For the icon to appear, the following conditions must be true:

  1. The MDL2 icon font must be loaded.
  2. The ms-Icon CSS classes must be available.
  3. The icon name must exist in the loaded icon set.

When one of these conditions is missing, the text still appears, but the icon remains invisible.

That explains why the ordered list may display:

Pig
Human
Cat
Dog
Robot

without displaying the icons.


10. Why the text appears but the icon does not

The item text is regular React content:

{item.text}

It does not depend on external CSS or an icon font.

The icon, however, depends on a generated class such as:

ms-Icon--Robot

That class normally maps to a character from a special font.

If the font or CSS class is unavailable, the <i> element remains empty.

Therefore, the PropertyFieldOrder control itself may be working correctly even when no icons are visible.

The proof is that:

  • the items appear;
  • the move-up and move-down buttons work;
  • the item order changes;
  • the React component receives the updated collection.

Only the visual icon rendering is failing.


11. Initialize Fluent UI icons

To ensure that Fluent UI icons are registered, import initializeIcons:

import {
initializeIcons
} from '@fluentui/react/lib/Icons';

Call it inside onInit():

protected async onInit(): Promise<void> {
await super.onInit();
initializeIcons();
// Remaining initialization code
}

This registers the Fluent UI icon collection used by the Icon component.


12. Use the Fluent UI Icon component

Instead of manually generating CSS classes, use:

import {
Icon
} from '@fluentui/react/lib/Icon';

Then render the icon like this:

<Icon
iconName={item.iconName}
styles={{
root: {
paddingRight: '6px'
}
}}
aria-hidden="true"
/>

This approach is clearer and more reliable than:

<i className={`ms-Icon ms-Icon--${item.iconName}`} />

13. Use known icon names during testing

Some icon names shown in older samples may not be available in every Fluent UI configuration.

For example:

Cat
Savings
FangBody

may not render depending on the icon package and version.

Start with common icon names:

this.properties.orderedItems = [
{
text: 'Home',
iconName: 'Home'
},
{
text: 'Mail',
iconName: 'Mail'
},
{
text: 'Calendar',
iconName: 'Calendar'
},
{
text: 'People',
iconName: 'People'
},
{
text: 'Settings',
iconName: 'Settings'
}
];

This helps separate two different problems:

  • the icon system was not initialized;
  • the icon name does not exist.

14. Diagnosing missing icons

A simple diagnostic technique is to display the icon name next to the item:

<li>
{item.text}{item.iconName}
</li>

Expected result:

Home — Home
Mail — Mail
Calendar — Calendar
People — People
Settings — Settings

This confirms that the object contains the correct values.

Next, test a fixed icon:

<Icon iconName="Home" />

If Home appears, the Fluent UI icon system is working.

Then return to the dynamic version:

<Icon iconName={item.iconName} />

If only some icons disappear, those icon names are probably unavailable.


15. React component properties

Create IPropertyFieldOrderWpProps.ts:

export interface IOrderedItem {
text: string;
iconName: string;
}
export interface IPropertyFieldOrderWpProps {
description: string;
isDarkTheme: boolean;
environmentMessage: string;
userDisplayName: string;
orderedItems: IOrderedItem[];
}

The orderedItems collection is passed from the Web Part to the React component.


16. Complete PropertyFieldOrderWp.tsx

import * as React from 'react';
import {
Icon
} from '@fluentui/react/lib/Icon';
import {
IPropertyFieldOrderWpProps
} from './IPropertyFieldOrderWpProps';
const PropertyFieldOrderWp:
React.FC<IPropertyFieldOrderWpProps> =
(props) => {
return (
<div>
<h1>
Property Field Order Web Part
</h1>
<p>
This is a sample Web Part demonstrating
the use of the PropertyFieldOrder control.
</p>
<p>
Dark Theme:
{' '}
{props.isDarkTheme ? 'Yes' : 'No'}
</p>
<p>
Environment Message:
{' '}
{props.environmentMessage}
</p>
<p>
User Display Name:
{' '}
{props.userDisplayName}
</p>
<p>
Ordered Items:
</p>
<ul>
{props.orderedItems.map(
(item, index) => (
<li key={`${item.text}-${index}`}>
<Icon
iconName={item.iconName}
styles={{
root: {
paddingRight: '6px'
}
}}
aria-hidden="true"
/>
{item.text}
</li>
)
)}
</ul>
</div>
);
};
export default PropertyFieldOrderWp;

17. How the new order reaches the React component

When the user moves an item in the Property Pane, PropertyFieldOrder calls:

onPropertyChange:
this.onPropertyPaneFieldChanged

The new collection is stored in:

this.properties.orderedItems

During the next render, the Web Part passes the updated array to React:

orderedItems:
this.properties.orderedItems || []

The React component renders the array in its current order:

props.orderedItems.map(...)

This creates the complete data flow:

Property Pane
PropertyFieldOrder
this.properties.orderedItems
Web Part render()
React component props
Rendered ordered list

18. Persistence considerations

The order is stored in the Web Part property bag.

However, it should not be overwritten during every initialization.

Incorrect:

protected async onInit(): Promise<void> {
await super.onInit();
this.properties.orderedItems = [
// Default items
];
}

This resets the user-defined order every time the Web Part loads.

Correct:

if (
!this.properties.orderedItems ||
this.properties.orderedItems.length === 0
) {
this.properties.orderedItems = [
// Default items
];
}

This preserves the saved collection.


19. Classic MDL2 icons versus Fluent UI System Icons

The following syntax belongs to the older font-based icon approach:

<i className="ms-Icon ms-Icon--Home" />

The icon is represented by a character in a font.

Modern Fluent UI System Icons are normally imported as SVG React components.

Example:

import {
Home24Regular
} from '@fluentui/react-icons';

Usage:

<Home24Regular />

However, PropertyFieldOrder stores the icon name as a string:

iconName: 'Home'

For this specific scenario, the Fluent UI Icon component is easier because it accepts a string:

<Icon iconName={item.iconName} />

20. Common problems

Items do not appear

Check whether orderedItems is initialized:

items: this.properties.orderedItems || []

Also refresh the Property Pane after initialization:

this.context.propertyPane.refresh();

The order resets after reloading

Do not assign the default array unconditionally.

Use:

if (
!this.properties.orderedItems ||
this.properties.orderedItems.length === 0
)

TypeScript reports duplicate files with different casing

Ensure that the file name and import use exactly the same capitalization:

orderedItem.tsx
import { orderedItem } from './components/orderedItem';

Text appears but icons do not

Initialize the Fluent UI icon set:

initializeIcons();

Render icons with:

<Icon iconName={item.iconName} />

Test with known names such as:

Home
Mail
Calendar
People
Settings

Conclusion

The PropertyFieldOrder control provides a simple way to reorder objects directly from the SPFx Property Pane.

The control was working correctly when:

  • the items appeared;
  • the move buttons were visible;
  • the order changed;
  • the React list reflected the new order.

The missing icons were a separate rendering issue.

The original code:

<i
className={`ms-Icon ms-Icon--${item.iconName}`}
/>

depends on icon-font CSS classes and valid MDL2 icon names.

The recommended approach is to initialize the Fluent UI icons:

initializeIcons();

and render them using:

<Icon iconName={item.iconName} />

It is also important to test with known icon names before using less common values.

Official reference

PropertyFieldOrder — PnP SPFx Property Controls

Edvaldo Guimrães Filho Avatar

Published by