Plugin.Maui.LoadingHud is a tiny native loading overlay for .NET MAUI. Lottie, image or spinner, configured once and overridable per screen.
Every app needs a loading indicator. It sounds like the simplest part of a project.
Then you build one in .NET MAUI, and the problems start.
The problem
Most MAUI loaders are built on pages, popups or modal navigation. That works for a demo, but in a real app it causes trouble:
- 😵 Showing the loader fires
OnAppearing/OnDisappearingon your pages - 🔒 Calling
Show()inside anotherShow()can leave the loader stuck on screen - 🪟 Two loaders can overlap
- 🔁 You repeat the same colors, text and animation on every screen
I hit all of these in a production app, so I stopped patching and built a loader that doesn't go through MAUI navigation at all.
The solution: Plugin.Maui.LoadingHud ✨
It's a native overlay (an Android Dialog and an iOS UIWindow). Your pages never know it's there, so no lifecycle events fire.
using (LoadingHud.Show("Loading...")) await api.GetDataAsync(); When the using block ends, the loader closes, even if your code throws.
Set it up once
Add your defaults in MauiProgram.cs. Every option is optional.
builder.UseLoadingHud(o => { o.Text = "Please wait..."; // default text everywhere o.Lottie = "loader.json"; // Resources/Raw o.Image = "logo.png"; // Resources/Images o.IndicatorColor = Colors.White; o.BackgroundColor = Color.FromArgb("#80000000"); o.OverlayColor = Color.FromArgb("#66000000"); o.TextColor = Colors.White; o.IndicatorSize = 40; o.LottieSize = 100; o.ImageSize = 60; o.Spacing = 8; // gap between loader and text }); Set nothing and you get a clean white spinner.
Change it for one screen
using (LoadingHud.Show("Uploading...", lottie: "upload.json")) { ... } using (LoadingHud.Show(image: "logo.png")) { ... } using (LoadingHud.Show("")) { ... } // no text this time The signature is simple:
IDisposable Show(string? text = null, string? lottie = null, string? image = null) How it decides what to show
| Priority | Loader |
|---|---|
| 1 | lottie passed to Show() |
| 2 | image passed to Show() |
| 3 | Lottie from MauiProgram |
| 4 | Image from MauiProgram |
| 5 | Activity indicator (spinner) |
Text works the same way: the text you pass wins, otherwise the default from MauiProgram. If a Lottie or image file can't be found, it quietly falls back to the spinner instead of crashing.
How it works under the hood
- Native overlay: Android uses a
Dialogand iOS uses aUIWindowabove your app. MAUI navigation is never involved. - Reference counting: each
Show()adds one, eachDispose()removes one, and the loader closes when the count reaches zero. Nested calls just work. -
Hide()as a safety net: it closes everything at once, and tokens from before it are ignored. - Memory: native views are disposed on hide. On iOS only the last Lottie stays cached, and Android uses Lottie's own cache.
- Threading: call
Show()/Hide()from any thread. The UI work is marshalled to the main thread.
Quick example
private async void Save_Clicked(object sender, EventArgs e) { using (LoadingHud.Show("Saving...")) { await SaveAsync(); using (LoadingHud.Show("Syncing...")) // nested: no clash await SyncAsync(); } } Get it
📦 NuGet: https://www.nuget.org/packages/Plugin.Maui.LoadingHud
💻 GitHub: https://github.com/PrathamsGithub/Plugin.Maui.LoadingHud
dotnet add package Plugin.Maui.LoadingHud Android and iOS are supported. Other targets compile but show nothing.
Feedback welcome
Found a bug on a device I didn't test, or want another option? Open an issue or send a PR on GitHub. If it saves you time, a ⭐ helps others find it. 🙌




