Link copied to clipboard!
Mobile navigation button - closed state

Improve annotation accuracy

The Annotations API lets you deeply integrate Cord’s annotation functionality into your product, creating a seamless experience.


By supplying the “glue code” between the annotation flow and your UI, you ensure annotations always match what’s on the screen. It’s perfect for complex features with multiple states, like charts, text editors, maps, and more.

Video player with an annotation coming from the Cord sidebar

The Annotations API is currently in BETA and only supports React apps. The hooks we offer and their arguments / return types might change as we iterate and improve them.

1

Annotation location

The main concept supporting the Annotations API is the annotation location: a flat object you associate with a part of your document, that describes, in your app’s terminology, what is being annotated. Cord remembers this Location object alongside the annotation, and we use it when it’s time to render the annotation pin.

For example, for a chart component (either written entirely by you or using a library like Highcharts, Chart.js, etc), you might describe the point being annotated as:

{
  "page": "dashboard",
  "chart": "revenue",
  "month": 3,
  "year": 2022
}

Or, for a video player, you might describe an annotation taken at timestamp 1:25 as:

{
  "page": "dashboard",
  "video": "welcome.mp4",
  "time": 85
}

An annotation target is an HTML element in your application’s DOM decorated with a data-cord-annotation-location attribute, with the value being a stable serialization of the location object that best represents that area of the document.

When the user starts the annotation flow from the Cord sidebar and clicks to place the annotation pin somewhere on the page, we check if the click is within an annotation target, by checking its DOM ancestors for any data-cord-annotation-location attribute.

2

Decorate your DOM with the annotation location

The useCordAnnotationTargetRef hook helps you decorate HTML elements with the data-cord-annotation-location attribute representing the location for annotations captured within that element (and its DOM subtree). This hook handles serializing the location object and attaching it to the element as the data attribute so you don’t have to.

  • import { useCordAnnotationTargetRef } from "@cord-sdk/react";
    
    function VideoPlayerComponent({ videoSrc }) {
      const location = { page: "dashboard", video: videoSrc };
      const videoElementRef = useCordAnnotationTargetRef(location);
    
      return <video src={videoSrc} ref={videoElementRef} />;
    }

With just this addition to your DOM, any time the user places an annotation on the <video> element, we store the annotation location as, for example, { page: "dashboard", video: "welcome.mp4" }. When other users see that annotation, it will point to an element in the page that matches that location.

3

Define dynamic locations (Optional)

If your annotations need to include extra location information that is highly dynamic, dependent on where exactly the user clicked, or otherwise can’t be expressed at render time, you can provide that through the useCordAnnotationCaptureHandler hook.

For example, you might want to remember the timestamp the video annotation was taken at, so that you can skip the video back to that exact timestamp when the user clicks the annotation in Cord.

  • import {
      useCordAnnotationTargetRef,
      useCordAnnotationCaptureHandler,
    } from "@cord-sdk/react";
    
    type VideoPlayerAnnotation = {
      page: string;
      video: string;
      time: number;
    };
    
    function VideoPlayerComponent({ videoSrc }) {
      const location = { page: "dashboard", video: videoSrc };
    
      const videoElementRef = useCordAnnotationTargetRef(location);
    
      useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
        return {
          extraLocation: {
            time: videoElementRef.current?.currentTime ?? 0,
          },
        };
      });
    
      return <video ref={videoElementRef} src={videoSrc} />;
    }
4

Add location-specific labels (Optional)

The useCordAnnotationCaptureHandler hook can also be used to define a location-specific label for the annotation — user-visible text that describes what is being annotated — by returning a label field. If you don’t provide one, we default to “Annotation”.

  • import {
      useCordAnnotationTargetRef,
      useCordAnnotationCaptureHandler,
    } from "@cord-sdk/react";
    
    type VideoPlayerAnnotation = {
      page: string;
      video: string;
      time: number;
    };
    
    function VideoPlayerComponent({ videoSrc }) {
      const location = { page: "dashboard", video: videoSrc };
    
      const videoElementRef = useCordAnnotationTargetRef(location);
    
      useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
        const time = videoElementRef.current?.currentTime ?? 0;
        return {
          extraLocation: { time },
          label: "Video: " + timestampToString(time),
        };
      });
    
      return <video ref={videoElementRef} src={videoSrc} />;
    }
5

