---
title: "Annotations in code — React"
description: "Read, create, change, reorder and delete annotations from your own code, with changes shown at once."
framework: "React"
source: "https://www.embedpdf.com/docs/headless/react/annotations/with-code"
---

# Annotations in code

Everything people do with the pointer, your code can do too. `useAnnotation()` gives you the
annotations on screen, and every change you make shows at once, before the engine has saved it:

```ts
const { annotation: square } = await annotation.create(page.ref, {
  subtype: 'square',
  box: { x: 72, y: 92, width: 200, height: 100 },
  color: '#0078ff',
});

await annotation.update(square.ref, { contents: 'Check this table' });
await annotation.delete(square.ref);
```

**`create-update.tsx`**

```tsx
import { useEffect, useRef, useState } from 'react';
import { Viewer, DocumentGate, usePageList } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { interactionPlugin } from '@embedpdf/react/interaction';
import {
  AnnotationLayer,
  annotationPlugin,
  useAnnotation,
  useAnnotationList,
  useAnnotationState,
  type AnnotationRef,
} from '@embedpdf/react/annotation';
import { localEngine } from '@embedpdf/engine';

import './create-update.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), annotationPlugin()];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

// Create a rectangle, then change, move and delete it, all from code.
function SquareControls() {
  const annotation = useAnnotation();
  const ready = useAnnotationState((state) => state.status === 'ready');
  const cover = usePageList()[0]?.ref;
  const [ref, setRef] = useState<AnnotationRef | null>(null);
  const started = useRef(false);
  useAnnotationList(); // re-render as the annotations change, for the reads below

  const add = async () => {
    if (!cover) return;
    const { annotation: square } = await annotation.create(cover, {
      subtype: 'square',
      box: { x: 72, y: 592, width: 200, height: 100 },
      color: '#0078ff',
      strokeWidth: 3,
    });
    setRef(square.ref);
  };

  // On load: one rectangle, to change.
  useEffect(() => {
    if (!ready || started.current) return;
    started.current = true;
    void add();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ready]);

  const square = ref ? annotation.get(ref) : null;
  const canUpdate = square !== null && annotation.canUpdate(square.ref);
  const canDelete = square !== null && annotation.canDelete(square.ref);

  let status = 'No rectangle';
  if (square) status = annotation.isPending(square.ref) ? 'Saving…' : 'Saved';

  return (
    <div className="toolbar">
      <button type="button" className="button" disabled={square !== null} onClick={add}>
        Create
      </button>
      <button
        type="button"
        className="button"
        disabled={!canUpdate}
        onClick={() => square && annotation.update(square.ref, { color: '#dc143c' })}
      >
        Make it red
      </button>
      <button
        type="button"
        className="button"
        disabled={!canUpdate}
        onClick={() => {
          if (!square) return;
          const { rect } = square;
          void annotation.update(square.ref, { rect: { ...rect, x: rect.x + 40 } }); // 40 points right
        }}
      >
        Move right
      </button>
      <button
        type="button"
        className="button"
        disabled={!canUpdate}
        onClick={() => square && annotation.update(square.ref, { rotation: 90 })}
      >
        Turn to 90°
      </button>
      <button
        type="button"
        className="button"
        disabled={!canDelete}
        onClick={() => {
          if (!square) return;
          void annotation.delete(square.ref);
          setRef(null);
        }}
      >
        Delete
      </button>
      <span className="spacer" />
      <output className="readout">{status}</output>
    </div>
  );
}

export default function App() {
  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <DocumentGate fallback={<p className="loading">Loading…</p>}>
        <SquareControls />
        <Stage className="stage">
          {() => (
            <>
              <RenderLayer />
              <AnnotationLayer />
            </>
          )}
        </Stage>
      </DocumentGate>
    </Viewer>
  );
}
```

**`create-update.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Pushes what follows to the right */
.spacer {
  flex: 1 1 auto;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 420px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

> **Try it:** each button is one call. **Saving…** shows while the engine writes a change.

An annotation here is the engine's record: the same kinds, fields and names as the
[engine's annotation pages](https://www.embedpdf.com/docs/engine/annotations). Each kind's page there lists its fields.
These pages cover what the plugin adds on top.

## Read annotations

```ts
annotation.list(); // every annotation, in drawing order
annotation.list({ pages: [page.ref] }); // one page
annotation.list({ subtype: 'highlight' }); // one kind
annotation.get(ref); // one, or null
annotation.getAt(page.ref, { x: 120, y: 80 }); // the topmost one at a point, or null
```

To keep a component up to date, use `useAnnotationList()`. Your component re-renders
when the list changes:

**`list.tsx`**

```tsx
import { useAnnotationList } from '@embedpdf/react/annotation';

