Using the PnP PropertyFieldButton Control in an SPFx Web Part
The PropertyFieldButton control adds an action button directly to the SharePoint Framework Property Pane.
Unlike input controls such as text fields, dropdowns, or checkboxes, this control does not necessarily store a value in the Web Part properties. Instead, it executes an action when the user clicks the button.
Some possible uses include:
- Opening a configuration page
- Resetting Web Part settings
- Testing a connection
- Starting a custom operation
- Displaying a confirmation message
- Loading or refreshing configuration data
This article demonstrates a basic implementation in which the button displays a browser alert.
The example is part of our broader PnP controls learning roadmap, where each control is tested in its own SPFx Web Part.
Official documentation
The official documentation for this control is available here:
PropertyFieldButton – PnP SPFx Property Controls
The PnP SPFx Property Controls project provides reusable controls designed specifically for the SPFx Property Pane.
Install the PnP Property Controls package
Open PowerShell in the root directory of the existing SPFx solution.
npm install @pnp/spfx-property-controls --save
After installing the dependency, restore the remaining project packages:
npm install
Import PropertyFieldButton
Open the main Web Part file and add the following import:
import { PropertyFieldButton} from '@pnp/spfx-property-controls/lib/PropertyFieldButton';
The import comes from the specific control directory instead of importing the entire library.
Web Part properties
For this example, the Web Part only stores the standard description property:
export interface IPropertyFieldButtonWpWebPartProps { description: string;}
A property such as Btn is not required because the button executes an action rather than storing a selected value.
If a string property were necessary, the TypeScript type should be written as lowercase string:
buttonValue: string;
Avoid using the JavaScript wrapper type:
buttonValue: String;
For the current example, neither property is needed.
Complete Web Part file
Open:
src/webparts/propertyFieldButtonWp/PropertyFieldButtonWpWebPart.ts
Replace its content with the following code:
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 * as strings from 'PropertyFieldButtonWpWebPartStrings';import PropertyFieldButtonWp from './components/PropertyFieldButtonWp';import { IPropertyFieldButtonWpProps} from './components/IPropertyFieldButtonWpProps';import { PropertyFieldButton} from '@pnp/spfx-property-controls/lib/PropertyFieldButton';export interface IPropertyFieldButtonWpWebPartProps { description: string;}export default class PropertyFieldButtonWpWebPart extends BaseClientSideWebPart<IPropertyFieldButtonWpWebPartProps> { private _isDarkTheme: boolean = false; private _environmentMessage: string = ''; public render(): void { const element: React.ReactElement<IPropertyFieldButtonWpProps> = React.createElement( PropertyFieldButtonWp, { description: this.properties.description } ); ReactDom.render(element, this.domElement); } protected onInit(): Promise<void> { return this._getEnvironmentMessage().then( (message: string): void => { this._environmentMessage = message; } ); } private _getEnvironmentMessage(): Promise<string> { if (this.context.sdks.microsoftTeams) { return this.context.sdks.microsoftTeams.teamsJs.app .getContext() .then((context): string => { let environmentMessage: string = ''; switch (context.app.host.name) { case 'Office': environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment; break; case 'Outlook': environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment; break; case 'Teams': case 'TeamsModern': environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment; break; default: environmentMessage = strings.UnknownEnvironment; } return environmentMessage; }); } 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 }), PropertyFieldButton('', { key: 'buttonID', text: 'Button', disabled: false, isPrimary: true, isVisible: true, onClick: (): void => { alert('Button Clicked'); } }) ] } ] } ] }; }}
Component properties interface
Open:
src/webparts/propertyFieldButtonWp/components/IPropertyFieldButtonWpProps.ts
Use the following interface:
export interface IPropertyFieldButtonWpProps { description: string;}
The component only receives the description because the button exists inside the Property Pane, not inside the React component.
React component
Open:
src/webparts/propertyFieldButtonWp/components/PropertyFieldButtonWp.tsx
Use the following code:
import * as React from 'react';import { IPropertyFieldButtonWpProps} from './IPropertyFieldButtonWpProps';const PropertyFieldButtonWp: React.FC<IPropertyFieldButtonWpProps> = ({ description }) => { return ( <section> <h2>PropertyFieldButton Demo</h2> <p>{description}</p> <p> Open the Web Part Property Pane and click the configured button. </p> </section> ); };export default PropertyFieldButtonWp;
The button is not rendered by this React component. It is rendered automatically by SPFx inside the Web Part Property Pane.
Adding the control to the Property Pane group
Every Property Pane page contains one or more groups:
groups: [ { groupName: strings.BasicGroupName, groupFields: [ // Property Pane controls ] }]
The PropertyFieldButton must be added to the groupFields array:
groupFields: [ PropertyPaneTextField('description', { label: strings.DescriptionFieldLabel }), PropertyFieldButton('', { key: 'buttonID', text: 'Button', disabled: false, isPrimary: true, isVisible: true, onClick: (): void => { alert('Button Clicked'); } })]
The first parameter is normally the target property name for a Property Pane control:
PropertyFieldButton('', {
In this case, an empty string is used because the button does not need to save a value in this.properties.
Understanding the properties
key
key: 'buttonID'
The key uniquely identifies the control inside the Property Pane.
Each control should have its own key.
text
text: 'Button'
Defines the text displayed inside the button.
A more descriptive value could be used:
text: 'Test configuration'
disabled
disabled: false
Controls whether the user can click the button.
To disable it:
disabled: true
This property can also depend on another Web Part property:
disabled: !this.properties.description
In this example, the button will only be enabled when the description contains a value.
isPrimary
isPrimary: true
Defines whether the button should use the primary visual style.
Primary button:
isPrimary: true
Standard button:
isPrimary: false
isVisible
isVisible: true
Controls whether the button appears in the Property Pane.
The property name is case-sensitive.
Use:
isVisible: true
Do not use:
isVIsible: true
The uppercase I in the middle produces an invalid property name.
onClick
onClick: (): void => { alert('Button Clicked');}
The onClick callback defines the operation executed when the user clicks the button.
For this demonstration, it displays a browser alert.
In a real solution, the callback could call a private Web Part method:
onClick: this._handleButtonClick
The method could then be defined inside the Web Part class:
private _handleButtonClick = (): void => { alert('Button Clicked');};
Using a separate method is useful when the action contains more than a few lines of code.
Object syntax versus JSX syntax
A common mistake is using JSX property syntax inside the Property Pane configuration.
This syntax is invalid inside a TypeScript object:
PropertyFieldButton('', { text={"Button"}, key={"buttonID"}, disabled={false}})
The {} syntax is normally used when passing values to React components in JSX:
<MyComponent text={"Button"} disabled={false}/>
However, PropertyFieldButton receives a regular TypeScript configuration object.
The correct syntax uses colons:
PropertyFieldButton('', { text: 'Button', key: 'buttonID', disabled: false})
Optional styling properties
The control also supports optional configuration such as:
className
styles
iconProps
These properties should only be added when their values have been declared.
For example, an icon can be configured directly:
PropertyFieldButton('', { key: 'buttonID', text: 'Run action', disabled: false, isPrimary: true, isVisible: true, iconProps: { iconName: 'Play' }, onClick: (): void => { alert('Action started'); }})
Do not add undeclared variables such as:
className: className,styles: styles,iconProps: iconProps
TypeScript will report that the names cannot be found unless those variables have previously been defined.
Refreshing the Property Pane
When the button changes Web Part properties programmatically, it may be necessary to refresh the Property Pane and render the Web Part again.
For example:
private _resetProperties = (): void => { this.properties.description = ''; this.context.propertyPane.refresh(); this.render();};
The button can then call the method:
PropertyFieldButton('', { key: 'resetButton', text: 'Reset properties', disabled: false, isPrimary: false, isVisible: true, onClick: this._resetProperties})
This pattern is useful for reset buttons and configuration utilities.
Start the SPFx project
Run the project with Heft:
heft start
Open the SharePoint-hosted Workbench and add the Web Part.
Edit the Web Part to open its Property Pane.
The Property Pane will display:
- The standard description text field
- The PnP
PropertyFieldButton - The configured action when the button is clicked
Conclusion
PropertyFieldButton is a simple but useful PnP SPFx Property Pane control.
Its main difference from most Property Pane controls is that it represents an action rather than a stored value. For this reason, the control can use an empty property name and execute its logic through the onClick callback.
The most important implementation details are:
import { PropertyFieldButton} from '@pnp/spfx-property-controls/lib/PropertyFieldButton';
PropertyFieldButton('', { key: 'buttonID', text: 'Button', disabled: false, isPrimary: true, isVisible: true, onClick: (): void => { alert('Button Clicked'); }})
Remember that the configuration is a TypeScript object, so its properties use colons rather than JSX assignment syntax.
Reference
PropertyFieldButton – Official PnP SPFx Property Controls documentation