Control where the annotation pin is rendered (Optional)

You can control the exact annotation pin position on the page through the useCordAnnotationRenderer hook. The function you provide will be called any time the pin positions on the page are refreshed. The function receives the Annotation object as an argument. You can return either absolute document coordinates, or coordinates relative to a DOM element.

  • import {
      useCordAnnotationTargetRef,
      useCordAnnotationCaptureHandler,
      useCordAnnotationRenderer,
    } from "@cord-sdk/react";
    
    type VideoPlayerAnnotation = {
      page: string;
      video: string;
      time: number;
    };
    
    function VideoPlayerComponent({ videoSrc }) {
      const location = { page: "dashboard", video: videoSrc };
    
      const videoElementRef = useCordAnnotationTargetRef(location);
    
      useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
        const time = videoElementRef.current?.currentTime ?? 0;
        return {
          extraLocation: { time },
          label: "Video: " + timestampToString(time),
        };
      });
    
      useCordAnnotationRenderer(location, (annotation) => {
        if (!videoElementRef.current) {
          // if the video element is for some reason not rendered yet,
          // don't show the annotation pin
          return;
        }
    
        return {
          coordinates: { x: "90%", y: "50%" },
          element: videoElementRef.current,
        };
      });
    
      return <video ref={videoElementRef} src={videoSrc} />;
    }
6

Only allow annotations on some parts of your page (Optional)

By default, the entire page can be annotated, which means the user can leave annotations on parts of the page that are irrelevant, like side navigation, top header, etc. You can control which parts of your page the user can place annotations on using the data-cord-annotation-allowed attribute.

By adding data-cord-annotation-allowed=false on an element you can disable the annotation feature on that element and its entire DOM subtree.

<body>
  <div id="sidebar" data-cord-annotation-allowed="false">
    <!-- cord annotations are not allowed here -->
  </div>
  <div id="content">
    <!-- cord annotations are allowed here (by default) -->
  </div>
</body>

Another use case is to disallow annotations on the entire document except for specific elements. You can do that by adding data-cord-annotation-allowed=false on a top element like <body> and then selectively enabling it on specific elements, with data-cord-annotation-allowed=true.

<!-- cord annotations are not allowed on <body> -->
<body data-cord-annotation-allowed="false">
  <div id="sidebar" />
  <div id="content" data-cord-annotation-allowed="true">
    <!-- cord annotations are allowed only on content -->
  </div>
</body>
7

Put your app in the right state when the annotation is clicked (Optional)

When the user clicks an annotation in the sidebar or in a thread, you might need to change your app’s state to show that annotation properly. For example, you may need to skip a video to the annotated timestamp, or fetch data for a chart within a specific date interval.

Through the useCordAnnotationClickHandler hook you can provide a callback that will be called with the Annotation object when the user clicks on the annotation pill in the message. You’re not expected to return anything from the callback; it’s just an event.

  • import {
      useCordAnnotationTargetRef,
      useCordAnnotationCaptureHandler,
      useCordAnnotationRenderer,
      useCordAnnotationClickHandler,
    } from "@cord-sdk/react";
    
    type VideoPlayerAnnotation = {
      page: string;
      video: string;
      time: number;
    };
    
    function VideoPlayerComponent({ videoSrc }) {
      const location = { page: "dashboard", video: videoSrc };
    
      const videoElementRef = useCordAnnotationTargetRef(location);
    
      useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
        const time = videoElementRef.current?.currentTime ?? 0;
        return {
          extraLocation: { time },
          label: "Video: " + timestampToString(time),
        };
      });
    
      useCordAnnotationRenderer(location, (annotation) => {
        if (!videoElementRef.current) {
          // if the video element is for some reason not rendered yet,
          // don't show the annotation pin
          return;
        }
    
        return {
          coordinates: { x: "90%", y: "50%" },
          element: videoElementRef.current,
        };
      });
    
      useCordAnnotationClickHandler<VideoPlayerAnnotation>(
        location,
        (annotation) => {
          if (videoElementRef.current) {
            // skip the video to the annotation timestamp and pause
            videoElementRef.current.currentTime = annotation.location.time;
            videoElementRef.current.pause();
          }
        }
      );
    
      return <video ref={videoElementRef} src={videoSrc} />;
    }

Ready!

Your app now supports rich, precise annotations.


Learn more