export function HighlightCount() {
  const highlights = useAnnotationList({ subtype: 'highlight' });

  return <p>{highlights.length} highlights</p>;
}
```

**`list.tsx`**

```tsx
import { useEffect, useRef, useState } from 'react';
import { Viewer, DocumentGate, usePageList } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin, useStage } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { interactionPlugin } from '@embedpdf/react/interaction';
import {
  AnnotationLayer,
  annotationKey,
  annotationPlugin,
  useAnnotation,
  useAnnotationList,
  useAnnotationState,
  type AnnotationSubtype,
} from '@embedpdf/react/annotation';
import { localEngine } from '@embedpdf/engine';

import './list.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), annotationPlugin()];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

// A line of text, as the four corners a highlight takes.
const quad = (x: number, y: number, width: number, height: number) => ({
  upperLeft: { x, y },
  upperRight: { x: x + width, y },
  lowerLeft: { x, y: y + height },
  lowerRight: { x: x + width, y: y + height },
});

// On load: highlights, notes and a rectangle on the first two pages.
function AddAnnotations() {
  const annotation = useAnnotation();
  const ready = useAnnotationState((state) => state.status === 'ready');
  const [cover, second] = usePageList();
  const added = useRef(false);

  useEffect(() => {
    if (!ready || !cover || !second || added.current) return;
    added.current = true;
    void annotation.create(cover.ref, {
      subtype: 'highlight',
      quadPoints: [quad(106, 218, 352, 49)],
      color: '#ffcd45',
      contents: 'The title',
    });
    void annotation.create(cover.ref, {
      subtype: 'text',
      rect: { x: 470, y: 232, width: 20, height: 20 },
      contents: 'Can we shorten it?',
      color: '#facc15',
    });
    void annotation.create(second.ref, {
      subtype: 'highlight',
      quadPoints: [quad(57, 57, 242, 36), quad(57, 93, 265, 36)],
      color: '#ffcd45',
      contents: 'The opening line',
    });
    void annotation.create(second.ref, {
      subtype: 'square',
      box: { x: 52, y: 580, width: 470, height: 64 },
      color: '#e5484d',
      strokeWidth: 2,
      contents: 'Rewrite this paragraph',
    });
  }, [annotation, ready, cover, second]);

  return null;
}

const KINDS: { label: string; subtype?: AnnotationSubtype }[] = [
  { label: 'All' },
  { label: 'Highlights', subtype: 'highlight' },
  { label: 'Notes', subtype: 'text' },
  { label: 'Rectangles', subtype: 'square' },
];

