React PDF annotations: Add highlights, comments, and markup to PDFs

Table of contents

    React PDF annotations: Add highlights, comments, and markup to PDFs
    Summary

    React PDF annotations are added in two ways: through the viewer’s built-in toolbar, or programmatically by creating annotation objects and calling instance.create(). This tutorial sets up Nutrient Web SDK in a Vite React project, mounts a PDF, creates text and ink annotations from code, exports the annotations as XML Forms Data Format (XFDF), and sets the author name. The PDF specification defines the annotation types covered here, from text markup to stamps and measurements.

    In this post, we’ll cover different types of PDF annotations and their potential use cases, and we’ll also show you how to implement PDF annotations in your applications using Nutrient’s React PDF annotation library with the React API.

    What are PDF annotations?

    PDF annotations are objects — like text, graphics, highlights, text boxes, etc. — that you can add to a PDF document without changing the content. They’re useful for reviewing documents, collaborating with others, and implementing interactive features such as forms.

    Normally, to annotate a document, you’d open a PDF in a third-party PDF editor and select an annotation tool.

    Drawing on a PDF

    But in place of an app, you can annotate PDF documents using frontend React code.

    This means everything is run on the client side and not on an external server. This is useful in situations where user privacy is important, since the server isn’t processing any sensitive data.

    Types of PDF annotations in React

    The PDF specification supports many types of annotations. They come in two categories:

    • Markup annotations — These are used to mark up the content of a PDF file. They’re typically used during a review process to allow your users to collaborate, emphasize parts of the text, and take notes.
    • Non-markup annotations — These are used for non-markup purposes such as adding multimedia or fillable form fields to an existing PDF.

    The following sections will provide a quick overview of the various types of PDF annotations.

    Text markup

    Text markup is used to annotate text in a PDF document. It can include highlighters, underlines, or strikeout annotations.

    Text with highlighting, underlining, and strikeout annotations

    Drawing annotations

    Drawing annotations are used to draw and write on a PDF page. You can customize the color, thickness, and transparency of the annotation tool.

    Drawing on a PDF, with the toolbar showing the annotation tool options

    Widget annotations

    Widgets are non-markup annotations. They appear in interactive forms, which include buttons, radio buttons, text fields, signature form fields, list boxes, and combo boxes. They can be used to create forms from flat documents.

    A PDF form being filled in with typed text

    Shape annotations

    These annotation types allow you to add a shape like a circle, box, or arrow annotation to a document. Cloudy annotations are commonly used in construction software to indicate a change in a plan.

    Measurement annotations

    With measurement tools, users can calculate dimensions, measure distances between lines, or trace the perimeter of drawings in a PDF document. To ensure accuracy, the scale of measurement can be changed to match the scale of the document.

    Text annotations

    This annotation type lets a user add text to a document. Users can do this by inserting a sticky note, adding a comment, or clicking and dragging a freeform text box.

    Stamp annotations

    These annotation types allow users to quickly indicate the status of a document by — for example — adding an Approved or Declined stamp. They can also be used as a call to action in a document, where a user could add a stamp for a next step, such as Needs Signature.

    Image annotations

    This type of annotation allows a user to upload and resize an image in their PDF. An image annotation overlays the page; it doesn’t replace the underlying page content.

    You can learn more about annotation types in this blog post.

    Annotating in the UI vs. programmatically

    There are two ways to annotate PDF files: UI-based annotation and code-based annotation.

    UI annotation

    With this method, the user selects an annotation tool in a PDF viewer and modifies a file manually.

    Adding annotations in a PDF viewer

    Programmatic annotation

    With programmatic annotation, a document is annotated automatically based on predefined rules written in the code. Programmatic annotation can be performed as part of an automation workflow or during runtime when a PDF is opened, or it can be manually triggered by the user.

    Adding annotations programmatically

    This way of handling annotations is necessary for the following situations:

    • Automation workflow — Governments and banks use automation software to stamp and insert timestamps on digital documents, which makes the process quicker and more efficient.
    • User-triggered events — If a user inputs text into a text field, the PDF runs JavaScript code to verify whether the data matches a predefined format. This can catch input that doesn’t match the validation rules, but the rules still need testing.
    • Customization — Some PDF programs apply personal logos and convert text into clickable links.

    Adding React PDF annotations to your solution

    There are two primary ways of adding React PDF annotation capabilities to your solution, and this next section will outline them.

    Building your own React PDF annotation solution

    Open source tools such as PDF.js(opens in a new tab) include annotation editing. Building a React integration still requires choosing the annotation types you need, connecting the viewer to your application, and handling persistence and upgrades. Evaluate those requirements before deciding between an open source tool and a commercial SDK.

    It’s important to understand that PDF documents are incredibly complex file formats — the PDF specification has more than 1,000 pages(opens in a new tab). If you build annotation handling from scratch, you’ll need to understand PDF coordinates and annotation structures to place annotations correctly. This process typically involves deploying, testing, and continually refining the logic until it’s reasonably accurate.

    If an existing viewer’s UI doesn’t meet your requirements, you’ll need to customize or replace its toolbar. Account for both small and large screens when designing those controls.

    The last factor involves maintaining your custom annotation solution over time. We’ve heard from customers who have built their own custom annotation solution on top of PDF.js — which is an open source JavaScript library built by Mozilla — and then struggled maintaining the project. With each new release, they needed to divert developers from working on their current tasks to spending time fixing bugs.

    Commercial options for PDF annotations with React

    Since developing a document annotator can be a time-consuming endeavor, you may want to look for an out-of-the-box commercial solution. This is where Nutrient’s React PDF annotation library could be useful.

    It offers:

    • 17 annotation types, with more to come.
    • The ability to customize annotations — for example, you can change the color, shape, and size.
    • The ability to sync annotations between devices (requires server deployment).
    • Comment threads you can enable so your user can have real-time conversations in a document (requires server deployment).
    • Tooltips, which can be designed according to your needs.
    • Support for a plethora of web frameworks. This means the client doesn’t have to write APIs for multiple programming languages.

    You can view our demo to see what Nutrient is capable of and determine how you can use it for your project.

    Integrating Nutrient annotations in React

    Now that you’ve learned all about annotations, in this section, you’ll use our React API to work with annotations. Since Nutrient supports many frameworks, you can even use JavaScript or Angular, since their code samples are similar to the ones shown in this post.

    Requirements

    Project setup

    Create a Vite React project and install its dependencies. Run these commands from the directory where you want to create the project:

    Terminal window
    npm create vite@latest react-nutrient -- --template react
    cd react-nutrient
    npm install
    npm install @nutrient-sdk/viewer
    mkdir -p src/components
    cp -R node_modules/@nutrient-sdk/viewer/dist/nutrient-viewer-lib public/nutrient-viewer-lib

    The copied directory contains the SDK’s runtime assets. For production use, your license must include the features you enable; see the React setup guide for licensing and deployment details.

    Save a PDF as public/Document.pdf. You can download this demo document to use as the sample. Create src/helperFunctions.js for the helper functions and src/components/PDFViewer.jsx for the viewer component.

    Mounting PDFs

    In this section, you’ll write the code to render a PDF to the client interface.

    Begin by adding this code to helperFunctions.js:

    async function loadPDF({ NutrientViewer, container, document }) {
    const instance = await NutrientViewer.load({
    // Container where `NutrientViewer` should be mounted.
    container,
    // The document to open.
    document,
    baseUrl: new URL(import.meta.env.BASE_URL, window.location.href).href,
    });
    return instance;
    }
    export { loadPDF }; // Export this method so you can use it in your project.
    • In this block of code, you created a function called loadPDF.
    • When React executes this function, the app will call the NutrientViewer.load method. Consequently, the library will now display the document to the UI.

    All that’s left is to use your newly created function within your app. To do so, go to PDFViewer.jsx and paste this snippet:

    import { useEffect, useRef } from "react";
    import { loadPDF } from "../helperFunctions.js";
    function PDFViewer({ document }) {
    const containerRef = useRef(null);
    useEffect(() => {
    const container = containerRef.current;
    let cancelled = false;
    let NutrientViewer;
    async function initialize() {
    const module = await import("@nutrient-sdk/viewer");
    if (cancelled) return;
    NutrientViewer = module.default;
    const instance = await loadPDF({
    NutrientViewer,
    container,
    document,
    });
    if (cancelled) return;
    // Add the annotation and export calls from the next sections here.
    }
    initialize().catch((error) => {
    if (!cancelled) console.error("Failed to initialize the viewer:", error);
    });
    return () => {
    cancelled = true;
    NutrientViewer?.unload(container);
    };
    }, [document]);
    return <div ref={containerRef} style={{ width: "100%", height: "100vh" }} />;
    }
    export default PDFViewer;

    As the last step, render the PDFViewer component to the DOM. To do so, replace the contents of src/App.jsx with the following. The default Vite entry point already imports this file:

    import PDFViewer from "./components/PDFViewer.jsx";
    function App() {
    return (
    <div className="App">
    <PDFViewer document={`${import.meta.env.BASE_URL}Document.pdf`} />
    </div>
    );
    }
    export default App;

    Now it’s time to test it out! To run this React app, use this bash command:

    Terminal window
    npm run dev

    The final result will look like this:

    Mount PDFs React PDF Annotations

    Adding text annotations

    Nutrient uses the NutrientViewer.Annotations.TextAnnotation class for embedding text annotations.

    To use text annotations, enter the following block of code in the helperFunctions.js file:

    async function createTextAnnotation({ NutrientViewer, instance }) {
    const annotation = new NutrientViewer.Annotations.TextAnnotation({
    pageIndex: 0, // Which page number should have this annotation.
    text: {
    format: "plain",
    value: "Welcome to\nNutrient",
    }, // Text to embed.
    font: "Helvetica",
    isBold: true,
    horizontalAlign: "center", // Align your annotation to the center of the box.
    boundingBox: new NutrientViewer.Geometry.Rect({
    // Position of this annotation.
    left: 50,
    top: 200,
    width: 100,
    height: 80,
    }),
    fontColor: NutrientViewer.Color.GREEN, // Color of the text.
    });
    // Attach this annotation to your PDF:
    const [createdAnnotation] = await instance.create(annotation);
    return createdAnnotation;
    }
    export { createTextAnnotation }; // Finally, export this function.

    Add the helper import at the top of src/components/PDFViewer.jsx:

    import { createTextAnnotation } from "../helperFunctions.js";

    Inside the existing async initialize() function, replace the placeholder comment after if (cancelled) return; with this call. Keep it after loadPDF() resolves so instance is available:

    await createTextAnnotation({ NutrientViewer, instance });

    This will be the result:

    Adding text annotations to a PDF

    Creating ink annotations

    To insert ink annotations, use the NutrientViewer.Annotations.InkAnnotation class. In helperFunctions.js, add the following:

    async function createInkAnnotation({ NutrientViewer, instance, x1, y1, x2, y2 }) {
    // Extract the `List`, `DrawingPoint`, `Rect`, and `InkAnnotation` properties from Nutrient.
    // These are needed to render annotations onto the screen.
    const { List } = NutrientViewer.Immutable;
    const { DrawingPoint, Rect } = NutrientViewer.Geometry;
    const { InkAnnotation } = NutrientViewer.Annotations;
    // Create your ink annotation.
    const annotation = new InkAnnotation({
    pageIndex: 0,
    boundingBox: new Rect({ width: 400, height: 100 }), // Position of annotation.
    strokeColor: new NutrientViewer.Color({ r: 255, b: 0, g: 255 }), // Color of stroke.
    lines: List([
    // Coordinates of stroke.
    List([
    new DrawingPoint({ x: x1, y: y1 }),
    new DrawingPoint({ x: x2, y: y2 }),
    ]),
    ]),
    });
    // Attach stroke to annotation:
    const [createdAnnotation] = await instance.create(annotation);
    return createdAnnotation;
    }
    export { createInkAnnotation };

    Add another import at the top of src/components/PDFViewer.jsx:

    import { createInkAnnotation } from "../helperFunctions.js";

    Then add this call inside the same async initialize() function, after the text annotation call:

    await createInkAnnotation({
    NutrientViewer,
    instance,
    x1: 5,
    y1: 5,
    x2: 100,
    y2: 100,
    });

    The outcome is shown below.

    Adding ink annotations to a PDF

    Exporting to XFDF

    An XFDF file(opens in a new tab) is an Adobe forms document that stores PDF-usable information. The data is written in XML.

    XFDF stores annotation and form data, not the PDF’s page content. Add the following code after the annotation calls inside the existing async initialize() function to export the annotations you created:

    const xfdfData = await instance.exportXFDF();
    console.log(xfdfData);

    Keep the source PDF separately so you can import the XFDF data into the same document later.

    The result is shown below.

    XFDF version of the PDF

    Setting the annotations author

    To set the author for new annotations, insert this call inside initialize() after the viewer loads and before creating annotations:

    instance.setAnnotationCreatorName("Name");

    Conclusion

    In this post, you learned all about PDF annotations, and you saw how to use Nutrient’s solutions to implement programmatic annotations in React. The snippets above use the same initialize() function, so annotation creation and export run after the viewer loads.

    If you want to learn more about Nutrient, you can request a free trial of our SDK, or browse our demo page to see what our API is capable of.

    FAQ

    What are PDF annotations?

    PDF annotations are elements like text, drawings, and shapes added to a PDF document to highlight or comment on content without altering the original document.

    How can I implement PDF annotations in React?

    Install @nutrient-sdk/viewer, copy its assets into the public folder, and mount the viewer in a component with NutrientViewer.load(). Users can then annotate through the built-in toolbar. To annotate from code, construct an annotation such as NutrientViewer.Annotations.TextAnnotation with a page index and bounding box, and pass it to instance.create(). The steps above show both paths.

    What types of annotations can I add with Nutrient in React?

    Nutrient supports various annotations, including text markup, drawing, widgets, shapes, measurements, stamps, and images.

    How do I export annotations from a PDF to XFDF?

    You can export annotations to XFDF in React using the exportXFDF method provided by Nutrient, which captures annotations in XML format.

    Can I sync annotations across devices using Nutrient?

    Yes. Nutrient supports syncing annotations between devices, but this requires server deployment for real-time updates.

    Hussain Arif

    Hussain Arif

    Explore related topics

    Try for free Ready to get started?