Install prompt
Show a custom "Install app" button via IInstallPrompt instead of the browser's default mini-infobar. The framework captures and defers the beforeinstallprompt event at boot, so you reveal your button when CanInstallAsync() is true and trigger PromptAsync() from the click. WASM-only — the install flow needs the live document and transient activation.
CanInstallAsync()/IsInstalledAsync() are one-shot polls; PromptAsync() replays the deferred event and returns the user's InstallOutcome. The browser only offers it over HTTPS with a valid manifest + service worker, once per load.
InstallPromptDemo.cs
using Rask.Site;
using Rask.Wasm.Browser;
namespace Rask.Site.Features;
/// <summary>
/// <see cref="IInstallPrompt" /> — show a custom "Install app" button driven by the deferred
/// <c>beforeinstallprompt</c> event, then trigger the prompt from the click. The button only appears
/// once the browser deems the app installable (valid manifest + service worker over HTTPS) and it
/// isn't already installed.
/// </summary>
public sealed partial class InstallPromptDemo(IInstallPrompt install) : Component
{
private bool _canInstall;
private bool _installed;
private string _status = "checking…";
protected override async Task OnRenderedAsync(bool firstRender)
{
if (!firstRender)
{
return;
}
await RefreshAsync();
}
private async Task RefreshAsync()
{
try
{
_installed = await install.IsInstalledAsync();
_canInstall = !_installed && await install.CanInstallAsync();
_status = _installed
? "running as an installed app"
: _canInstall
? "installable — use the button"
: "not installable yet (needs HTTPS + manifest + service worker, fired once per load)";
}
catch (Exception ex)
{
_status = "check failed: " + ex.Message;
}
StateHasChanged();
}
private async Task Install()
{
try
{
var outcome = await install.PromptAsync();
_status = $"prompt outcome: {outcome}";
await RefreshAsync();
}
catch (Exception ex)
{
_status = "prompt failed: " + ex.Message;
}
}
protected override Component? Render() =>
Div.Class($"{Tw.Card} shadow-sm border-0")[
Div.Class(Tw.CardBody)[
Div.Class("flex gap-2 flex-wrap mb-2")[
Button
.Class(Tw.BtnPrimary)
.Id("install-button")
.Disabled(!_canInstall)
.OnClickAsync(Install)[
UiIcon.Name(UiIconName.Download).Class("me-1"), "Install app"],
Button
.Class(Tw.BtnOutlineSecondary)
.Id("install-refresh")
.OnClickAsync(RefreshAsync)[
"Re-check"]
],
Div.Class("text-sm text-ui-muted")["Status: ", Code.Id("install-status")[_status]]
]
];
}
Live result
Status:
checking…