BrowserApi.JSInterop 0.1.0-preview.9

This is a prerelease version of BrowserApi.JSInterop.
dotnet add package BrowserApi.JSInterop --version 0.1.0-preview.9
                    
NuGet\Install-Package BrowserApi.JSInterop -Version 0.1.0-preview.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BrowserApi.JSInterop" Version="0.1.0-preview.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BrowserApi.JSInterop" Version="0.1.0-preview.9" />
                    
Directory.Packages.props
<PackageReference Include="BrowserApi.JSInterop" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BrowserApi.JSInterop --version 0.1.0-preview.9
                    
#r "nuget: BrowserApi.JSInterop, 0.1.0-preview.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BrowserApi.JSInterop@0.1.0-preview.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BrowserApi.JSInterop&version=0.1.0-preview.9&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=BrowserApi.JSInterop&version=0.1.0-preview.9&prerelease
                    
Install as a Cake Tool

BrowserApi

Typed C# wrappers for every browser API — generated from W3C/WHATWG specs.

CI codecov NuGet NuGet NuGet .NET 10 License: MIT Tests GitHub stars Last commit API Docs

Turn magic strings and untyped IJSRuntime calls into compile-time-checked, IntelliSense-rich C# code — without writing a single line of JavaScript.

Performance: understand the interop cost before using this library.

Every property access and method call on a BrowserApi type is a JavaScript interop call under the hood. Each call has overhead from JSON serialization and the WASM/JS boundary crossing. In Blazor Server, each call is also a network roundtrip over SignalR — meaning latency depends on your connection (typically 10–100ms per call).

What this means in practice:

  • Setting 10 element properties = 10 interop calls. This is fine for occasional DOM updates, but not for tight loops or animation frames.
  • Synchronous calls (IJSInProcessRuntime) are ~4x faster than async calls in WASM, but unavailable on Server.
  • Microsoft's official guidance: "Avoid excessively fine-grained calls" and "roll individual JS interop calls into a single call."

BrowserApi provides two solutions for high-performance scenarios:

  1. JsBatch — batches N void operations (property sets, method calls) into a single interop call.
  2. QueryValuesAsync / QueryElementsAsync — bulk-reads DOM data in one call, then use LINQ in pure C# (zero interop), then write back with JsBatch (one call). Total: 2 interop calls instead of N+N.
  3. BrowserApi.SourceGen — Roslyn source generator that reads your .ts, .d.ts, or .js modules and emits typed C# classes with DI injection, path resolver support, and enum serialization. Same InvokeAsync cost as hand-written interop, but with compile-time safety and IntelliSense.

Bottom line: Use the typed API freely for event handlers, user interactions, and occasional DOM reads/writes. For bulk operations, use batching. For performance-critical rendering (Canvas animations, real-time updates), consider keeping that logic in a JS module and calling it via BrowserApi.SourceGen.


The Problem

Every Blazor developer writes code like this:

// Untyped, fragile, no IntelliSense
await js.InvokeVoidAsync("eval", "document.getElementById('hero').style.display = 'flx'");
// "flx" is a typo — but it compiles and silently does nothing.

The Solution

// Typed, safe, full IntelliSense
var hero = Document.QuerySelector<HtmlDivElement>("#hero")!;
hero.Style.Display = Display.Flex;    // enum — typo is a compile error
hero.Style.Gap = Length.Rem(1.5);     // strongly-typed CSS value
hero.FadeIn(500);                     // Web Animations API

2,693 types generated directly from the same WebIDL specs that browsers implement against. Every property, method, and event — typed.


Quick Start

@inherits BrowserApiComponentBase

<h1>@_title</h1>

@code {
    private string _title = "Loading...";

    protected override async Task OnBrowserApiReadyAsync() {
        // DOM
        _title = Document.Title;
        var input = Document.QuerySelector<HtmlInputElement>("#email")!;

        // Events — typed, disposable
        input.OnInput(e => _title = input.Value);

        // Fetch
        var users = await Http.GetAsync<List<User>>("/api/users");

        // Canvas
        var ctx = Document.QuerySelector<HtmlCanvasElement>("canvas")!.GetContext2D();
        ctx.SetFill(CssColor.Red).FillRect(0, 0, 200, 100);
        ctx.Path().MoveTo(10, 10).LineTo(190, 90).Stroke();

        // Storage
        var storage = Window.TypedLocalStorage();
        storage.Set("users", users);

        // Animations
        input.FadeIn(500);

        StateHasChanged();
    }
}

Setup (two lines):

