loading
Note:
What it actually does A drawing app renders a stroke when its input event reaches the app, which is at least a frame behind the pointer - visible as a gap between the stylus tip and the ink. Delegated ink hands the last known point to the compositor, which draws the missing segment itself, outside your frame loop. You still draw the real stroke; this only covers the gap until you do, so match its colour and width to your own or the seam shows.
Warning:
Chromium only, and an enhancement by nature When it is unavailable nothing is lost but the few milliseconds of latency it was hiding, so there is no fallback to write - just draw as you would have anyway.

Support check

IsSupported

Returns true when the runtime exposes navigator.ink.requestPresenter. During prerender/SSR the check returns false rather than throwing, so defer it to OnAfterRenderAsync.

C#
@inject Bit.Butil.Ink ink

var supported = await ink.IsSupported();
Live sample
support check output
Results will appear here when you interact with the samples.

Draw with a trail

StartTrail / InkTrailHandle

The trail is started on an element and clipped to it. The pointer listener lives in JavaScript, because the presenter has to be fed the browser's own PointerEvent - by the time .NET saw one it would be a copy, and an untrusted one at that. Draw in the box below with the trail on and off and compare how far the ink lags behind the cursor; the effect is most visible with fast strokes on a high-refresh display.

Razor
@implements IAsyncDisposable
@inject Bit.Butil.Ink ink

<canvas @ref="_canvas" width="600" height="300"></canvas>

@code {
    private ElementReference _canvas;
    private InkTrailHandle? _trail;

    // The trail is drawn by the compositor ahead of the app's own strokes, so it hides the latency
    // between the pen and the canvas. The app still renders every stroke itself.
    private async Task Start() =>
        _trail = await ink.StartTrail(_canvas, color: "#0f6cbd", diameter: 4);

    public async ValueTask DisposeAsync()
    {
        if (_trail is not null) await _trail.DisposeAsync();   // stops the trail
    }
}
Live sample
trail output
Results will appear here when you interact with the samples.

Change the pen

InkTrailHandle.SetStyle

What a colour or brush-size picker calls: the trail changes from the next point onwards, without stopping and restarting it. Keep it in step with your own stroke style - the trail is the part of the line the user sees first.

Razor
@code {
    private InkTrailHandle? _trail;   // from StartTrail

    // Takes effect on the next segment the compositor draws; strokes already on the canvas are the
    // app's own and are unaffected.
    private async Task SwitchPen() => await _trail!.SetStyle("#c50f1f", diameter: 8);
}
Live sample
4 px
style output
Results will appear here when you interact with the samples.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes navigator.ink.requestPresenter. Returns default (false) during prerender/SSR instead of throwing.
StartTrail
ValueTask<InkTrailHandle?> StartTrail(ElementReference presentationArea, string color = &quot;black&quot;, double diameter = 3)
Starts painting a trail over an element as the pointer moves across it. Null when the API is unavailable or the element can't present ink.
DisposeAsync
ValueTask DisposeAsync()
On scope/circuit teardown, stops any trail whose handle was never disposed, so an abandoned pointer listener can't outlive the component that started it.
InkTrailHandle.SetStyle
ValueTask<bool> SetStyle(string color, double diameter = 0)
Changes the trail's colour and width from the next point onwards. Pass 0 to keep the current width.
InkTrailHandle.DisposeAsync
ValueTask DisposeAsync()
Stops the trail and detaches its pointer listener.
An unhandled error has occurred. Reload 🗙