Sizing and alignment
How container width, page margin, and align interact — and how to make your DOM exactly fill the page.
react-print-pdf is a camera, not a scaler. It reads each element's pixel position from the live DOM and stamps those exact coordinates into the PDF. It never resizes your content. That makes the export pixel-faithful to what you see on screen, but it also means three independent widths are at play on every export.
The three widths
PDF page width ── fixed by format + orientation
A4 portrait = 794 px @ 96dpi
A4 landscape = 1123 px
Letter port. = 816 px
Page content area width ── pageWidth − marginLeft − marginRight
0 mm margins → 794 px (full page)
10 mm margins → 718 px
20 mm margins → 642 px
Your container width ── whatever your CSS says
<div className="w-[640px]"> → always 640 pxIf your container is narrower than the content area, the leftover space
is slack. The align option decides where the
slack goes: "center" (default) splits it evenly, "left" pushes content
to the left margin, "right" to the right margin.
If your container is wider than the content area, content gets clipped on the right. The library does not auto-shrink.
The fix: match container to content area
Use the pageContentWidth
helper to size your root exportable container exactly to the page's
content area, so there is no slack at all:
import { exportToPDF, pageContentWidth } from "react-print-pdf";
function Invoice() {
const ref = useRef<HTMLDivElement>(null);
const width = pageContentWidth({ format: "A4", margin: "10mm" }); // 718 px
return (
<>
<div ref={ref} style={{ width }}>
{/* your content fills the page exactly */}
</div>
<button
onClick={() =>
exportToPDF(ref.current!, { format: "A4", margin: "10mm" })
}
>
Export
</button>
</>
);
}Now align is irrelevant — the DOM and the content area are the same width,
so there's nothing to align.
When you don't know the page format up front
If you let users pick A4 vs Letter at runtime, recompute the width from the
same options you pass to exportToPDF:
const opts = { format, orientation, margin };
const width = pageContentWidth(opts);
return (
<>
<div ref={ref} style={{ width }}>...</div>
<button onClick={() => exportToPDF(ref.current!, opts)}>Export</button>
</>
);Single source of truth (recommended pattern)
The most common bug is sizing the container from one set of options and exporting with a different set. The library does not auto-shrink your DOM, so a width / margin mismatch silently clips the right edge of the page.
Keep one options object and use it everywhere:
const pageOpts = { format: "A4", margin: "10mm" } as const;
<div ref={ref} style={{ width: pageContentWidth(pageOpts) }}>
<Invoice />
</div>
<button onClick={() => exportToPDF(ref.current!, { ...pageOpts, ...other })}>
Export
</button>If the source DOM is wider than the page content area at export time,
exportToPDF logs a console warning explaining what it measured, what was
expected, and how to fix it. There is no silent clipping with a debugger
attached.
Authoring receipts and single-page documents
If paper size doesn't matter — you just want a PDF that's exactly the size
of your DOM — use pageBreak: "single":
exportToPDF(ref.current, { pageBreak: "single", margin: 0 });The page is sized to your content's bounding box. No slack, no clipping, no worries about A4 vs Letter.
Why is align "center" by default?
Because the most common user mistake is hard-coding a container width that
happens to be slightly narrower than the content area (e.g. w-[640px] on
A4 with 10mm margins). With align: "left" that produces a lopsided PDF.
With align: "center" the slack is split evenly so the document still
looks balanced even when the user hasn't sized their root container
precisely.
If you size your container with pageContentWidth(...), the alignment
choice doesn't matter — there's no slack to distribute.
Summary
| Goal | Recipe |
|---|---|
| Exactly fill the page | Size root with pageContentWidth({ format, margin }) |
| Center a fixed-width DOM | Default behavior (no extra config) |
| Left-anchor a fixed-width DOM | align: "left" |
| Page sized to content | pageBreak: "single" |
| Edge-to-edge content | margin: 0 plus pageContentWidth({ format }) for width |