SharePoint Framework (SPFx) Cascading Dropdown – Step-by-Step Tutorial

If you’ve ever built an SPFx web part where users need to pick a Country first and then a State from that country — you already know the problem. Show all 50 states upfront, and you’ll confuse users. That’s exactly where a cascading dropdown saves the day.

In this tutorial, I’ll walk you through building a cascading dropdown in an SPFx web part property pane using two real SharePoint lists — a Country list and a State list. When a user picks a country, the state dropdown automatically loads only the states that belong to that country.

If you are new to SPFx and want to learn SharePoint Framework development, check out the complete SharePoint Framework development training course.

Let me show you exactly how to set this up, step by step.

What Is a Cascading Dropdown?

A cascading dropdown (also called a dependent dropdown) is when the options in one dropdown change based on what you pick in another dropdown.

For example:

  • You pick United States in the Country dropdown
  • The State dropdown immediately updates to show only California, Texas, New York, Florida, etc.
  • If you switch to Canada, the options change to Ontario, British Columbia, Quebec, and so on.

This is a really common pattern in SharePoint web parts, especially for forms or configuration panels.

What We’re Building

Here’s the full scenario:

spfx-cascading-dropdown-react-component

Input: User opens the web part property pane and selects a country from the Country dropdown.

Output: The State dropdown automatically loads and shows only the states that belong to the selected country. The user can then pick a state, and both values are displayed in the web part body.

SharePoint Lists We’ll Use

Before we touch any code, let’s get our two SharePoint lists ready. These are the data source for our dropdowns.

List 1: Country

Create a SharePoint list called Country with a Title column (default).

IDTitle
1United States
2Canada
3United Kingdom
spfx cascading dropdown

List 2: State

Create a SharePoint list called State with two columns:

  • Title — the state name
  • Country — a Lookup column that points to the Country list
IDTitleCountry
1CaliforniaUnited States
2TexasUnited States
3New YorkUnited States
4FloridaUnited States
5OntarioCanada
6British ColumbiaCanada
7QuebecCanada
8EnglandUnited Kingdom
9ScotlandUnited Kingdom
spfx-cascading-dropdown-project-structure

That’s it. No fancy setup needed. Just these two lists with the data above.

Prerequisites

Before we start coding, make sure you have:

  • Node.js (v18.x LTS recommended as of 2025–2026)
  • SharePoint Framework Yeoman generator installed (@microsoft/generator-sharepoint)
  • Heft installed globally
  • Access to a SharePoint Online site where you’ve created the two lists above
  • Basic familiarity with TypeScript and React

If you need to set up your dev environment from scratch, check the official Microsoft docs for SPFx setup — it takes about 15 minutes.

Step 1: Scaffold a New SPFx Project

Open your terminal and run:

md spfx-cascading-dropdown
cd spfx-cascading-dropdown
yo @microsoft/sharepoint

When the Yeoman generator asks you questions, answer like this:

  • Solution name: spfx-cascading-dropdown
  • Type of client-side component: WebPart
  • Web part name: CountryState
  • Template: React
spfx cascading dropdown

Once the scaffolding finishes, open the project in VS Code:

code .

Step 2: Define Web Part Properties

We need two properties to store the user’s selections — one for the selected country and one for the selected state.

2a. Update the manifest

Open src/webparts/countryState/CountryStateWebPartManifest.json and update the properties section:

"properties": {
"selectedCountry": "",
"selectedState": ""
}

2b. Update the props interface

Open src/webparts/countryState/ICountryStateWebPartProps.ts and replace its content with:

export interface ICountryStateWebPartProps {
selectedCountry: string;
selectedState: string;
}

Step 3: Set Up the PnPjs Library

We’ll use PnPjs to query the SharePoint lists. It’s clean, well-maintained, and makes REST calls much simpler.

Install the required packages:

npm install @pnp/sp --save

Step 4: Initialize PnPjs in the Web Part

Open src/webparts/countryState/CountryStateWebPart.ts.

Add the import at the top:

import { spfi, SPFx } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import {
IPropertyPaneConfiguration,
PropertyPaneDropdown,
IPropertyPaneDropdownOption
} from '@microsoft/sp-property-pane';

Then inside the class, add:

private _sp: ReturnType<typeof spfi>;

protected onInit(): Promise<void> {
this._sp = spfi().using(SPFx(this.context));
return super.onInit();
}

