---
title: "Headless text annotations | Nutrient Web SDK"
canonical_url: "https://www.nutrient.io/guides/web/headless/text-annotations/"
md_url: "https://www.nutrient.io/guides/web/headless/text-annotations.md"
last_updated: "2026-05-15T19:10:05.084Z"
description: "Drive the Nutrient Web SDK text annotation editor from your own toolbar, set fonts, colors, and text style without using the default inline UI."
---

# Headless text annotations

The `instance.annotations.text` namespace exposes the text annotation editor as a programmatic surface. Apply bold, italic, and underline to the active annotation; change the font family, size, color, and background; and subscribe to editor state changes, all from your own UI.

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 text-formatting toolbar that appears when a text annotation is being edited. This page covers the programmatic surface you’ll call from inside that custom UI.

## When to use this

Reach for the headless text annotation API when you’re building:

- A host-app text-formatting toolbar that lives outside the viewer container.

- A keyboard shortcut palette your application defines for bold, italic, font selection, and so on.

- A custom font picker fed by `getFonts()` instead of the default font list.

- A multipane editing experience where the formatting controls are in a separate panel from the canvas.

## API reference

The text annotation namespace exposes three groups of methods: applying style and formatting to the active editor, controlling editor focus and selection, and reading editor state and the font list.

### Style and formatting

Each setter accepts an optional second argument identifying the annotation to apply the change to. Omit the argument and the SDK targets the annotation currently in the editor.

| Method                                                                 | Notes                                                                                                                                          |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `instance.annotations.text.setTextStyle(options, annotationOrId?)`     | Applies one or more text styles. `options` is `{ bold?: boolean, italic?: boolean, underline?: boolean }`. Omit a field to leave it unchanged. |
| `instance.annotations.text.setFontFamily(family, annotationOrId?)`     | Sets the font family. `family` should be one of the values returned by `getFonts()`.                                                           |
| `instance.annotations.text.setFontSize(size, annotationOrId?)`         | Sets the font size in points.                                                                                                                  |
| `instance.annotations.text.setFontColor(color, annotationOrId?)`       | Sets the font color. `color` is a `NutrientViewer.Color`.                                                                                      |
| `instance.annotations.text.setBackgroundColor(color, annotationOrId?)` | Sets the background fill color. Pass `null` to clear.                                                                                          |
| `instance.annotations.text.restoreSelection()`                         | Restores the most recent selection inside the inline editor. Useful after a toolbar interaction has stolen focus.                              |

### Editor focus and selection

These methods drive the live editor while it’s in `EDITING` mode, which is useful for keyboard-shortcut palettes and external toolbars that need to drive the caret directly.

| Method                                                      | Notes                                                                                                                                    |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `instance.annotations.text.focusEditor()`                   | Programmatically focuses the live text editor. Requires the editor to be in `EDITING` mode.                                              |
| `instance.annotations.text.blurEditor()`                    | Programmatically blurs the live text editor. Requires `EDITING` mode.                                                                    |
| `instance.annotations.text.setSelection(start, length = 0)` | Sets the editor selection by character offsets. `length = 0` places the caret. Requires `EDITING` mode; validates non-negative integers. |

### Editor state and fonts

| Method                                        | Notes                                                                                                                                                                                                                   |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `instance.annotations.text.getEditorState()`  | Returns a snapshot of the editor state, or `null` if no text annotation is active.                                                                                                                                      |
| `instance.annotations.text.getFonts()`        | Returns `{ supportedFonts, customFonts, unsupportedCurrentFont }`. The last field is set when the active annotation uses a font the SDK didn’t load. Render it in your picker so the current font is always selectable. |
| `instance.annotations.text.getColorPresets()` | Returns the resolved color presets for font, background, and border colors.                                                                                                                                             |

## Events

| Event                              | When it fires                                                               |
| ---------------------------------- | --------------------------------------------------------------------------- |
| `textAnnotationEditorState.change` | Whenever the editor state changes, selection, formatting, focus, and so on. |

The event payload is `{ current, previous }`, where each side has the same shape as `getEditorState()`’s return value (or `null` when no text annotation is active). Read `current` to keep your custom toolbar in sync with the editor without polling; `previous` is useful for diffing transitions.

## Example: An external text-formatting toolbar

This example builds a host-app toolbar with bold, italic, font family, font size, and color controls. The toolbar listens for `textAnnotationEditorState.change` and reflects the current editor state in its button states:

```js

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

  document: "contract.pdf"
});

const toolbar = document.getElementById("text-toolbar");
const boldButton = toolbar.querySelector("#bold");

const italicButton = toolbar.querySelector("#italic");

const fontSelect = toolbar.querySelector("#font");

const sizeInput = toolbar.querySelector("#size");

const colorInput = toolbar.querySelector("#color");

const fonts = instance.annotations.text.getFonts();
const families = [...fonts.supportedFonts,...fonts.customFonts];
// `unsupportedCurrentFont` is the font the active annotation uses but the
// SDK didn't load. Prepend it so the dropdown always reflects the current font.
if (fonts.unsupportedCurrentFont &&!families.includes(fonts.unsupportedCurrentFont)) {
  families.unshift(fonts.unsupportedCurrentFont);
}
families.forEach((family) => {
  const option = document.createElement("option");
  option.value = family;
  option.textContent = family;
  fontSelect.appendChild(option);
});

instance.addEventListener("textAnnotationEditorState.change", ({ current }) => {
  if (!current) {
    toolbar.style.display = "none";
    return;
  }

  toolbar.style.display = "flex";
  boldButton.classList.toggle("is-active", current.marks.bold === "on");
  italicButton.classList.toggle("is-active", current.marks.italic === "on");
  fontSelect.value = current.fontFamily.value?? "";
  sizeInput.value = current.fontSize.value?? "";
});

boldButton.onclick = () => {
  instance.annotations.text.restoreSelection();
  const state = instance.annotations.text.getEditorState();
  const isBold = state?.marks.bold === "on";
  instance.annotations.text.setTextStyle({ bold:!isBold });
};

italicButton.onclick = () => {
  instance.annotations.text.restoreSelection();
  const state = instance.annotations.text.getEditorState();
  const isItalic = state?.marks.italic === "on";
  instance.annotations.text.setTextStyle({ italic:!isItalic });
};

fontSelect.onchange = () => {
  instance.annotations.text.restoreSelection();
  instance.annotations.text.setFontFamily(fontSelect.value);
};

sizeInput.oninput = () => {
  instance.annotations.text.restoreSelection();
  instance.annotations.text.setFontSize(Number(sizeInput.value));
};

colorInput.oninput = () => {
  instance.annotations.text.restoreSelection();
  instance.annotations.text.setFontColor(
    NutrientViewer.Color.fromHex(colorInput.value)
  );
};

```

The `restoreSelection()` call before each formatting action is important; clicking a toolbar button moves focus out of the editor and clears the selection, so we restore it before applying the change.

## Example: Combining several styles in one call

`setTextStyle` accepts multiple flags at once, so a “make this bold and italic” command is a single call:

```js

instance.annotations.text.restoreSelection();
instance.annotations.text.setTextStyle({ bold: true, italic: true });

```

Omitting a field leaves that style unchanged; only the fields you set are applied.

## Related

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

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

- [Callout annotations](https://www.nutrient.io/guides/web/headless/callout.md) — `TextAnnotation` variants with leader lines.
---

## 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 ink annotations](/guides/web/headless/ink.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 shape annotations](/guides/web/headless/shape.md)
- [Headless text markup](/guides/web/headless/text-markup.md)

