---
title: "Quick start — React"
description: "Build a PDF viewer step by step, with text selection, zoom buttons, a highlighter and a save button."
framework: "React"
source: "https://www.cloudpdf.com/docs/headless/react/quick-start"
---

# Quick start

This guide builds a small PDF viewer: the pages, text people can select, zoom buttons, a
highlighter and a save button. Each step adds one plugin or one control, so you can stop at any
step and have a working viewer.

## 1. Install

You need two packages: the one for your framework, and an engine.

```sh
pnpm add @embedpdf/react@next @cloudpdf/engine@next
```

Every feature is a separate import from the framework package, such as `@embedpdf/react/stage`,
so what you don't use never reaches your bundle.

## 2. Show a PDF

Create the engine once, then give it to `<Viewer>` with the plugins you want and the document to
open. `<Stage>` shows the pages, and `<RenderLayer>` draws each one:

**`basic.tsx`**

```tsx
import { Viewer, DocumentGate } 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 { cloudEngine } from '@cloudpdf/engine';

// The engine is created synchronously and costs nothing until first use, so
// a module-scope `const engine = …` is safe — even under SSR. Only opening a
// document does real work: the UI renders at t≈0.
const engine = cloudEngine();
const plugins = [stagePlugin(), renderPlugin()];

const ebook: OpenInput = { kind: 'share', shareToken: 'shr_WGj1goAtlNN_fQ5OswPrbJQM' };

export default function App() {
  return (
    <Viewer engine={engine} plugins={plugins} initialDocuments={[{ source: ebook }]}>
      <div style={{ height: 500 }}>
        {/* Document UI is defined over a document — gate it on having one. */}
        <DocumentGate fallback={<p>Loading…</p>}>
          <Stage style={{ height: '100%' }}>{() => <RenderLayer />}</Stage>
        </DocumentGate>
      </div>
    </Viewer>
  );
}
```

`cloudEngine()` holds an HTTP client and nothing else, so creating it at the top of a
module is safe, even with server-side rendering. Every page, text and annotation call goes to your
CloudPDF server; see [Setup](setup).

`<DocumentGate>` shows its `fallback` until the document is open, so the Stage never renders
without one.

## 3. Let people select text

Add the interaction plugin, which handles the pointer for every feature, and the selection plugin.
Then put `<SelectionLayer>` above the page:

**`select-text.tsx`**

```tsx
import { interactionPlugin } from '@embedpdf/react/interaction';
import { RenderLayer, renderPlugin } from '@embedpdf/react/render';
import { SelectionLayer, selectionPlugin } from '@embedpdf/react/selection';
import { Stage, stagePlugin } from '@embedpdf/react/stage';

export const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), selectionPlugin()];

export function Pages() {
  return (
    <Stage>
      {() => (
        <>
          <RenderLayer />
          <SelectionLayer />
        </>
      )}
    </Stage>
  );
}
```

Drag over text to select it, double-click a word, or triple-click a line. On a phone, long-press a
word. See [Selecting & copying](text/selection) for copying and a selection menu.

## 4. Add zoom buttons

Every plugin has a hook for its API and one for its state. `useStage()` does things,
and `useStageState()` gives you what to show:

**`zoom-controls.tsx`**

```tsx
import { useStage, useStageState } from '@embedpdf/react/stage';

export function ZoomControls() {
  const stage = useStage();
  const { zoomLevel } = useStageState();

  return (
    <div>
      <button onClick={() => stage.zoomOut()}>−</button>
      <span>{Math.round(zoomLevel * 100)}%</span>
      <button onClick={() => stage.zoomIn()}>+</button>
    </div>
  );
}
```

Put `<ZoomControls />` anywhere inside `<DocumentGate>`. It re-renders when the zoom
changes, whether a button, a pinch or code changed it.

## 5. Add a highlighter

The annotation plugin shows the annotations a PDF already has, and lets people add their own. Add
it with its layer, and a button that turns on the highlight tool:

**`highlighter.tsx`**

```tsx
import { AnnotationLayer, annotationPlugin } from '@embedpdf/react/annotation';
import { useInteraction, useInteractionState } from '@embedpdf/react/interaction';
import { RenderLayer } from '@embedpdf/react/render';
import { SelectionLayer } from '@embedpdf/react/selection';
import { Stage } from '@embedpdf/react/stage';

export const plugins = [/* your other plugins */ annotationPlugin()];

export function HighlightButton() {
  const interaction = useInteraction();
  const { activeToolId } = useInteractionState();
  const on = activeToolId === 'highlight';

  return (
    <button aria-pressed={on} onClick={() => interaction.activateTool(on ? 'pointer' : 'highlight')}>
      Highlight
    </button>
  );
}

export function Pages() {
  return (
    <Stage>
      {() => (
        <>
          <RenderLayer />
          <SelectionLayer />
          <AnnotationLayer />
        </>
      )}
    </Stage>
  );
}
```

With the tool on, select text and it's highlighted. Click a highlight with the `pointer` tool to
select it. The page picture leaves the annotations out by itself, because the annotation layer
draws them now. See [Annotations](annotations) for every
tool.

## 6. Save

`documents.download()` gives you the PDF with every change, as bytes. `saveFile` hands them to the
browser as a download:

**`save-button.tsx`**

```tsx
import { saveFile, useDocuments } from '@embedpdf/react/runtime';

export function SaveButton() {
  const documents = useDocuments();
  return (
    <button onClick={async () => saveFile(await documents.download(), 'reviewed.pdf')}>Save</button>
  );
}
```

Put `<SaveButton />` anywhere inside `<Viewer>`.


You don't need to save to keep the highlights: the server stored each one as it was made.
`download()` is for when you want the file itself.

## Next steps

- [Setup](setup) — Server-side rendering, bundlers, fonts, and hosting the engine yourself.
- [How it works](concepts/how-it-works) — Plugins, how your UI talks to them, layers, and which document a component talks to.
- [Scroll, zoom & layout](viewing/stage) — Layouts, zoom modes, page navigation and touch.
