Create custom annotation toggle button

This guide demonstrates how to create a custom toggle button in the Nutrient Web SDK toolbar that enables users to show or hide all annotations in a PDF document.

Live demo — Try the custom annotation toggle live in our playground to see it in action.

Use cases

The custom annotation toggle button provides users with a way to hide or show all annotations across all pages of a PDF document. This is particularly useful for:

  • Reviewing documents with and without annotations
  • Presentations where annotations might be distracting
  • Comparing original content with annotated versions
  • Accessibility scenarios where annotations need to be temporarily hidden

For documents with many annotations, the toggle operation processes all pages sequentially. Consider implementing batching strategies for optimal performance in large documents.

Implementation

To create a custom annotation toggle button, follow the steps below:

Basic setup

First, set up Nutrient Web SDK with your desired configuration:

// Initialize the annotation visibility state.
let annotationsHidden = false; // Track the current state.
NutrientViewer.load({
container: document.getElementById("viewerContainer"),
document: "path/to/your/document.pdf",
licenseKey: "your-license-key",
theme: NutrientViewer.Theme.DARK,
toolbarItems: [...NutrientViewer.defaultToolbarItems],
}).then(async (instance) => {
// Custom button implementation goes here.
});

Creating the custom toggle button

Add the custom toggle button to the toolbar:

// Define the custom toggle button configuration.
const toggleAnnotationsButton = {
type: "custom",
id: "toggle-annotations",
title: "Toggle Annotations",
icon: annotationsHidden ? "👁️" : "🙈", // Dynamic icon based on state.
onPress: async () => {
await toggleAllAnnotations(instance);
},
};
// Add the button to the toolbar.
instance.setToolbarItems((items) => {
items.push(toggleAnnotationsButton);
return items;
});

Complete toggle implementation

Below is the complete implementation with error handling and state management:

// Initialize state variable to track annotation visibility.
let annotationsHidden = false;
NutrientViewer.load({
container: document.getElementById("viewerContainer"),
document: "path/to/your/document.pdf",
licenseKey: "your-license-key",
theme: NutrientViewer.Theme.DARK,
toolbarItems: [...NutrientViewer.defaultToolbarItems],
}).then(async (instance) => {
// Function to toggle all annotations across the document.
const toggleAllAnnotations = async () => {
try {
const totalPages = instance.totalPageCount;
// Process all pages in the document.
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
const annotations = await instance.getAnnotations(pageIndex);
// Update each annotation’s visibility using Promise.all for performance.
const updatePromises = annotations.map(async (annotation) => {
// Set noView property: invert current state (!annotationsHidden).
// When annotationsHidden=false (visible), set noView=true to hide.
// When annotationsHidden=true (hidden), set noView=false to show.
const updatedAnnotation = annotation.set("noView", !annotationsHidden);
return instance.update(updatedAnnotation);
});
// Wait for all updates on current page to complete before proceeding.
await Promise.all(updatePromises);
}
// Toggle the global state.
annotationsHidden = !annotationsHidden;
// Update button appearance to reflect new state.
updateToggleButton();
console.log(`Annotations ${annotationsHidden ? 'hidden' : 'shown'}`);
} catch (error) {
console.error('Error toggling annotations:', error);
}
};
// Function to update button appearance based on current state.
const updateToggleButton = () => {
const buttonTitle = annotationsHidden ? "Show Annotations" : "Hide Annotations";
const buttonIcon = annotationsHidden ? "👁️" : "🙈";
instance.setToolbarItems((items) => {
const buttonIndex = items.findIndex(item => item.id === "toggle-annotations");
if (buttonIndex !== -1) {
items[buttonIndex] = {
...items[buttonIndex],
title: buttonTitle,
icon: buttonIcon
};
}
return items;
});
};
// Create the initial toggle button configuration.
const toggleAnnotationsButton = {
type: "custom",
id: "toggle-annotations",
title: "Hide Annotations",
icon: "🙈",
onPress: toggleAllAnnotations,
};
// Add the button to the toolbar.
instance.setToolbarItems((items) => {
items.push(toggleAnnotationsButton);
return items;
});
});

Code breakdown

Below is a breakdown of the key parts of the implementation:

State management

let annotationsHidden = false;

This variable tracks whether annotations are currently hidden. Starting with false means annotations are visible by default.

Iterating through pages

const totalPages = instance.totalPageCount;
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
const annotations = await instance.getAnnotations(pageIndex);
// Process annotations.
}

The toggle function iterates through all pages in the document to ensure every annotation is affected.

Updating annotation visibility

const updatedAnnotation = annotation.set("noView", !annotationsHidden);
await instance.update(updatedAnnotation);

The noView property controls annotation visibility:

  • true — Annotation is hidden
  • false — Annotation is visible

Asynchronous processing

const updatePromises = annotations.map(async (annotation) => {
const updatedAnnotation = annotation.set("noView", !annotationsHidden);
return instance.update(updatedAnnotation);
});
await Promise.all(updatePromises);

Using Promise.all() ensures all annotations on a page are updated simultaneously for better performance.

Advanced features

Below are some advanced features you can implement for the annotation toggle functionality:

Custom icon implementation

Replace emoji icons with SVG or CSS classes for better styling:

const toggleAnnotationsButton = {
type: "custom",
id: "toggle-annotations",
title: annotationsHidden ? "Show Annotations" : "Hide Annotations",
className: annotationsHidden ? "annotation-hidden-btn" : "annotation-visible-btn",
onPress: toggleAllAnnotations,
};

