Build your first page

Add Imposia to a React app, render HTML as A4 pages, then print or save them as PDF.

This guide adds a paginated preview to an existing React app. When you finish, the same component will show your HTML as A4 pages and open the browser's native print flow for paper or PDF output.

Before you begin

You need React 18 or later and a client-side browser environment. Imposia does not render pages in Node.js or during server-side rendering, so mount it below your framework's client boundary.

Install the React package

pnpm add @imposia/react react react-dom

Import the package stylesheet once — in this component or in your application's client entry file. Importing it more than once is harmless, but forgetting it entirely leaves the Viewer unstyled.

Render a page document

app/preview.tsx
import { ImposiaPageViewer, type ImposiaPageViewerHandle } from "@imposia/react";
import { useRef } from "react";
import "@imposia/react/styles.css";

export function Preview() {
  const viewer = useRef<ImposiaPageViewerHandle>(null);

  return (
    <>
      <ImposiaPageViewer
        ref={viewer}
        source={{ html: "<article><h1>Hello</h1><p>Browser-native pages.</p></article>" }}
        documentOptions={{ page: { size: "A4", margin: "18mm" } }}
      />
      <button type="button" onClick={() => void viewer.current?.print()}>
        Print / Save as PDF
      </button>
    </>
  );
}

source.html is the content to paginate, and documentOptions.page sets the sheet size and margin. The handle always points at the latest page document that finished successfully — never at one still being built.

Confirm the result

Open the component in a browser. Your HTML should appear as an A4 page inside the Viewer.

Click Print / Save as PDF. The browser's own print dialog opens for the pages currently shown, where the reader chooses a printer or Save as PDF. The method resolves once the dialog has been dispatched; it does not return PDF bytes.

Show progress and failures

The example above renders a static string, so it commits almost immediately. Real sources take time — images settle, fonts load, long documents paginate across many pages. Use useImposiaDocument when you need to show that work.

app/preview-with-state.tsx
import { useImposiaDocument } from "@imposia/react";

export function Preview({ html }: { html: string }) {
  const { hostRef, state } = useImposiaDocument({ source: { html } });

  return (
    <>
      {state.status === "loading" && <p role="status">Preparing pages…</p>}
      {state.status === "error" && <p role="alert">Could not paginate this document.</p>}
      <div ref={hostRef} />
    </>
  );
}

state.status is idle, loading, ready, aborted, or error. After a first success, loading keeps the previous pages visible while preparing the next source, so put a spinner beside the preview. error and aborted can also retain the last successful state.document. Cancellation is terminal and does not call onError; stop the loading indicator. state.source is present only in ready and identifies the source used for that commit.

Choose when preparation starts

preparation: "immediate" is the default. Choose "idle" to schedule the first preparation during browser idle time, or "manual" to wait for prepare() or retry(). After preparation starts, source changes update normally. Changing the policy does not pause an active controller. These methods belong to the single-document API; useImposiaPublication does not expose them. A retry with an already-aborted options signal needs a fresh signal and documentOptionsRevision; see the React API.

app/manual-preview.tsx
import { useImposiaDocument } from "@imposia/react";

export function Preview({ html }: { html: string }) {
  const { hostRef, state, prepare, retry } = useImposiaDocument({
    source: { html },
    preparation: "manual",
  });
  return (
    <>
      <button type="button" onClick={prepare} disabled={state.status !== "idle"}>Prepare pages</button>
      {state.status === "error" && <button type="button" onClick={retry}>Retry</button>}
      {state.status === "aborted" && <p role="status">Preparation cancelled.</p>}
      <div ref={hostRef} />
    </>
  );
}

Optional: export an EPUB

If your product also needs a semantic, reflowable artifact, the same committed document can produce an EPUB 3.3 Blob. This is a side output — it is not part of the preview-and-print path above, and you can skip it entirely.

Metadata is required; the export rejects without a title, language, and identifier.

app/export-epub.ts
const blob = await viewer.current?.exportEpub({
  metadata: { title: "Hello", language: "en", identifier: "urn:example:hello" },
});

The method returns the Blob; connecting it to a download or upload is your application's job. EPUB output is built from the semantic source, not from the paginated page DOM, so page furniture and generated page numbers are deliberately absent.

Next steps

On this page