// Program.cs
builder.Services.AddBrowserApi();

<script src="_content/BrowserApi.JSInterop/browserapi.js"></script>

Packages

Package Dependencies What it does
BrowserApi None 2,693 generated types: DOM, CSS, Canvas, Fetch, Storage, Events, Animations. Pure C#.
BrowserApi.JSInterop Microsoft.JSInterop IJSRuntime bridge — connects types to a live browser.
BrowserApi.Blazor ASP.NET Components AddBrowserApi() DI, BrowserApiComponentBase, lifecycle hooks.
BrowserApi.Runtime Jint Server-side JS execution — test DOM interactions without a browser.
BrowserApi.SourceGen (build-time only) Roslyn source generator: .ts/.d.ts/.js → typed C# module classes with DI, path resolver, and enum serialization. Guide
BrowserApi.Css.SourceGen (build-time only) Roslyn source generator + analyzers for the CSS-in-C# authoring API: pre-populates class/variable/keyframe names, generates typed Assets.* from wwwroot/, parses third-party CSS into Mud.*-style typed surfaces, and ships BCA001/BCA002/BCA003 diagnostics. Analyzer-only — ships nothing at runtime. Guide

Features

Typed DOM — no casts, no magic strings

var input = Document.QuerySelector<HtmlInputElement>("#email")!;
var div = Document.CreateElement<HtmlDivElement>();
div.TextContent = "Created from C#";
Document.QuerySelector<Element>("body")!.AppendChild(div);

Typed Events — not addEventListener("clck", ...)

using var sub = button.OnClick(e => {
    Console.WriteLine($"Clicked at ({e.ClientX}, {e.ClientY})");
});

input.OnKeyDown(e => {
    if (e.Key == "Enter") Submit();
});

CSS Value Types — not "1.5rem" strings

element.Style.Margin = 1.5.Rem;
element.Style.Color = CssColor.Hsl(220, 90, 56);
element.Style.Transform = Transform.Rotate(45.Deg).Scale(1.2);
element.Style.Transition = Transition.All(300.Ms, Easing.EaseInOut);

CSS-in-C# Authoring — not separate .css files

public class AppStyles : StyleSheet {
    public static readonly CssVar<CssColor> Primary = new(CssColor.Hex("#0066cc"));
    public static readonly Class Btn = new() {
        Display      = Display.InlineFlex,
        Padding      = (8.Px, 16.Px),
        Background   = Primary,
        BorderRadius = 8.Px,
        [Self.Hover] = new() { Background = ((CssColor)Primary).Darken(8) },
    };
}
@* App.razor — emits combined CSS for every StyleSheet in the AppDomain *@
<HeadContent><BrowserApiCss /></HeadContent>

@* anywhere *@
<button class="@AppStyles.Btn">Click me</button>

Typed selectors, typed values, BEM modifiers via .Variant, :hover/ :focus/etc. as nested blocks, full @media/@supports/@container/ @keyframes support, design-token variables via CssVar<T>. See the CSS-in-C# guide.

Fluent Fetch — not IJSRuntime.InvokeAsync("fetch", ...)

var user = await Http.GetAsync<User>("/api/users/42");

var created = await Http.Post("/api/users")
    .WithJsonBody(new { Name = "Alice" })
    .WithHeader("Authorization", "Bearer token")
    .SendJsonAsync<User>();

// Non-throwing pattern
var result = await Http.Get("/api/data").TrySendAsync<Data>();
if (result.IsSuccess) Use(result.Value!);

Canvas 2D — fluent paths, typed fills, save/restore scoping

var ctx = canvas.GetContext2D();

using (ctx.SaveState()) {
    ctx.SetFill(CssColor.Rgb(255, 100, 0))
       .SetShadow(CssColor.Black, blur: 10, offsetX: 3, offsetY: 3);

    ctx.Path()
       .MoveTo(50, 50).LineTo(200, 50).LineTo(125, 150)
       .ClosePath().Fill();
}

var gradient = ctx.LinearGradient(0, 0, 300, 0)
    .AddStop(0, CssColor.Red)
    .AddStop(1, CssColor.Blue)
    .Build();

ctx.Font = CanvasFont.Of(24, "Inter").Bold();

Web Animations — not element.animate({...}, {...})

element.FadeIn(500);
element.SlideIn(300, "right");

element.Animate(
    new KeyframeBuilder()
        .AddFrame(new { transform = "rotate(0deg)" })
        .AddFrame(new { transform = "rotate(360deg)" }),
    new AnimationOptionsBuilder()
        .Duration(1000)
        .Easing(Easing.EaseInOutCubic)
        .Iterations(double.PositiveInfinity));

