Tutorials/Power Apps/Keep Users in the Loop: Building Spinners and Progress Bars in Canvas Apps
Power Appsintermediate

Keep Users in the Loop: Building Spinners and Progress Bars in Canvas Apps

A guide to setting user expectations and improving app reliability through well-placed loading indicators, saving animations, and progress bars. Learn how to prevent double taps and reduce perceived latency.

NA
Narmer Abader
@narmer · Published June 3, 2026

Every second a user spends staring at a static screen feels like an eternity. In Power Apps, data operations rarely happen instantly—especially when dealing with large SharePoint lists, SQL tables, or complex workflows. Without visual feedback, users instinctively tap buttons multiple times, causing duplicate submissions or corrupted data flows.

The solution isn't just faster code (though you should optimize). It is about managing perception. Spinners, saving animations, and progress bars tell the user, "Something is happening. Your action was received." This article walks you through building all three for a sample Warehouse Inventory Audit app.

Scenario Overview: Warehouse Inventory Audit

To keep things concrete, I will reference a simple Warehouse Inventory Audit app throughout this guide.

Key Screens:

  • Scan Station: The user scans a bin barcode to begin an audit.
  • Audit Form: A detail form where the user records item counts and condition notes.
  • Sync Data: An administrative screen that loads configuration data (catalog, team roster, history) before the app becomes usable.

Let us look at how progress indicators improve each of these screens.

Building a Reliable Loading Spinner for Screen Navigations

When a user taps a "Scan Bin" button on the Scan Station screen, the app must look up the bin details before the Audit Form appears. This lookup is nearly instant, but on a slow network the delay is noticeable. Instead of a confusing flash, we display a spinner and block further interactions.

Step 1: Acquire a Spinner Image

Power Apps does not include a default spinner asset. You can supply your own using free tools like Loading.io. Upload the exported SVG or GIF image to your app under App Settings > Resources.

Step 2: The Variable and Overlay Pattern

Place the spinner image in the center of the Scan Station screen. On top of every control on the screen, add a transparent overlay label sized to fill the whole screen. This overlay prevents the user from tapping anything else while the spinner is active.

Set the Visible property of both the overlay and the spinner image to a variable like var_IsLoading.

powerfxVisible property of spinner and overlay
var_IsLoading

Set the overlay label's Fill property to a semi-transparent black to visually dim the screen:

powerfxFill property of overlay label
RGBA(0, 0, 0, 0.2)

Step 3: Trigger the Spinner on Button Click

On the OnSelect of the "Scan Bin" button, show the spinner immediately, perform the data lookup, and then navigate to the destination screen.

powerfxOnSelect of Scan Bin button
// Show the loading state
Set(var_IsLoading, true);

// Perform the data retrieval
Set(var_CurrentBarcode, txt_Scan.Text);
Set(var_CurrentBinLookup, LookUp(WarehouseBins, Barcode = var_CurrentBarcode));

// Navigate to the audit screen
Navigate('AuditForm', ScreenTransition.Fade);

Wait — Why doesn't the spinner hide here?

Placing Set(var_IsLoading, false) right after the Navigate function is unreliable. When Navigate executes, the screen's context is often replaced before the next line runs. Instead, we hide the spinner on the destination screen.

Step 4: Hide the Spinner on the Destination Screen

Using a Timer control on the AuditForm screen is a robust way to clear the loading state.

  • Add a Timer control.
  • Set Duration to 500 (half a second).
  • Set AutoStart to true.
  • Set OnTimerEnd to Set(var_IsLoading, false).

Alternatively, simply place the reset logic in the OnVisible property of the destination screen:

powerfxOnVisible of AuditForm
Set(var_IsLoading, false);

This pattern guarantees the spinner will eventually disappear and does not block the user interface of the new screen.

Crafting a Dependable Saving Spinner

A "Saving..." indicator is critical for forms. Without it, a user might click a Submit button multiple times, flooding your data source with duplicate records.

Setup the Overlay

Reuse the same technique from the loading spinner:

  • A full-screen transparent Label (with a white or transparent fill).
  • A spinner image and a "Saving..." Label.
  • Set all their Visible properties to var_Saving.

Required Form Logic

Modern forms have clear lifecycle events. Use these to reliably control the spinner.

Submit Button (OnSelect):

powerfxOnSelect of Submit button
Set(var_Saving, true);
SubmitForm(frm_AuditForm);

Audit Form (OnSuccess and OnFailure):

powerfxForm events
// OnSuccess
Set(var_Saving, false);
Notify("Audit record saved", NotificationType.Success);

