Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration!How!?

Shell | Dialogs

All user-facing dialogs in Shiny Shell go through the IDialogs interface. Inject it into your ViewModels via constructor injection.

public class MyViewModel(IDialogs dialogs)
{
// dialogs is ready to use
}

IDialogs is registered as a singleton by UseShinyShell() and dispatches all calls to the UI thread automatically — safe to call from any thread.

The default IDialogs implementation (ShellDialogs) uses MAUI’s built-in Shell dialogs. You can replace it with your own implementation — for example to plug in a third-party dialog library, a custom bottom-sheet, or a test double — using UseDialogs<TDialog>():

builder
.UseMauiApp<App>()
.UseShinyShell(x => x
.AddGeneratedMaps()
.UseDialogs<MyCustomDialogs>()
);
public class MyCustomDialogs : IDialogs
{
public Task Alert(string? title, string message, string acceptText = "OK") { /* ... */ }
public Task<bool> Confirm(string? title, string message, string acceptText = "Yes", string cancelText = "No") { /* ... */ }
public Task<string?> Prompt(string? title, string message, string acceptText = "OK", string cancelText = "Cancel", string? placeholder = null, string initialValue = "", int maxLength = -1, Keyboard? keyboard = null) { /* ... */ }
public Task<string> ActionSheet(string? title, string? cancel, string? destruction, params string[] buttons) { /* ... */ }
}

The default registration uses TryAddSingleton, so calling UseDialogs<>() always wins — regardless of call order.

A pre-built alternative using the owned, animated, themeable dialog service from Shiny.Maui.Controls. The dialog is always rendered by the library — the native platform alert/prompt is never used — so it looks identical across platforms and respects your theme. Install Shiny.Maui.Shell.ShinyDialogs, then configure:

builder
.UseMauiApp<App>()
.UseShinyControls() // registers the Shiny.Maui.Controls IDialogService
.UseShinyShell(x => x
.UseShinyDialogs() // route IDialogs through the Controls dialog service
.AddGeneratedMaps()
)

UseShinyControls() registers the underlying IDialogService; UseShinyDialogs() registers the IDialogs adapter that forwards Shell dialog calls to it. No ViewModel changes needed — same IDialogs interface, different visual presentation.

A pre-built alternative using UXDivers Popups for styled, animated popup dialogs. Install UXDivers.Popups.Maui, add theme dictionaries to App.xaml, then configure:

builder
.UseMauiApp<App>()
.UseShinyShell(x => x
.UseUxDiversDialogs() // Registers IDialogs and initializes the UxDivers popup infrastructure
.AddGeneratedMaps()
)

Add these to your App.xaml merged dictionaries:

<uxd:DarkTheme xmlns:uxd="clr-namespace:UXDivers.Popups.Maui.Controls;assembly=UXDivers.Popups.Maui" />
<uxd:PopupStyles xmlns:uxd="clr-namespace:UXDivers.Popups.Maui.Controls;assembly=UXDivers.Popups.Maui" />

No ViewModel changes needed — same IDialogs interface, different visual presentation.

Display an informational dialog with a single button.

await dialogs.Alert("Error", "Something went wrong");
// With custom button text
await dialogs.Alert("Success", "Item saved successfully", "Got it");
Parameter Type Default Description
title string? Alert title. Pass null to omit.
message string Alert body text (required)
acceptText string "OK" Button text

Display a confirmation dialog and return the user’s choice.

bool confirmed = await dialogs.Confirm(
"Delete Item",
"Are you sure you want to delete this?",
"Delete",
"Cancel"
);
if (confirmed)
{
// proceed with deletion
}
Parameter Type Default Description
title string? Dialog title. Pass null to omit.
message string Dialog body text (required)
acceptText string "Yes" Accept button text
cancelText string "No" Cancel button text

Display a text input dialog. Returns the entered text, or null if the user cancelled.

// Simple prompt
var name = await dialogs.Prompt("Name", "What is your name?");
if (name != null)
{
// user entered a value
}
// With all options
var pin = await dialogs.Prompt(
"Security",
"Enter your PIN",
acceptText: "Submit",
cancelText: "Cancel",
placeholder: "4-digit PIN",
initialValue: "",
maxLength: 4,
keyboard: Keyboard.Numeric
);
Parameter Type Default Description
title string? Dialog title. Pass null to omit.
message string Dialog body text (required)
acceptText string "OK" Accept button text
cancelText string "Cancel" Cancel button text
placeholder string? null Placeholder text shown when the input is empty
initialValue string "" Pre-filled input value
maxLength int -1 Maximum characters allowed (-1 = no limit)
keyboard Keyboard? null Keyboard type (Keyboard.Numeric, Keyboard.Email, etc.)

Display an action sheet with multiple options. Returns the text of the selected button.

var action = await dialogs.ActionSheet(
"Photo Options",
"Cancel",
"Delete Photo",
"Take Photo", "Choose from Library", "Share"
);
switch (action)
{
case "Take Photo":
// open camera
break;
case "Choose from Library":
// open gallery
break;
case "Share":
// share photo
break;
case "Delete Photo":
// destructive action
break;
}
Parameter Type Description
title string? Sheet title. Pass null to omit.
cancel string? Cancel button text. Pass null to omit.
destruction string? Destructive action text (shown in red on some platforms). Pass null to omit.
buttons string[] Action button labels

Use IDialogs with INavigationConfirmation to guard unsaved changes:

public class EditViewModel(INavigator navigator, IDialogs dialogs) :
INavigationConfirmation
{
public bool HasUnsavedChanges { get; set; }
public async Task<bool> CanNavigate()
{
if (!HasUnsavedChanges)
return true;
return await dialogs.Confirm(
"Unsaved Changes",
"You have unsaved changes. Discard them?"
);
}
}
public class ItemViewModel(INavigator navigator, IDialogs dialogs)
{
async Task DeleteItem()
{
if (await dialogs.Confirm("Delete", "This action cannot be undone."))
{
await itemService.Delete(ItemId);
await navigator.GoBack(("Deleted", true));
}
}
}
public class ListViewModel(IDialogs dialogs)
{
async Task RenameItem(Item item)
{
var newName = await dialogs.Prompt(
"Rename",
"Enter a new name",
placeholder: "New name",
initialValue: item.Name,
maxLength: 100
);
if (newName != null)
item.Name = newName;
}
}