Add corresponding CSS:

.annotation-hidden-btn::before {
content: "👁️";
margin-right: 8px;
}
.annotation-visible-btn::before {
content: "🙈";
margin-right: 8px;
}

Selective annotation types

Hide only specific types of annotations:

const toggleSpecificAnnotations = async (annotationTypes = ['ink', 'highlight']) => {
const totalPages = instance.totalPageCount;
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
const annotations = await instance.getAnnotations(pageIndex);
const filteredAnnotations = annotations.filter(annotation =>
annotationTypes.includes(annotation.type)
);
const updatePromises = filteredAnnotations.map(async (annotation) => {
const updatedAnnotation = annotation.set("noView", !annotationsHidden);
return instance.update(updatedAnnotation);
});
await Promise.all(updatePromises);
}
annotationsHidden = !annotationsHidden;
};

Animation effects

Add smooth transitions when toggling annotations:

const toggleWithAnimation = async () => {
// Add fade-out effect.
document.querySelector('.PSPDFKit-Container').style.transition = 'opacity 0.3s';
document.querySelector('.PSPDFKit-Container').style.opacity = '0.7';
await toggleAllAnnotations();
// Restore opacity.
setTimeout(() => {
document.querySelector('.PSPDFKit-Container').style.opacity = '1';
}, 300);
};

Keyboard shortcut support

Add keyboard shortcuts for the toggle functionality:

document.addEventListener('keydown', (event) => {
// Toggle with Ctrl+H (or Cmd+H on Mac).
if ((event.ctrlKey || event.metaKey) && event.key === 'h') {
event.preventDefault();
toggleAllAnnotations();
}
});

Best practices

Below are some best practices to follow when implementing annotation toggling:

Error handling

Always wrap annotation operations in try-catch blocks:

const toggleAllAnnotations = async () => {
try {
// Toggle logic here.
} catch (error) {
console.error('Failed to toggle annotations:', error);
// Show user-friendly error message.
showErrorMessage('Unable to toggle annotations. Please try again.');
}
};

Performance optimization

For documents with many annotations, consider batching updates:

const BATCH_SIZE = 50;
const toggleAnnotationsInBatches = async () => {
const totalPages = instance.totalPageCount;
let allAnnotations = [];
// Collect all annotations first.
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
const pageAnnotations = await instance.getAnnotations(pageIndex);
allAnnotations = allAnnotations.concat(Array.from(pageAnnotations));
}
// Process in batches.
for (let i = 0; i < allAnnotations.length; i += BATCH_SIZE) {
const batch = allAnnotations.slice(i, i + BATCH_SIZE);
const updatePromises = batch.map(annotation => {
const updatedAnnotation = annotation.set("noView", !annotationsHidden);
return instance.update(updatedAnnotation);
});
await Promise.all(updatePromises);
}
};

State persistence

Save the toggle state to localStorage:

const STORAGE_KEY = 'nutrient-annotations-hidden';
// Load initial state.
annotationsHidden = localStorage.getItem(STORAGE_KEY) === 'true';
const toggleAllAnnotations = async () => {
// Toggle logic.
// Save state.
localStorage.setItem(STORAGE_KEY, annotationsHidden.toString());
};

Loading indicator

Show progress for large documents:

const showLoadingIndicator = () => {
const loader = document.createElement('div');
loader.id = 'annotation-toggle-loader';
loader.textContent = 'Toggling annotations...';
loader.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 20px;
border-radius: 8px;
z-index: 10000;
`;
document.body.appendChild(loader);
};
const hideLoadingIndicator = () => {
const loader = document.getElementById('annotation-toggle-loader');
if (loader) {
loader.remove();
}
};
const toggleAllAnnotations = async () => {
showLoadingIndicator();
try {
// Toggle logic here.
} finally {
hideLoadingIndicator();
}
};

Troubleshooting

Use the below troubleshooting tips and solutions when implementing custom annotation toggle:

Common issues

Annotations not hiding

  • Ensure you’re using the correct property name: noView
  • Check that annotations are being updated with instance.update()

Button not appearing

  • Verify the button is being added after the viewer loads
  • Check that setToolbarItems is called correctly

Performance issues with large documents

  • Implement batching as shown in best practices
  • Consider adding a loading indicator
  • Use Promise.all() for concurrent updates

State synchronization issues

  • Always update the state variable after successful operations
  • Consider using a more robust state management solution for complex applications

Debug helper

Add the below helper function for debugging:

const debugAnnotations = async () => {
const totalPages = instance.totalPageCount;
console.log(`Document has ${totalPages} pages`);
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
const annotations = await instance.getAnnotations(pageIndex);
console.log(`Page ${pageIndex + 1}: ${annotations.size} annotations`);
annotations.forEach((annotation, index) => {
console.log(` Annotation ${index + 1}:`, {
type: annotation.type,
id: annotation.id,
noView: annotation.noView
});
});
}
};
// Call in browser console for debugging.
window.debugAnnotations = debugAnnotations;

The above implementation provides a robust, user-friendly way to toggle annotation visibility in your web applications using Nutrient Web SDK. The code can be customized further based on specific application requirements and design preferences.

Next steps

  • Explore related features — Refer to the customizing toolbar guide for more toolbar customization options
  • Advanced annotations — Learn about annotation types to create targeted toggle functionality
  • Performance optimization — Review best practices for handling large documents with many annotations
  • Custom overlay items — Learn how to create custom overlay items