You can customize the Nutrient SDK to show an annotation inspector in a popup whenever you select an annotation. If you want to see an example, check out our demo.

In this guide, you’ll implement an inspector for shape annotations that has the option to change the color of the stroke. You’ll use NutrientViewer.Container#annotationTooltipCallback to add a tooltip that’s visible when selecting any shape annotation:

let instance;
const seenAnnotations = new Set();
NutrientViewer.load({
...baseOptions,
theme: NutrientViewer.Theme.DARK,
annotationTooltipCallback: (annotation) => {
if (!(annotation instanceof NutrientViewer.Annotations.ShapeAnnotation)) {
return [];
}
if (!annotation.id || !seenAnnotations.has(annotation.id)) {
if (annotation.id) {
seenAnnotations.add(annotation.id);
}
return [];
}
const doc =
instance.contentDocument instanceof Document
? instance.contentDocument
: instance.contentDocument.ownerDocument;
const wrapper = doc.createElement("div");
wrapper.classList.add("stroke-color-control");
const text = doc.createElement("span");
text.classList.add("stroke-color-text");
text.textContent = "Stroke Color";
wrapper.appendChild(text);
const swatchContainer = doc.createElement("div");
swatchContainer.classList.add("stroke-color-swatch-container");
wrapper.appendChild(swatchContainer);
const swatchButton = doc.createElement("button");
swatchButton.type = "button";
swatchButton.classList.add("stroke-color-button");
swatchContainer.appendChild(swatchButton);
const palette = doc.createElement("div");
palette.classList.add("stroke-color-palette");
palette.hidden = true;
swatchContainer.appendChild(palette);
const swatchColors = [
"#F44336",
"#FF9800",
"#FFEB3B",
"#4CAF50",
"#3F83F8",
"#D946EF",
"#F8B4B4",
"#FBD38D",
"#FCE96A",
"#B7F0A4",
"#C7D2FE",
"#FBCFE8",
"#E5E7EB",
"#9CA3AF",
"#6B7280",
"#374151",
"#111827",
null,
];
const updateButtonColor = (color) => {
swatchButton.style.setProperty("--stroke-color", color || "transparent");
swatchButton.classList.toggle("is-transparent", !color);
};
updateButtonColor(annotation.strokeColor?.toHex() ?? null);
swatchColors.forEach((color) => {
const swatch = doc.createElement("button");
swatch.type = "button";
swatch.classList.add("stroke-color-swatch");
if (!color) {
swatch.classList.add("is-transparent");
swatch.dataset.color = "";
} else {
swatch.dataset.color = color;
swatch.style.setProperty("--swatch-color", color);
}
swatch.addEventListener("click", async () => {
const selectedColor = swatch.dataset.color;
const updatedAnnotation = annotation.set(
"strokeColor",
selectedColor ? NutrientViewer.Color.fromHex(selectedColor) : null
);
await instance.update(updatedAnnotation);
updateButtonColor(selectedColor || null);
palette.hidden = true;
});
palette.appendChild(swatch);
});
swatchButton.addEventListener("click", () => {
palette.hidden = !palette.hidden;
});
return [
{
type: "custom",
node: wrapper,
onPress: () => {
console.log(annotation);
},
},
];
},
initialViewState: new NutrientViewer.ViewState({
enableAnnotationToolbar: false,
}),
}).then((_instance) => {
instance = _instance;
const styleRoot =
instance.contentDocument instanceof Document
? instance.contentDocument.head
: instance.contentDocument;
if (styleRoot && !styleRoot.querySelector("style[data-stroke-tooltip]")) {
const styleTag = styleRoot.ownerDocument.createElement("style");
styleTag.dataset.strokeTooltip = "true";
styleTag.textContent = strokeTooltipStyles;
styleRoot.appendChild(styleTag);
}
console.log("Nutrient loaded!");
console.log("API docs: https://www.nutrient.io/api/web/");
console.log("Guides: https://www.nutrient.io/guides/web/");
return instance;
});

The code above will result in the following.

0:00
0:00

Similarly, you can add other elements that control the different properties of the annotation.