// OnFailure
Set(var_Saving, false);
Notify("Save failed. Please try again.", NotificationType.Error);

The OnFailure handler is crucial. Without it, a failed SubmitForm leaves the spinner spinning forever.

Preventing Double Taps

The true safety net is disabling the Submit button while the save is in progress. Set the DisplayMode property of the Submit button to:

powerfxDisplayMode of Submit button
If(var_Saving, DisplayMode.Disabled, DisplayMode.Edit)

Now the button appears greyed out and unresponsive during the save operation. The user knows exactly what is happening.

Developing an Informative Progress Bar

For long operations, like loading multiple tables on app startup or syncing data, a progress bar provides context that keeps the user from abandoning the app.

The Visual Setup

  • Track: A dark gray Rectangle or Label with height ~20 and width covering most of the screen.
  • Fill: A blue Rectangle (or Label) placed exactly over the track.
  • Status Label: A Label positioned directly above the bar.

Width of the Fill Bar:

powerfxWidth property of the blue fill bar
lbl_Track.Width * var_ProgressWidth

Text of the Status Label:

powerfxText property of the status label
var_LoadingMessage

Sequential Loading with Estimated Progress

When loading tables one after another, you can manually increase a progress variable. This is predictable but often slower.

powerfxOnSelect of Load Data button
// Reset
Set(var_LoadingMessage, "Starting data sync...");
Set(var_ProgressWidth, 0.0);

// Step 1
Set(var_LoadingMessage, "Creating product catalog...");
Set(var_ProgressWidth, 0.25);
ClearCollect(col_Catalog, Products);

// Step 2
Set(var_LoadingMessage, "Syncing auditor roster...");
Set(var_ProgressWidth, 0.50);
ClearCollect(col_Team, Auditors);

// Step 3
Set(var_LoadingMessage, "Loading audit history...");
Set(var_ProgressWidth, 0.75);
ClearCollect(col_History, AuditLog5000);

// Step 4
Set(var_LoadingMessage, "Sync complete! Ready.");
Set(var_ProgressWidth, 1.0);

Concurrent Loading with a Simple Status Message

If you use the Concurrent() function to load multiple tables at once for better performance, tracking the exact progress percentage is difficult. In this case, it is better to use an indefinite spinner combined with a clear status message.

powerfxUsing Concurrent for faster startup
Set(var_ProgressWidth, 0.1);
Set(var_LoadingMessage, "Loading initial data...");

Concurrent(
  ClearCollect(col_Catalog, Products),
  ClearCollect(col_Team, Auditors),
  ClearCollect(col_History, AuditLog5000)
);

Set(var_LoadingMessage, "All data loaded! Ready.");
Set(var_ProgressWidth, 1.0);

Delegation Warning: A progress bar over a non-delegated query is a poor user experience. The spinner will stay visible until the entire data set is downloaded to the device. Always filter your data at the source using delegation-friendly functions like Filter and Search.

Common Mistakes and Troubleshooting

Even experienced builders hit these snags. Here is how to fix them:

1. The Spinner Never Hides

  • Cause: The variable is never set to false. This usually happens in forms when OnSuccess or OnFailure does not fire.
  • Solution: Add a time limit. Wrap your spinner logic in a Timer that automatically sets the variable to false after 30 seconds as a safety net.

2. Transparent Overlay Does Not Block Clicks

  • Cause: A Label control only blocks clicks if its Fill property is set to a color (even a transparent color like RGBA(0,0,0,0)). If the Fill is Transparent, clicks pass through.
  • Solution: Use RGBA(0, 0, 0, 0.2) for the loading overlay so it blocks clicks and dims the screen slightly.

3. Progress Bar Does Not Reset

  • Cause: The var_ProgressWidth variable retains its value from the previous run.
  • Solution: Reset it to 0 in the screen's OnVisible property before the loading sequence starts.
powerfxOnVisible of the loading screen
If(
  var_Role = "Admin",
  Set(var_ProgressWidth, 0);
  Set(var_LoadingMessage, "");
)

4. Saving Spinner Visible on Screen Load

  • Cause: The var_Saving variable is not initialized.
  • Solution: Set var_Saving = false in App.OnStart or the screen's OnVisible.

Final Recommendation

Visual feedback is not a decorative element—it is a core reliability feature. You should integrate these patterns into every project from day one:

  • Use a Spinner for any screen transition that queries a database.
  • Use a Saving Indicator for every SubmitForm call.
  • Use a Progress Bar for lengthy startup sequences or bulk synchronization.

The absence of feedback creates anxious users. An anxious user is a double-clicking user. By systematically implementing these indicators, you make your app feel faster, more professional, and safer.

References