This gives us a ready-to-use _sp instance throughout the web part.

Step 5: Declare State Variables

Inside the CountryStateWebPart class, add these private variables right after the class declaration opening brace:

private countries: IPropertyPaneDropdownOption[] = [];
private states: IPropertyPaneDropdownOption[] = [];

private countriesDropdownDisabled: boolean = true;
private statesDropdownDisabled: boolean = true;

private loadingIndicator: boolean = true;

Here’s what each one does:

  • countries — stores the list of country options for the first dropdown
  • states — stores the state options filtered by the selected country
  • countriesDropdownDisabled — keeps the country dropdown locked until data loads
  • statesDropdownDisabled — keeps the state dropdown locked until a country is picked
  • loadingIndicator — shows a spinner in the property pane while data loads

Step 6: Load Countries from SharePoint

Add this method to the class. This calls the Country list and maps the results into IPropertyPaneDropdownOption format:

private async loadCountries(): Promise<IPropertyPaneDropdownOption[]> {
const items = await this._sp.web.lists
.getByTitle("Country")
.items
.select("Id", "Title")();

return items.map((item: { Id: number; Title: string }) => ({
key: item.Title,
text: item.Title
}));
}

What this does:

  • Connects to the Country SharePoint list
  • Fetches the Id and Title columns
  • Returns an array like [{ key: "United States", text: "United States" }, ...]

Step 7: Load States Based on Selected Country

Now add a method to load states. Notice how it filters using a $filter query on the Country lookup column:

private async loadStates(): Promise<IPropertyPaneDropdownOption[]> {
if (!this.properties.selectedCountry) {
return [];
}

const items = await this._sp.web.lists
.getByTitle("State")
.items
.select("Id", "Title", "Country/Title")
.expand("Country")
.filter(`Country/Title eq '${this.properties.selectedCountry}'`)();

return items.map((item: { Id: number; Title: string }) => ({
key: item.Title,
text: item.Title
}));
}

What this does:

  • Checks if a country has been selected — returns empty if not
  • Uses OData $filter to only fetch states where Country/Title matches the selected country
  • Expands the Country lookup column to read its Title
  • Returns mapped dropdown options

So if the user selects United States, this method queries: Country/Title eq 'United States' and returns California, Texas, New York, and Florida.

Step 8: Trigger Loading When Property Pane Opens

Override the onPropertyPaneConfigurationStart() method. This runs every time the user opens the property pane:

protected async onPropertyPaneConfigurationStart(): Promise<void> {
this.countriesDropdownDisabled = !this.countries.length;
this.statesDropdownDisabled = !this.properties.selectedCountry || !this.states.length;

if (this.countries.length) {
return;
}

this.loadingIndicator = true;
this.context.propertyPane.refresh();

// Load countries
this.countries = await this.loadCountries();
this.countriesDropdownDisabled = false;

// If a country was previously selected, load its states too
if (this.properties.selectedCountry) {
this.states = await this.loadStates();
this.statesDropdownDisabled = false;
}

this.loadingIndicator = false;
this.context.propertyPane.refresh();
}

This method makes sure:

  • Countries only load once (not on every open)
  • If the user previously selected a country, the states for it are pre-loaded
  • The spinner shows and hides correctly

Step 9: React When Country Changes

This is the key part of the cascading behavior. Override onPropertyPaneFieldChanged():

protected async onPropertyPaneFieldChanged(
propertyPath: string,
oldValue: string,
newValue: string
): Promise<void> {
if (propertyPath === 'selectedCountry' && newValue) {
// Show loading spinner
this.loadingIndicator = true;

// Save the new country value
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);

// Reset state selection
this.properties.selectedState = '';

// Disable state dropdown while loading
this.statesDropdownDisabled = true;
this.context.propertyPane.refresh();

// Load states for the new country
this.states = await this.loadStates();

// Enable state dropdown
this.statesDropdownDisabled = false;

// Hide loading spinner
this.loadingIndicator = false;

this.context.propertyPane.refresh();
} else {
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
}
}

Walk through what happens here:

  1. User picks United States in the Country dropdown
  2. The state dropdown gets disabled and the spinner appears
  3. loadStates() runs and fetches California, Texas, New York, Florida
  4. The state dropdown gets enabled with the new options
  5. Spinner disappears — user picks their state

