---
title: "Headless ink annotations | Nutrient Web SDK"
canonical_url: "https://www.nutrient.io/guides/web/headless/ink/"
md_url: "https://www.nutrient.io/guides/web/headless/ink.md"
last_updated: "2026-05-15T19:10:05.084Z"
description: "Drive ink annotations from your own UI with Nutrient Web SDK, observe live drawing state, configure the eraser at load time, and respond to in-progress strokes without using the default toolbar."
---

# Headless ink annotations

The `instance.annotations.ink` namespace exposes the live drawing state the SDK tracks while the user is drawing an ink annotation. Combine it with the existing ink configuration and event surfaces to build an ink experience that doesn’t go through the default toolbar.

Looking for the visual side of this? See the [supported slots reference](https://www.nutrient.io/guides/web/user-interface/ui-customization/supported-slots.md) for replacing the inline ink toolbar that appears when ink mode is active. This page covers the programmatic surface you’ll call from inside that custom UI.

## When to use this

Reach for the headless ink API when you’re building:

- A floating brush toolbar that lives outside the viewer and stays visible across documents.

- A status indicator that reflects whether the user is currently in the middle of a stroke.

- An analytics or collaboration layer that tracks how long users spend drawing.

- Switching the eraser between point and stroke modes, either at load time or at runtime from a custom toolbar.

## API reference

The headless ink surface splits into two parts: the live drawing state you can read while a stroke is in progress, and the eraser mode configuration.

### Drawing state

| Symbol                                       | Notes                                                                                                                  |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `instance.annotations.ink.getDrawingState()` | Returns a snapshot of the current ink drawing state. Reads safely at any time; useful when polling from a render loop. |

`getDrawingState()` returns an object with:

- `isDrawing: boolean` — Whether the user is currently in the middle of a stroke.

- `activeAnnotationId: string | null` — The ID of the ink annotation being drawn, or `null` when idle.

- `lastDrawStartAt: number | null` — Timestamp (ms since epoch) when the most recent stroke started.

- `lastDrawEndAt: number | null` — Timestamp when the most recent stroke ended.

- `lastDrawDurationMs: number | null` — Duration of the most recent stroke in milliseconds.

The state is transient telemetry; it doesn’t include the stroke geometry, color, or width. For brush settings, refer to the `inkAnnotation` annotation preset. For the rendered stroke geometry, read the annotation directly once it has been created.

### Eraser configuration

| Symbol                                         | Notes                                                                                                                                             |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NutrientViewer.InkEraserMode`                 | Enum with two values: `POINT` (erase individual points) and `STROKE` (erase whole strokes).                                                       |
| `Configuration.inkEraserMode`                  | Initial eraser mode, passed to `NutrientViewer.load()`. Defaults to `InkEraserMode.POINT`.                                                        |
| `instance.annotations.ink.setEraserMode(mode)` | Updates the eraser mode at runtime. Accepts an `InkEraserMode` value and returns the applied mode. Use this from a custom toolbar after `load()`. |

## Events

| Event         | When it fires                                                                                                                                                                          |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ink.drawing` | Emitted during an active ink stroke with payload `{ annotation: InkAnnotation }`, where `annotation` is the in-progress stroke. Pair with `getDrawingState()` for lifecycle telemetry. |

## Example: A status indicator wired to drawing state

This example renders an indicator outside the viewer container that flips when the user starts or stops drawing:

```js

const instance = await NutrientViewer.load({
  container: "#viewer",

  document: "contract.pdf"
});

const indicator = document.getElementById("drawing-indicator");

function render() {
  const state = instance.annotations.ink.getDrawingState();
  indicator.classList.toggle("is-active", state.isDrawing);
  indicator.textContent = state.isDrawing? "Drawing…"
    : state.lastDrawDurationMs!= null? `Last stroke: ${Math.round(state.lastDrawDurationMs)}ms`
      : "Idle";
}

instance.addEventListener("ink.drawing", render);
render();

```

You can also poll `getDrawingState()` synchronously from a render loop — for example, when you want to step the indicator at a fixed frame rate:

```js

function frame() {
  const state = instance.annotations.ink.getDrawingState();
  indicator.classList.toggle("is-active", state.isDrawing);
  requestAnimationFrame(frame);
}

requestAnimationFrame(frame);

```

## Example: Configure the eraser

Set the initial eraser mode by passing `inkEraserMode` to `NutrientViewer.load()`:

```js

const instance = await NutrientViewer.load({
  container: "#viewer",

  document: "contract.pdf",
  inkEraserMode: NutrientViewer.InkEraserMode.STROKE
});

```

`InkEraserMode.POINT` (the default) erases individual points as the eraser passes over them. `InkEraserMode.STROKE` erases the entire stroke under the eraser as a single action.

Switch the mode at runtime by calling `setEraserMode()`, which is typically wired up to a custom toolbar control:

```js

document.getElementById("eraser-mode-stroke").onclick = () => {
  instance.annotations.ink.setEraserMode(NutrientViewer.InkEraserMode.STROKE);
};

document.getElementById("eraser-mode-point").onclick = () => {
  instance.annotations.ink.setEraserMode(NutrientViewer.InkEraserMode.POINT);
};

```

## Related

- [Color presets](https://www.nutrient.io/guides/web/headless/color-presets.md) — The cross-namespace pattern for reading preset colors from inside a custom UI.

- [Ink annotation reference](https://www.nutrient.io/api/web/classes/NutrientViewer.Annotations.InkAnnotation.html) — The full `InkAnnotation` API.
---

## Related pages

- [Headless callout annotations](/guides/web/headless/callout.md)
- [Headless color presets](/guides/web/headless/color-presets.md)
- [Headless image annotations](/guides/web/headless/image.md)
- [Annotation clipboard](/guides/web/headless/clipboard.md)
- [Headless](/guides/web/headless.md)
- [Headless stamp annotations](/guides/web/headless/stamp.md)
- [Headless link annotations](/guides/web/headless/link.md)
- [Headless note annotations](/guides/web/headless/note.md)
- [Redactions from text selection](/guides/web/headless/redactions-from-selection.md)
- [Programmatic notes panel](/guides/web/headless/notes-panel.md)
- [Headless text annotations](/guides/web/headless/text-annotations.md)
- [Headless shape annotations](/guides/web/headless/shape.md)
- [Headless text markup](/guides/web/headless/text-markup.md)

