Bit.Butil
10.6.0-pre-02
See the version list below for details.
dotnet add package Bit.Butil --version 10.6.0-pre-02
NuGet\Install-Package Bit.Butil -Version 10.6.0-pre-02
<PackageReference Include="Bit.Butil" Version="10.6.0-pre-02" />
<PackageVersion Include="Bit.Butil" Version="10.6.0-pre-02" />
<PackageReference Include="Bit.Butil" />
paket add Bit.Butil --version 10.6.0-pre-02
#r "nuget: Bit.Butil, 10.6.0-pre-02"
#:package Bit.Butil@10.6.0-pre-02
#addin nuget:?package=Bit.Butil&version=10.6.0-pre-02&prerelease
#tool nuget:?package=Bit.Butil&version=10.6.0-pre-02&prerelease
bit Butil
The browser platform, in C#. Butil wraps the Web APIs a Blazor app actually needs - the DOM,
storage, media, sensors, crypto, workers - as injectable, strongly-typed, XML-documented services,
so you can stop writing IJSRuntime.InvokeVoidAsync("someGlobal.someFunction", ...) and start
writing C#.
Works on Blazor WebAssembly, Blazor Server, Blazor Hybrid and under prerendering, on .NET 8, 9 and 10.
Getting started
Install the package:
dotnet add package Bit.Butil
Add its script tag to your host page, before the Blazor script so window.BitButil exists by
the time the app boots:
<script src="_content/Bit.Butil/bit-butil.js"></script>
<script src="_framework/blazor.web.js"></script>
Register the services:
using Bit.Butil;
builder.Services.AddBitButilServices();
Then inject whatever you need:
@inject Bit.Butil.Window window
@inject Bit.Butil.LocalStorage localStorage
@inject Bit.Butil.Clipboard clipboard
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender is false) return;
await localStorage.SetItem("last-visit", DateTime.UtcNow.ToString("O"));
await clipboard.WriteText("copied from C#");
await window.AddEventListener(ButilEvents.KeyDown, args => { /* ... */ });
}
}
What's in the box
Every wrapper below is an injectable service in the Bit.Butil namespace. They are registered as
scoped, which matches Blazor's one-circuit-or-one-WASM-app-per-user model.
Window & browsing
| Service | What it wraps |
|---|---|
Window |
The DOM window: events, dialogs, sizes, matchMedia, scrolling, printing, selection |
Document |
The DOM document: title, cookies, fullscreen, visibility, design mode, pointer lock |
History |
Session history: back/forward, pushState/replaceState, popstate |
Navigation |
The Navigation API: read the history entry list, traverse to a key, and know whether you can go back |
Location |
Read and mutate the current URL, reload, navigate |
Navigator |
Identity, languages, share, vibrate, badges, sendBeacon, device memory |
UserAgent |
Parsed user-agent brands, platform and mobile-ness (UA Client Hints) |
Screen & diagnostics
| Service | What it wraps |
|---|---|
Screen |
Physical screen metrics, colour depth, availability |
ScreenOrientation |
Read, lock and observe the screen orientation |
VisualViewport |
The visual viewport: scale, offsets, resize and scroll events |
Performance |
High-resolution timing, marks, measures, PerformanceObserver |
Reporting |
ReportingObserver: deprecation, intervention and crash reports |
Console |
The full browser console API: log, table, group, time, count, profile |
DOM & interaction
| Service | What it wraps |
|---|---|
ElementReference extensions |
Attributes, scrolling, fullscreen, pointer capture, per-element events |
| Animation extensions | The Web Animations API on any element |
Keyboard |
App-wide keyboard shortcuts with modifier support |
IntersectionObserver |
Element visibility inside the viewport or a scroll container |
MutationObserver |
DOM tree, attribute and character-data mutations |
ResizeObserver |
Element size changes with box-model detail |
PictureInPicture |
Float a <video> in an always-on-top window |
ViewTransition |
Animate between two states of the page, the browser doing the work |
| Media element extensions | Play, pause, seek, volume and rate on any <audio>/<video> |
Storage
| Service | What it wraps |
|---|---|
LocalStorage / SessionStorage |
Synchronous key/value storage, per origin or per tab |
Cookie |
Read, set and remove document cookies with full options |
CookieStore |
The async Cookie Store API, with change events |
IndexedDb |
Structured, transactional client-side database |
CacheStorage |
The service-worker Cache API |
StorageManager |
Quota, usage estimates and persistence |
StorageAccess |
Ask for unpartitioned storage from inside a third-party iframe |
Files & data
| Service | What it wraps |
|---|---|
FileReader |
Read user-selected files as text, data URLs or bytes |
FileSystem |
The File System Access API: pick real files/folders and write back to them |
ObjectUrls |
Create and revoke blob: object URLs from C# data |
Clipboard |
Read and write text and typed items on the system clipboard |
Crypto |
SubtleCrypto: encryption, decryption, hashing, key generation, random values |
Fetch |
The fetch API with full request/response control and progress |
Compression |
Gzip and deflate through the browser's native codec |
Network & workers
| Service | What it wraps |
|---|---|
ServiceWorker |
Register and inspect service workers, and message them |
BackgroundSync |
Defer work until the user has connectivity (one-shot and periodic) |
Push |
Web push subscriptions |
NetworkInformation |
Connection type, speed, save-data and change events |
BroadcastChannel |
Message other tabs and windows of the same origin |
WebLocks |
Cross-tab cooperative resource locking |
EventSource |
Server-sent events, with reconnection built into the browser |
Device & hardware
| Service | What it wraps |
|---|---|
Battery |
Battery level, charging state and related events |
Geolocation |
Current position, watched positions and errors |
MediaDevices |
Cameras and microphones, plus screen capture (getDisplayMedia) |
Gamepad |
Game controllers: buttons, sticks, triggers and rumble |
DeviceOrientation |
Tilt, acceleration and rotation from the device's own sensors |
Nfc |
Read and write NDEF messages on NFC tags |
WakeLock |
Keep the screen awake, with an auto-reacquiring persistent mode |
IdleDetector |
User and screen idle-state changes |
ContactPicker |
Let users pick contacts to share with your app |
EyeDropper |
Sample any pixel colour on the screen |
BarcodeDetector |
Find QR codes and barcodes in a camera frame or an image |
Identity & permissions
| Service | What it wraps |
|---|---|
WebAuthn |
Passkeys: create credentials and verify assertions |
Permissions |
Query the state of any browser permission |
Notification |
Request permission and show system notifications |
Media & speech
| Service | What it wraps |
|---|---|
SpeechSynthesis |
Text-to-speech with voices, pitch and rate |
SpeechRecognition |
Speech-to-text with interim results and events |
WebAudio |
Play and control audio buffers |
MediaRecorder |
Record a camera, microphone or screen share to a file |
MediaSession |
Lock-screen metadata and hardware media-key handlers |
The patterns worth knowing
Prerendering is safe by default
During static SSR / prerender there is no JS runtime. Rather than throwing, reads return a safe
default - "" for strings, [] for arrays, default(T) for everything else - and void calls are
no-ops. That means a read in OnInitializedAsync won't crash your prerender pass.
The trade-off: a false from IsSupported() during prerender is indistinguishable from a genuine
false. If you branch on a result, do the read in OnAfterRenderAsync instead:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender is false) return;
if (await wakeLock.IsSupported())
{
await wakeLock.Request();
}
}
Subscriptions are disposable
Anything that attaches a listener returns a ButilSubscription. Dispose it to detach:
private ButilSubscription? _subscription;
_subscription = await resizeObserver.Observe(_element, entries => { /* ... */ });
// later - idempotent, and safe during teardown:
await _subscription.DisposeAsync();
If you forget, the owning service detaches everything it registered when its scope is torn down. That's a safety net, not a plan.
Handles own hardware
MediaStreamHandle, MediaRecordingHandle, WakeLock's persistent handle and the File System
handles all represent something the browser is holding open. Dispose them:
await using var stream = await mediaDevices.GetUserMedia(audio: false, video: true);
await stream!.AttachTo(_videoElement);
// the camera light goes out when the handle is disposed
Gestures and secure contexts
Many APIs only work from inside a user-gesture handler (a click), or only over HTTPS. Butil doesn't
hide that - each method's XML docs say which preconditions apply, and calls that the browser
refuses come back as false/null rather than as exceptions where dismissal is a normal outcome.
Optional fast invoke
On Blazor WebAssembly, the handful of APIs backed by genuinely synchronous JS functions -
LocalStorage, SessionStorage, Cookie, Console, Location - can skip the async marshalling:
BitButil.UseFastInvoke();
Everything wrapping a Promise-returning API keeps running asynchronously regardless, so this can't break those calls. On Blazor Server it's a no-op.
Trimming and AOT
The package is marked IsTrimmable. Types crossing the interop boundary carry
[DynamicDependency] annotations, so trimming a published WASM app keeps what the serializer
needs. The public FastInvoke* extensions are annotated [RequiresUnreferencedCode] so a trimming
consumer gets the warning at their own call site.
Samples and docs
The Bit.Butil.Demo project in this repository is a full documentation site: one page per API,
with runnable samples, an API reference table, and a browser-support matrix. Run it to try any of
the above in your own browser.
Every public member carries XML documentation with a link to the corresponding MDN page, so IntelliSense is the reference of record.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.0)
-
net8.0
- Microsoft.AspNetCore.Components.Web (>= 8.0.0)
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.6.0-pre-03 | 0 | 8/25/2026 |
| 10.6.0-pre-02 | 458 | 8/14/2026 |
| 10.6.0-pre-01 | 509 | 8/4/2026 |
| 10.5.0 | 1,230 | 7/20/2026 |
| 10.5.0-pre-10 | 159 | 7/20/2026 |
| 10.5.0-pre-09 | 263 | 7/19/2026 |
| 10.5.0-pre-08 | 478 | 7/11/2026 |
| 10.5.0-pre-07 | 226 | 7/10/2026 |
| 10.5.0-pre-06 | 247 | 7/7/2026 |
| 10.5.0-pre-05 | 581 | 6/28/2026 |
| 10.5.0-pre-04 | 575 | 6/22/2026 |
| 10.5.0-pre-02 | 714 | 6/18/2026 |
| 10.5.0-pre-01 | 226 | 6/14/2026 |
| 10.4.5 | 2,087 | 5/28/2026 |
| 10.4.5-pre-02 | 144 | 5/27/2026 |
| 10.4.5-pre-01 | 119 | 5/26/2026 |
| 10.4.4 | 495 | 5/16/2026 |
| 10.4.4-pre-04 | 176 | 5/13/2026 |
| 10.4.4-pre-03 | 293 | 5/10/2026 |
| 10.4.4-pre-02 | 449 | 4/15/2026 |