A JavaScript library for adding interactive text annotation functionality to web applications.
Also available: React wrapper | TEI/XML extension | Recogito PDF Annotator
npm install @recogito/text-annotatorimport { createTextAnnotator } from '@recogito/text-annotator';
const anno = createTextAnnotator(document.getElementById('content'));
// Load annotations from a file
anno.loadAnnotations('annotations.json');
// Listen to user events
anno.on('createAnnotation', annotation => {
console.log('new annotation', annotation);
}); const anno = createTextAnnotator(element, options);| Option | Type | Default | Description |
|---|---|---|---|
allowModifierSelect |
boolean |
false |
Allows users to extend an existing annotation by holding Ctrl (Cmd on Mac) while selecting. |
annotatingEnabled |
boolean |
true |
Enable or disable creation of new annotations. |
dismissOnNotAnnotatable |
'NEVER' | 'ALWAYS' | function |
'NEVER' |
Controls whether the currently selected annotation is un-selected when clicking outside the annotatable area. |
mergeHighlights |
object |
undefined |
Merge adjacent highlights. Options: horizontalTolerance and verticalTolerance (pixels) |
selectionMode |
'shortest' | 'all' |
'shortest' |
When selecting overlapping annotations: select all or only the shortest. |
style |
HighlightStyleExpression |
undefined |
Custom highlight styling function. |
user |
User |
anonymous guest | Current user info, automatically added to new or updated annotations. |
userSelectAction |
UserSelectActionExpression |
SELECT |
Behavior when the user selects an annotation (see below) |
Add a single annotation programmatically.
anno.addAnnotation(annotation);Programmatically cancel the current selection.
anno.cancelSelected();Test if there are any re- or undoable user actions in the undo stack.
const canRedo = anno.canRedo();Remove all annotations.
anno.clearAnnotations();Destroy the annotator instance and clean up all event listeners.
anno.destroy();Retrieve a specific annotation.
const annotation = anno.getAnnotationById('annotation-id');Return all annotations.
const annotations = anno.getAnnotations();Return currently selected annotations.
const selected = anno.getSelected();Return the current user.
const user = anno.getUser();Load annotations from a URL.
await anno.loadAnnotations('/annotations.json');Programmatically redo or undo the last user edit.
anno.undo();
anno.redo();Delete an annotation by ID or object.
anno.removeAnnotation('annotation-id');Scroll the annotation into view. Returns true if successful, false if annotation is not currently rendered.
anno.scrollIntoView('annotation-id');Enable or disable annotation creation.
anno.setAnnotatingEnabled(false);Configure the annotation mode:
CREATE_NEW(default): each user selection creates a new annotation.ADD_TO_CURRENT: the user selection extends the currently selected annotation's range, if any.REPLACE_CURRENT: the user selection replaces the currently selected annotations' range, if any.
anno.setAnnotatingMode('ADD_TO_CURRENT');Bulk-add annotations. If replace is true (default), all existing annotations are deleted first. If false, the new annotations are appended to existing ones.
anno.setAnnotations(annotations);Apply a filter function to control which annotations are displayed.
anno.setFilter(annotation =>
annotation.bodies.some(b => b.purpose === 'commenting')
);Programmatically select annotation(s). Passing undefined or no argument will clear selection.
anno.setSelected('annotation-id');
anno.setSelected(['id-1', 'id-2']);Change the highlighting style function or set a static style - see details below.
anno.setStyle({
fill: 'yellow',
fillOpacity: 0.25
});
anno.setStyle((annotation, state) => ({
fill: annotation.bodies[0]?.purpose === 'tagging' ? 'yellow' : 'lightblue',
fillOpacity: state.hovered ? 0.5 : 0.25
}));Set the current user.
anno.setUser({ id: '[email protected]', name: 'John' });Change the current userSelectAction, which determines what happens when the user selects an annotation interactively. Can be a UserSelectAction, or a function that receives the annotation as input and returns a UserSelectAction.
- SELECT (default): The annotation will be selected and the
selectionChangedevent will be triggered. - NONE: the annotation is inert, clicking has no effect.
anno.setUserSelectAction('NONE');
anno.setUserSelectAction(annotation => annotation.bodies.length > 0 ? 'SELECT' : 'NONE');Show or hide all annotations.
anno.setVisible(false); Update an existing annotation.
anno.updateAnnotation(updated);Listen to annotation lifecycle events using on() and remove listeners with off().
Fired when the user creates a new annotation.
// Example: save new annotations to a backend
anno.on('createAnnotation', annotation => {
console.log('Created:', annotation);
fetch('/my-api/annotations', {
method: 'POST',
body: JSON.stringify(annotation)
});
});Fired when the user updates an annotation. Receives the updated annotation and the previous version.
anno.on('updateAnnotation', (annotation, previous) => {
console.log('Updated:', annotation, 'was:', previous);
});Fired when the user deletes an annotation.
anno.on('deleteAnnotation', annotation => {
console.log('Deleted:', annotation);
});Fired when the selection was changed by the user.
anno.on('selectionChanged', annotations => {
console.log('Selected:', annotations);
});Fired when annotations enter or leave the visible area.
anno.on('viewportIntersect', annotations => {
console.log('Visible:', annotations);
});Remove event listeners:
const handler = annotation => console.log(annotation);
anno.on('createAnnotation', handler);
anno.off('createAnnotation', handler);The Text Annotator data model aligns closely with the W3C Web Annotation Data Model, but with a few key differences to optimize for performance and ease of use. Every annotation in Annotorious is represented by a JavaScript object with the following structure:
{
"id": "67a427d7-afc3-474a-bdab-1e2ea8dc78f6",
"bodies": [],
"target": {
"selector": [{
"quote": "Tell me, O muse",
"start": 48,
"end": 63
}],
"creator": {
"id": "zD62eVrpvJgMEEWuPpPS"
},
"created": "2025-09-30T07:28:54.973Z",
"updated": "2025-09-30T07:28:56.158Z"
}
}-
id- a unique identifier for the annotation. The ID can be any alphanumeric string. Annotations created by users will receive a globally unique UUID automatically. -
target- the target represents the text range that the annotation is associated with. Theselectorprovides the selectedquoteand character offsets forstartandend. -
bodiesare designed to carry application-specific payload, such as comments, tags, or other metadata associated with the annotation.
You can customize the appearance of highlights using the style config option or setStyle() method.
const anno = createTextAnnotator(element, {
style: {
fill: '#ffeb3b',
fillOpacity: 0.25,
underlineStyle: 'dashed',
underlineColor: '#7d7208ff',
underlineOffset: 0,
underlineThickness: 2
}
});You can provide a function to style annotations based on their properties. The style function receives three arguments:
annotation- the annotation objectstate- an object withselectedandhoveredboolean propertieszIndex- the stacking order (useful for layering effects on overlapping annotations)
anno.setStyle((annotation, state, zIndex) => {
const hasTag = annotation.bodies.some(b => b.purpose === 'tagging');
return {
fill: hasTag ? '#ffeb3b' : '#bbdefb',
fillOpacity: state.hovered ? 0.35 : 0.2,
underlineColor: hasTag ? '#f57f17' : undefined
};
});The Recogito Text Annotator is licensed under the BSD 3-Clause license.
