Browser support
Butil runs anywhere Blazor runs, but it can only expose what the browser underneath it implements. This is the whole catalogue in one table: how widely each API is implemented, what it demands of the calling page - and, if you press the button, what the browser you are reading this in actually has.
72 of the 136 classes here expose IsSupported(), a cheap feature detection that asks the runtime whether the underlying API object exists. Pressing the button runs all of them and fills the last column of the table below. This is exactly the check you should be making in your own app before offering a feature - never a user-agent string.
if (await wakeLock.IsSupported())
{
await wakeLock.Request();
}
else
{
// offer the experience without it, rather than failing
}# The matrix
136 APIsFilter by name, or by what an API demands of the page. "All engines" means every current Chromium, Firefox and WebKit release implements it; "varies by engine" means it is there everywhere but some members or behaviours are not.
Powerful APIs are only exposed to pages served over HTTPS - or from localhost, which browsers treat as trusted so that development works. On an insecure origin the API object is simply absent, so IsSupported() returns false and there is nothing to catch. Window.IsSecureContext tells you which side of that line the page is on.
if (await window.IsSecureContext() is false)
{
// Clipboard, Crypto, Geolocation, ServiceWorker, WebAuthn and friends
// do not exist here. Do not offer them.
}A permission-gated API shows the user a prompt the first time it is used, and the answer sticks for the origin. Query the current state before prompting so you can explain what you are about to ask for - a prompt that arrives out of nowhere is the one users deny. A denial is permanent until the user clears it in browser settings, so treat 'denied' as a stable state and not a retry opportunity.
var state = await permissions.Query("geolocation");
if (state == PermissionState.Denied)
{
// Do not call the API - it will fail without re-prompting.
// Explain how to re-enable it in site settings instead.
}Opening a picker, entering fullscreen, playing audio, requesting a permission, writing to the clipboard - all of these require the call to originate from a real user interaction. The window is short and does not survive an arbitrary await, so do the gesture-bound call first and the slow work afterwards, not the other way around.
private async Task OnClick()
{
// First, while the gesture is still live:
var color = await eyeDropper.Open();
// Then everything else.
await SaveToServer(color);
}A handful of the APIs here are still being designed. They ship in one engine, sometimes behind a flag or an origin trial, and their shape can change. Butil wraps them because they are genuinely useful where they exist - but build them as enhancements you can withdraw, never as the only path through a flow.
// The pattern for anything on this list: detect, use, degrade.
_canPickContacts = await contactPicker.IsSupported();IsSupported() asks the runtime the actual
question and costs one interop call.