BrowserApi.Css.SourceGen 0.1.0-preview.9

This is a prerelease version of BrowserApi.Css.SourceGen.
dotnet add package BrowserApi.Css.SourceGen --version 0.1.0-preview.9
                    
NuGet\Install-Package BrowserApi.Css.SourceGen -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.Css.SourceGen" Version="0.1.0-preview.9">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BrowserApi.Css.SourceGen" Version="0.1.0-preview.9" />
                    
Directory.Packages.props
<PackageReference Include="BrowserApi.Css.SourceGen">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
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.Css.SourceGen --version 0.1.0-preview.9
                    
#r "nuget: BrowserApi.Css.SourceGen, 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.Css.SourceGen@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.Css.SourceGen&version=0.1.0-preview.9&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=BrowserApi.Css.SourceGen&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

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

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
0.1.0-preview.9 76 5/2/2026