JavaScript Interop

Tesserae compiles C# to JavaScript with the h5 compiler. Most of the time you work with the strongly-typed browser bindings in H5.Core.dom, but when you need raw JavaScript — a browser API h5 doesn't surface, or a global exposed by a bundled library — h5 provides several escape hatches.

Reaching the DOM and browser globals

Import the DOM bindings and use document, window, console, alert, navigator, and the rest as typed C#:

using static H5.Core.dom;

document.body.style.overflow = "hidden";
window.setTimeout((_) => DoThing(), 16);
console.error("something went wrong");

Prefer these typed bindings whenever they exist — they are checked at compile time, unlike inline script.

Script.Write — inline JavaScript

H5.Script.Write emits JavaScript verbatim. The generic overload returns a value. Positional placeholders ({0}, {1}, …) are replaced with the compiled form of the C# arguments — real JavaScript references, not string concatenation:

using H5;

// no return value
Script.Write("{0}.scrollIntoView()", element);

// typed return value
double now     = Script.Write<double>("Date.now()");
bool   isArray = Script.Write<bool>("Array.isArray({0})", children);
object lib     = Script.Write<object>("new SomeLib({0}, { gutter: {1} })", element, 10);

Pass C# values as {n} placeholders rather than interpolating them into the string — that way they compile correctly, including under minification. Keep the JavaScript short, and guard calls that might throw:

Script.Write("try { {0}.setPointerCapture({1}); } catch (e) { }", element, pointerId);

This is exactly how the toolkit drives tippy tooltips, masonry relayout, and pointer capture for gestures.

Typed bindings with attributes

When you want a typed C# surface over existing JavaScript instead of scattering Script.Write calls, h5 offers a set of attributes:

  • [H5.Name("globalName")] maps a C# type to a JavaScript global or namespace. Tesserae uses it on every component ([H5.Name("tss.Button")]) to keep generated class names stable.
  • [External] with extern members declares a binding that has no C# body — the call compiles straight to the underlying JavaScript member.
  • [H5.Template("…")] emits a specific JavaScript expression for a member, using placeholders like {this} and the argument names.
[H5.Name("tss.ROA")]
public class ReadOnlyArray<T>
{
    public extern ReadOnlyArray(T[] data);

    [External]
    public extern T this[int index] { [Template("{this}[{index}]")] get; }

    public extern int Length { [Name("length")] get; }   // compiles to .length
}

Converting values

Use .As<T>() to reinterpret a value between its C# and JavaScript views when the type system needs a nudge — for example passing either an element or a string to the same JavaScript option.

Caveats

  • Script.Write strings are opaque to the compiler, so typos surface only at runtime. Keep them minimal and lean on typed bindings where possible.
  • Any external global you call must actually exist at runtime. Bundle the script through h5.json first — see Wrapping a JavaScript Library.

See also

© 2026 Curiosity. All rights reserved.