Performance — batching & bulk queries

// Write: N operations → 1 interop call
await JsBatch.RunAsync(batch => {
    batch.SetProperty(el1, "textContent", "hello");
    batch.SetProperty(el2, "className", "active");
    batch.InvokeVoid(ctx, "fillRect", 0, 0, 100, 100);
});

// Read: fetch all data in 1 call → LINQ in C# → batch write back
var texts = await document.QueryValuesAsync<string>("li", "textContent");
var sorted = texts.Where(t => t.Length > 3).OrderBy(t => t).ToList();

Server-Side Testing — no browser needed

var engine = new BrowserEngine();  // Jint + virtual DOM

engine.Execute(@"
    var card = document.createElement('div');
    card.className = 'card';
    card.style.display = 'flex';
    document.body.appendChild(card);
");

var card = engine.VirtualDocument.QuerySelector(".card");
Assert.Equal("flex", card!.Style["display"]);
Assert.Contains("card", engine.VirtualDocument.Body.OuterHtml);

Architecture

Your C# code
    │
    ▼
┌─────────────────────────────────────────────┐
│  BrowserApi  (zero dependencies)            │
│  2,693 types: DOM, CSS, Canvas, Fetch, ...  │
│  Generated from W3C WebIDL + CSS specs      │
└───────────────┬─────────────┬───────────────┘
                │             │
    ┌───────────▼──┐   ┌──────▼────────────┐
    │  JSInterop   │   │  Runtime (Jint)   │
    │  Backend     │   │  Backend          │
    │  (browser)   │   │  (virtual DOM)    │
    └───────┬──────┘   └──────────────────┘
            │
    ┌───────▼──────┐
    │  Blazor      │
    │  Integration │
    └──────────────┘

The IBrowserBackend abstraction separates types from transport. Today: IJSRuntime (Blazor) and Jint (server-side). Future: native WASM Component Model imports.


Code Generation

Types are generated from official W3C/WHATWG specs — not hand-written:

337 WebIDL specs + 124 CSS data files
          │
    BrowserApi.Generator (CLI)
          │
    2,693 generated .g.cs files

Generated code is checked in for full IDE support and reviewable diffs.

Hand-written ergonomic layers (fluent builders, operators, factory methods) extend the generated partial types without modifying them.


Project Structure

BrowserApi/
├── src/
│   ├── BrowserApi/                 # Core types (zero deps)
│   │   ├── Common/                 # JsObject, IBrowserBackend, JsBatch
│   │   ├── Css/                    # Length, CssColor, Transform, Shadow, ...
│   │   ├── Dom/                    # QuerySelector<T>, EventExtensions, ...
│   │   ├── Canvas/                 # PathBuilder, GradientBuilder, CanvasFont
│   │   ├── Fetch/                  # Http, RequestBuilder, FetchResult
│   │   ├── Storage/                # TypedStorage, StorageExtensions
│   │   ├── Events/                 # Key, Modifiers, typed event extensions
│   │   ├── Animations/             # Easing, KeyframeBuilder, AnimateExtensions
│   │   └── Generated/              # 2,693 auto-generated .g.cs files
│   ├── BrowserApi.JSInterop/       # IJSRuntime backend + browserapi.js
│   ├── BrowserApi.Blazor/          # DI + BrowserApiComponentBase
│   ├── BrowserApi.Runtime/         # Jint + VirtualDom + BrowserEngine
│   └── BrowserApi.Generator/       # WebIDL/CSS → C# code generator
├── tests/                          # 599 tests, no browser needed
├── specs/                          # W3C/WHATWG spec files (generator input)
└── docs/                           # docfx API documentation

Documentation

Full API reference with examples: kasparorange.github.io/BrowserApi


License

MIT

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on BrowserApi.JSInterop:

Package Downloads
BrowserApi.Blazor

Blazor integration for BrowserApi. AddBrowserApi() DI registration, BrowserApiComponentBase with typed Window/Document access, and lifecycle hooks.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-preview.9 84 5/2/2026
0.1.0-preview.8 61 4/28/2026
0.1.0-preview.7 63 4/22/2026
0.1.0-preview.6 60 4/22/2026
0.1.0-preview.5 66 4/22/2026
0.1.0-preview.4 61 4/22/2026
0.1.0-preview.3 63 4/21/2026
0.1.0-preview.2 68 4/15/2026
0.1.0-preview.1 70 4/15/2026