Step 10: Wire Up the Property Pane UI

Update getPropertyPaneConfiguration() to render both dropdowns:

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
showLoadingIndicator: this.loadingIndicator,
pages: [
{
header: {
description: "Configure the web part"
},
groups: [
{
groupName: "Location Settings",
groupFields: [
PropertyPaneDropdown('selectedCountry', {
label: "Select Country",
options: this.countries,
disabled: this.countriesDropdownDisabled
}),
PropertyPaneDropdown('selectedState', {
label: "Select State",
options: this.states,
disabled: this.statesDropdownDisabled,
selectedKey: this.properties.selectedState
})
]
}
]
}
]
};
}

The selectedKey: this.properties.selectedState binding on the second dropdown is important. Without it, the state dropdown won’t visually reset when you switch countries.

Step 11: Display the Selection in the Web Part Body

Open src/webparts/countryState/components/CountryState.tsx and update the render() method:

public render(): React.ReactElement<ICountryStateProps> {
const { selectedCountry, selectedState } = this.props;

return (
<div>
<h2>Location Details</h2>
<p>
<strong>Country:</strong> {selectedCountry || "No country selected"}
</p>
<p>
<strong>State:</strong> {selectedState || "No state selected"}
</p>
</div>
);
}

Also make sure your ICountryStateProps.ts interface includes both properties:

export interface ICountryStateProps {
selectedCountry: string;
selectedState: string;
// ... other default props from scaffold
}

And update the render() method in CountryStateWebPart.ts to pass them:

public render(): void {
const element = React.createElement(CountryState, {
selectedCountry: this.properties.selectedCountry,
selectedState: this.properties.selectedState,
// ... other props
});

ReactDom.render(element, this.domElement);
}

Step 12: Run and Test

Start the local workbench:

Heft start --clean

Open your SharePoint Online site’s workbench:

https://yourtenant.sharepoint.com/sites/yoursite/_layouts/15/workbench.aspx

Add the CountryState web part to the page and open its property pane. Here’s what you should see:

On first open:

  • A loading spinner appears briefly
  • The Country dropdown populates with United States, Canada, United Kingdom
  • The State dropdown is greyed out (disabled)

After selecting United States:

  • The State dropdown briefly shows a spinner
  • It loads: California, Texas, New York, Florida
  • You pick California

Web part body now shows:

Country: United States
State: California

If you switch to Canada:

  • The State dropdown resets and reloads
  • New options: Ontario, British Columbia, Quebec
  • Previous selection (California) is cleared automatically

That’s the full cascading behavior working end to end.

Complete File Summary

Here’s a quick reference for which files you touched and why:

FileWhat You Changed
CountryStateWebPartManifest.jsonAdded selectedCountry and selectedState properties
ICountryStateWebPartProps.tsDefined TypeScript interface for both properties
CountryStateWebPart.tsAdded PnPjs, all loading methods, property pane config
ICountryStateProps.tsAdded both props to the React component interface
CountryState.tsxUpdated render to display country and state values

Common Mistakes to Avoid

  • Not calling this.context.propertyPane.refresh() after updating state variables — the UI won’t update without it.
  • Forgetting selectedKey on the State dropdown — the dropdown won’t visually reset when the country changes.
  • Using undefined instead of '' to reset selectedState — only an empty string triggers a proper UI reset in PropertyPaneDropdown.
  • Not expanding the lookup column — if you query the State list without .expand("Country"), the Country/Title filter won’t work.
  • Calling loadCountries() every time the property pane opens — always check if data is already loaded before making another network call.

What’s Next?

Once you have this working, here are some natural extensions:

  • Add a third level — a City dropdown that cascades from the selected state
  • Replace the property pane approach with an inline form inside the web part body using React state
  • Use PnP React Controls (PropertyFieldListPicker and PropertyFieldMultiSelect) for even more built-in power
  • Add error handling around the PnPjs calls so users see a friendly message if the list fetch fails

Conclusion

In this tutorial, I explained how to implement a cascading dropdown in a SharePoint Framework web part.

Also, you may like:

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

SharePoint Permission Checker Agent using Microsoft Copilot Studio

Learn how to build a SharePoint Permission Checker Agent using Copilot Studio. Using this agent, you can check library-level and file-level permissions. Also, identify Users, SharePoint Groups, and Permission Levels.

📅 1st July 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