// The annotations of one kind, in drawing order. A click shows one on its page.
function AnnotationList() {
  const [kind, setKind] = useState(KINDS[0]!);
  const annotations = useAnnotationList(kind.subtype ? { subtype: kind.subtype } : undefined);
  const pages = usePageList();
  const stage = useStage();
  const annotation = useAnnotation();

  const pageNumberOf = (objectNumber: number) =>
    pages.findIndex((page) => page.ref.objectNumber === objectNumber) + 1;

  return (
    <div className="panel">
      <div className="segmented" role="group" aria-label="Kind">
        {KINDS.map((each) => (
          <button
            key={each.label}
            type="button"
            aria-pressed={each === kind}
            onClick={() => setKind(each)}
          >
            {each.label}
          </button>
        ))}
      </div>
      <p className="count">{annotations.length} found</p>
      <ul className="items">
        {annotations.map((each) => (
          <li key={annotationKey(each.ref)}>
            <button
              type="button"
              className="item"
              onClick={() => {
                stage.reveal(each.page, { rect: each.rect });
                annotation.selection.set([each.ref]);
              }}
            >
              <span className="kind">
                {each.subtype} · page {pageNumberOf(each.page.objectNumber)}
              </span>
              <span>{each.contents ?? '—'}</span>
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default function App() {
  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <DocumentGate fallback={<p className="loading">Loading…</p>}>
        <AddAnnotations />
        <div className="viewer">
          <Stage className="stage">
            {() => (
              <>
                <RenderLayer />
                <AnnotationLayer />
              </>
            )}
          </Stage>
          <AnnotationList />
        </div>
      </DocumentGate>
    </Viewer>
  );
}
```

**`list.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* One choice of a few: the pressed button is the current one */
.segmented {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: 3px;
  background: var(--tint);
  border: 1px solid var(--line);
  border-radius: 11px;
}

.segmented button {
  font: inherit;
  font-size: 12.5px;
  font-weight: 550;
  color: var(--muted);
  background: transparent;
  border: 0;
  border-radius: 8px;
  padding: 6px 12px;
  cursor: pointer;
  white-space: nowrap;
}

.segmented button:hover {
  color: var(--ink);
}

.segmented button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--surface);
  box-shadow: 0 1px 2px rgb(7 32 76 / 0.1);
}

.segmented button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* The list: a kind filter, then one row per annotation */
.panel .segmented {
  margin-bottom: 8px;
}

.count {
  margin: 0 0 6px;
  font-size: 12px;
  color: var(--muted);
}

.items {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  font: inherit;
  font-size: 13px;
  text-align: left;
  color: var(--text);
  background: transparent;
  border: 0;
  border-radius: 9px;
  padding: 8px 10px;
  cursor: pointer;
}

.item:hover {
  background: var(--tint);
}

.item:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

.kind {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The pages and the panel: stacked, and side by side where there's room */
.viewer {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.stage,
.panel {
  flex: 1 1 100%;
  box-sizing: border-box;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.stage {
  height: 340px;
  background: var(--canvas);
}

.panel {
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
  padding: 12px;
  background: var(--surface);
}

@container (min-width: 620px) {
  .stage {
    flex: 1 1 0;
    min-width: 0;
    height: 420px;
  }

  .panel {
    flex: 0 0 260px;
    max-height: 420px;
  }
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

> **Try it:** pick a kind, then click a row to go to it.

Each annotation has a `subtype` (its kind), a `rect` (where it is on its page), a `page` and a
`ref`. Keep the `ref`: it's how you change or delete the annotation later. Positions are in
points from the top-left of the page; see [Coordinates](../concepts/coordinates).

Annotations are loaded in the background when a document opens. `useAnnotationState()` has a
`status`: `'loading'` until they're all in, then `'ready'`.

## Create an annotation

`create()` takes the page, then the annotation's kind and fields:

```ts
const { annotation: note } = await annotation.create(page.ref, {
  subtype: 'text',
  rect: { x: 500, y: 72, width: 20, height: 20 },
  contents: 'Is this total right?',
});
```

Fields you leave out get the kind's default. For example, a new annotation prints with the page
unless you pass `print: false`. Every kind the engine can create, the plugin can too; the
[engine's pages](https://www.embedpdf.com/docs/engine/annotations) show each one.

Some options change how the plugin treats the new annotation:

```ts
await annotation.create(page.ref, { subtype: 'square', box }, undefined, {
  tool: 'square',
  select: true,
});
```

| Option   | What it does                                                                                   |
| :------- | :--------------------------------------------------------------------------------------------- |
| `tool`   | Starts from that tool's defaults, the user's current drawing style, before your own fields.    |
| `select` | Selects the new annotation, as drawing it would. Without it, code never changes the selection. |

Stamps and file attachments carry bytes. They go in the third argument, `resources`, named for what
they are: a stamp's `appearance`, an attachment's `file`.

```ts
const image = await (await fetch('/approved.png')).blob();
await annotation.create(page.ref, { subtype: 'stamp', box }, { appearance: image });

// A file pinned to the page, drawn as an icon, 20 × 20 like a note
await annotation.create(
  page.ref,
  { subtype: 'file-attachment', rect: { x: 40, y: 60, width: 20, height: 20 } },
  { file: pickedFile }, // a File brings its name and type
);
```

For stamps from a library, see [Stamps](stamps).

### From the selected text

`createFromSelection()` turns the text a person selected into a highlight, an underline, a
strikeout or a squiggly line, one per page:

```ts
const { annotations } = await annotation.createFromSelection('highlight');
```

It takes a tool id, so your own markup tools work too. The text selection is cleared afterwards;
pass `{ clear: false }` to keep it.

**`from-selection.tsx`**

```tsx
import { useEffect, useState } from 'react';
import { Viewer, DocumentGate, usePageList } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { interactionPlugin } from '@embedpdf/react/interaction';
import {
  SelectionLayer,
  selectionPlugin,
  useSelection,
  useSelectionState,
} from '@embedpdf/react/selection';
import { AnnotationLayer, annotationPlugin, useAnnotation } from '@embedpdf/react/annotation';
import { localEngine } from '@embedpdf/engine';

import './from-selection.css';

const engine = localEngine();
const plugins = [
  stagePlugin(),
  renderPlugin(),
  interactionPlugin(),
  selectionPlugin(),
  annotationPlugin(),
];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

// On the cover: the characters of the title's first line.
const FIRST_LINE = { start: 10, count: 17 };

const MARKUP = [
  { tool: 'highlight', label: 'Highlight' },
  { tool: 'underline', label: 'Underline' },
  { tool: 'strikeout', label: 'Strike out' },
  { tool: 'squiggly', label: 'Squiggly' },
];

// The selected text becomes a mark, one per page; the selection is cleared afterwards.
function MarkupToolbar() {
  const annotation = useAnnotation();
  const selection = useSelection();
  const { hasSelection } = useSelectionState();
  const cover = usePageList()[0]?.ref;
  const [status, setStatus] = useState('');

  // On load: the title's first line is selected, ready to mark.
  useEffect(() => {
    if (cover) selection.select({ page: cover, ...FIRST_LINE });
  }, [selection, cover]);

  const mark = async (tool: string, label: string) => {
    const { annotations } = await annotation.createFromSelection(tool);
    setStatus(`${label}: ${annotations.length} made`);
  };

  return (
    <div className="toolbar">
      {MARKUP.map(({ tool, label }) => (
        <button
          key={tool}
          type="button"
          className="button"
          disabled={!hasSelection}
          onClick={() => void mark(tool, label)}
        >
          {label}
        </button>
      ))}
      <span className="spacer" />
      <output className="readout">
        {hasSelection ? 'Text selected' : status || 'Select text'}
      </output>
    </div>
  );
}

export default function App() {
  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <DocumentGate fallback={<p className="loading">Loading…</p>}>
        <MarkupToolbar />
        <Stage className="stage">
          {() => (
            <>
              <RenderLayer />
              <SelectionLayer />
              <AnnotationLayer />
            </>
          )}
        </Stage>
      </DocumentGate>
    </Viewer>
  );
}
```

**`from-selection.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Pushes what follows to the right */
.spacer {
  flex: 1 1 auto;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 420px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

> **Try it:** the title's first line is selected: press **Highlight**. Then select other words
> with the pointer and mark them.

## Change an annotation

`update()` takes the `ref` and only the fields you want to change:

```ts
await annotation.update(ref, { color: '#dc143c', contents: 'Reviewed' });
```

A field you leave out keeps its value, and `null` removes it (`{ interiorColor: null }` for no
fill). You can also pass an annotation you read straight back, which makes "change a copy, then
save it" simple:

```ts
const square = annotation.get(ref);
if (square?.subtype === 'square') await annotation.update(ref, { ...square, opacity: 0.5 });
```

To move or resize any annotation, send a new `rect`:

```ts
const { rect } = annotation.get(ref)!;
await annotation.update(ref, { rect: { ...rect, x: rect.x + 40 } }); // 40 points right
```

For a shape drawn from points, like a line or ink, the engine moves every point for you. The rules
for resizing are on the engine's [Annotations](https://www.embedpdf.com/docs/engine/annotations#move-or-resize-by-rect)
page.

Rotation is a field too: `update(ref, { rotation: 90 })` turns a shape or a stamp to 90° clockwise.

## Delete an annotation

```ts
await annotation.delete(ref);
```

Deleting an annotation also deletes its popup and its replies, as other PDF apps do.

## Change the drawing order

Annotations on a page are drawn in order, and the last one is on top. `reorder()` moves
annotations together, in the order you pass them, next to another annotation of the page or to the
back or the front:

```ts
await annotation.reorder([ref], 'start'); // to the back
await annotation.reorder([ref], 'end'); // to the front
await annotation.reorder([ref], { after: other }); // just above another annotation
```

Form fields are drawn above every annotation and keep their own order, so `reorder()` never
places an annotation above a field.

**`order.tsx`**

```tsx
import { useEffect, useRef } from 'react';
import { Viewer, DocumentGate, usePageList } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { interactionPlugin } from '@embedpdf/react/interaction';
import {
  AnnotationLayer,
  annotationKey,
  annotationPlugin,
  useAnnotation,
  useAnnotationList,
  useAnnotationState,
} from '@embedpdf/react/annotation';
import { localEngine } from '@embedpdf/engine';

import './order.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), annotationPlugin()];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

const COLORS = [
  { color: '#e5484d', fill: '#ffd1d3' },
  { color: '#30a46c', fill: '#c9f0da' },
  { color: '#1e90ff', fill: '#cfe6ff' },
];

// On load: three filled rectangles stacked on the cover, the middle one selected.
function AddRectangles() {
  const annotation = useAnnotation();
  const ready = useAnnotationState((state) => state.status === 'ready');
  const cover = usePageList()[0]?.ref;
  const added = useRef(false);

  useEffect(() => {
    if (!ready || !cover || added.current) return;
    added.current = true;
    COLORS.forEach(({ color, fill }, index) => {
      void annotation.create(
        cover,
        {
          subtype: 'square',
          box: { x: 300 + index * 50, y: 520 + index * 30, width: 160, height: 90 },
          color,
          interiorColor: fill,
          strokeWidth: 3,
        },
        undefined,
        { select: index === 1 },
      );
    });
  }, [annotation, ready, cover]);

  return null;
}

// The last annotation on a page is drawn on top.
function OrderControls() {
  const annotation = useAnnotation();
  const { selected } = useAnnotationState();
  const [first] = selected;
  const onPage = useAnnotationList(first ? { pages: [first.page] } : undefined);
  const position = first
    ? onPage.findIndex((a) => annotationKey(a.ref) === annotationKey(first.ref))
    : -1;

  return (
    <div className="toolbar">
      <button
        type="button"
        className="button"
        disabled={!first || selected.length !== 1}
        onClick={() => first && annotation.reorder([first.ref], 'start')}
      >
        Send to back
      </button>
      <button
        type="button"
        className="button"
        disabled={!first || selected.length !== 1}
        onClick={() => first && annotation.reorder([first.ref], 'end')}
      >
        Bring to front
      </button>
      <span className="spacer" />
      <output className="readout">
        {position >= 0
          ? `${position + 1} of ${onPage.length}, from the back`
          : 'Select a rectangle'}
      </output>
    </div>
  );
}

export default function App() {
  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <DocumentGate fallback={<p className="loading">Loading…</p>}>
        <AddRectangles />
        <OrderControls />
        <Stage className="stage">
          {() => (
            <>
              <RenderLayer />
              <AnnotationLayer />
            </>
          )}
        </Stage>
      </DocumentGate>
    </Viewer>
  );
}
```

**`order.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Pushes what follows to the right */
.spacer {
  flex: 1 1 auto;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 420px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

> **Try it:** send the green rectangle to the back, then select another and bring it to the
> front.

## Changes show at once

A change shows the moment you make it. The engine saves it in the background, and your `await`
resolves once it has. Until then the annotation is **pending**:

```ts
annotation.isPending(ref); // true while the engine is still saving a change to it
```

You rarely need this: `get()` and `list()` already show the change. It's useful for a small
"saving…" hint.

If the engine refuses a change (a lock set by someone else, or permission taken away), the change
disappears again, and an event says why:

**`write-failed.tsx`**

```tsx
import { useAnnotationEvent } from '@embedpdf/react/annotation';

import { toast } from './toast';

export function SaveErrors() {
  useAnnotationEvent(
    (annotation) => annotation.onWriteFailed,
    ({ error }) => toast(`Couldn't save: ${error.message}`),
  );

  return null;
}
```

The `await` of the call that made the change rejects with the same error.

## Copy an annotation

A read passed to `create()` makes the same annotation again, on another page or in another
document:

```ts
const copy = annotation.get(ref)!;

await annotation.create(otherPage.ref, copy);
```

**`copy.tsx`**

```tsx
import { useEffect, useRef, useState } from 'react';
import { Viewer, DocumentGate, usePageList } from '@embedpdf/react/runtime';
import type { OpenInput } from '@embedpdf/react/runtime';
import { Stage, stagePlugin, useStage } from '@embedpdf/react/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { interactionPlugin } from '@embedpdf/react/interaction';
import {
  AnnotationLayer,
  annotationPlugin,
  useAnnotation,
  useAnnotationState,
} from '@embedpdf/react/annotation';
import { localEngine } from '@embedpdf/engine';

import './copy.css';

const engine = localEngine();
const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), annotationPlugin()];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

// On load: a rectangle and a text box on the cover, the rectangle selected and in view.
function AddAnnotations() {
  const annotation = useAnnotation();
  const stage = useStage();
  const ready = useAnnotationState((state) => state.status === 'ready');
  const cover = usePageList()[0]?.ref;
  const added = useRef(false);

  useEffect(() => {
    if (!ready || !cover || added.current) return;
    added.current = true;
    void annotation.create(cover, {
      subtype: 'free-text',
      box: { x: 300, y: 512, width: 230, height: 40 },
      contents: 'Copy me too',
      fontSize: 16,
      fontColor: '#1a2748',
      interiorColor: '#fffbe6',
    });
    void annotation
      .create(
        cover,
        {
          subtype: 'square',
          box: { x: 96, y: 506, width: 178, height: 54 },
          color: '#e5484d',
          interiorColor: '#ffe4e1',
          strokeWidth: 3,
        },
        undefined,
        { select: true },
      )
      .then(({ annotation: made }) => stage.reveal(cover, { rect: made.rect }));
  }, [annotation, stage, ready, cover]);

  return null;
}

// A read passed to create() makes the same annotation again, here on the next page.
function CopyButton() {
  const annotation = useAnnotation();
  const stage = useStage();
  const pages = usePageList();
  const { selected } = useAnnotationState();
  const [status, setStatus] = useState('');
  const [first] = selected;
  const index = first
    ? pages.findIndex((page) => page.ref.objectNumber === first.page.objectNumber)
    : -1;
  const next = pages[index + 1];

  const copyToNextPage = async () => {
    if (!first || !next) return;
    const copy = annotation.get(first.ref)!;
    const { annotation: made } = await annotation.create(next.ref, copy, undefined, {
      select: true, // so the next click copies the copy, a page further
    });
    stage.reveal(next.ref, { rect: made.rect });
    setStatus(`Copied to page ${next.index + 1}`);
  };

  return (
    <div className="toolbar">
      <button
        type="button"
        className="button"
        disabled={selected.length !== 1 || !next}
        onClick={() => void copyToNextPage()}
      >
        Copy to the next page
      </button>
      <span className="spacer" />
      <output className="readout">
        {status || (first ? 'Ready to copy' : 'Select an annotation to copy')}
      </output>
    </div>
  );
}

export default function App() {
  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <DocumentGate fallback={<p className="loading">Loading…</p>}>
        <AddAnnotations />
        <CopyButton />
        <Stage className="stage">
          {() => (
            <>
              <RenderLayer />
              <AnnotationLayer />
            </>
          )}
        </Stage>
      </DocumentGate>
    </Viewer>
  );
}
```

**`copy.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Pushes what follows to the right */
.spacer {
  flex: 1 1 auto;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 420px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

> **Try it:** copy the rectangle, then copy the copy: it moves a page further each time. Select the
> text box to copy it instead.

Stamps and file attachments also need their bytes. Download them first:

```ts
const drawing = await annotation.downloadResource(stamp.ref, 'appearance');
await annotation.create(otherPage.ref, stamp, { appearance: drawing });

const file = await annotation.downloadResource(attachment.ref, 'file');
await annotation.create(otherPage.ref, attachment, { file });
```

To copy on the **same page**, clear its unique name first: `{ ...copy, nm: null }`. A name is
unique per page, so a copy with a taken name is refused.

To copy many annotations at once, use [export and import](saving#export-and-import).

## Methods

What `useAnnotation()` gives you directly. Its parts have their own pages: `selection`, `draft` and
`text` are on [Selecting & editing](selecting#methods), `tools` and `stamps` on
[Tools & defaults](tools#methods), and `comments` on [Comments & replies](comments#methods).

| Method                                                         | What it does                                                                                                                                                                                                                                     |
| :------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get(ref)`                                                     | One annotation as it shows now, pending changes included, or `null`                                                                                                                                                                              |
| `list({ pages?, subtype? })`                                   | Every annotation in drawing order, or the ones on some pages or of one kind. In a component, use `useAnnotationList(filter)`. [Read annotations](#read-annotations)                                                                              |
| `getAt(page, point)`                                           | The topmost annotation at a point on a page, or `null`. A selected annotation's handles count as the annotation.                                                                                                                                 |
| `isPending(ref)`                                               | Whether the engine is still saving a change to it. [Changes show at once](#changes-show-at-once)                                                                                                                                                 |
| `create(page, fields, resources?, { tool?, select? })`         | Creates an annotation from its kind and fields, and shows it at once. A stamp's drawing or an attachment's file goes in `resources`, as `{ appearance }` or `{ file }`. Resolves `{ annotation }`. [Create an annotation](#create-an-annotation) |
| `createFromSelection(tool, { clear? })`                        | Turns the selected text into a highlight, an underline, a strikeout or a squiggly line, one per page. Resolves `{ annotations }`. [From the selected text](#from-the-selected-text)                                                              |
| `update(ref, changes, resources?)`                             | Changes only the fields you pass, and `null` removes one. New `resources` replace a stamp's drawing or an attachment's file. Resolves `{ annotation }`. [Change an annotation](#change-an-annotation)                                            |
| `delete(ref)`                                                  | Deletes an annotation, with its popup and its replies. [Delete an annotation](#delete-an-annotation)                                                                                                                                             |
| `reorder(refs, position)`                                      | Moves annotations to a new place in their page's drawing order: next to another annotation, or to the back or front. [Change the drawing order](#change-the-drawing-order)                                                                       |
| `export({ refs?, pages? })`, `import(bundle, options?)`        | Takes annotations out as a bundle, with the bytes they carry, or puts a bundle in. `import` resolves `{ annotations, refMap, dropped }`. [Export and import](saving#export-and-import)                                                           |
| `downloadResource(ref, role)`                                  | Downloads a stamp's drawing (`'appearance'`) or an attachment's file (`'file'`), ready to pass back to `create()`. [Copy an annotation](#copy-an-annotation)                                                                                     |
| `links.get(ref)`, `links.set(ref, target)`, `links.clear(ref)` | Where an annotation links to, such as a page or a website: read it, set it, or remove the link                                                                                                                                                   |
| `refresh()`                                                    | Reads every annotation from the engine again. Changes arrive by themselves, so you rarely need it.                                                                                                                                               |
| `getSettings()`, `updateSettings(changes)`, `resetSettings()`  | Reads `snap`, `chrome` and `afterCreate`, changes them while the app runs, or goes back to what you registered. In a component, use `useAnnotationSettings()`. [Settings](../annotations#settings)                                               |
| `canRead()`, `canCreate()`, `canUpdate(ref)`, `canDelete(ref)` | Whether the user may see, create, change or delete annotations. The checks that take a `ref` answer for that annotation. [Permissions](#permissions)                                                                                             |

Every method that returns a promise also takes a `signal` in its options, to cancel it.

## Permissions

| Permission           | What it allows     | Check                       |
| :------------------- | :----------------- | :-------------------------- |
| `doc.annotate.read`  | Seeing annotations | `annotation.canRead()`      |
| `annotations:create` | Creating them      | `annotation.canCreate()`    |
| `annotations:update` | Changing them      | `annotation.canUpdate(ref)` |
| `annotations:delete` | Deleting them      | `annotation.canDelete(ref)` |

A permission can end in `:self`, the user's own annotations, or `:group`, their group's.
`annotations:update:self` lets someone change what they made and only look at the rest, so the checks
that take a `ref` answer per annotation. They also follow the annotation's own flags: for a `locked`
one, `canDelete(ref)` is false, so your UI offers no delete button, even though your own code may still
delete it (see [Locked annotations](#locked-annotations)). More on
[Permissions](../concepts/permissions).

## Good to know

### Locked annotations

The `locked` flag stops people from moving, resizing or deleting an annotation, and
`lockedContents` stops them from changing its text. These flags are for the people using your
app: your own code can still change a locked annotation, as the engine can. To stop the code
too, check the flags first.

### What a copy can't carry

A few annotations can't be made again from their read, and `create()` refuses them with
`invalid-input`:

- a link whose action the engine can't create, such as one that runs JavaScript;
- a popup whose annotation is gone;
- a value another app wrote that the engine can't write, such as a beveled border;
- a kind the engine doesn't know (`subtype: 'unsupported'`).

[Import](saving#export-and-import) leaves these out and tells you which ones, and why.

### Changes other people make

On a shared document, other people's changes arrive as events and show up in `list()` by
themselves. See [Events, saving & sharing](saving).
